From 41f81d10d79feb00cfe5ac176630cd84522be6eb Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 8 Jun 2026 19:35:20 +0100 Subject: [PATCH 1/4] feat(cli): set up AI tooling in trigger init and add getting-started skill init now offers the MCP server and agent skills together as one AI-tooling step, then lets you scaffold with the CLI or hand off to your assistant. The new getting-started skill is the bootstrap recipe that hand-off path uses: install the SDK, write trigger.config.ts, create a first task, run dev. Shared prompt-once gating means trigger dev no longer asks about skills separately. --- .changeset/cli-init-ai-tooling.md | 5 + .../cli-v3/skills/getting-started/SKILL.md | 214 ++++++++++++++++++ packages/cli-v3/src/commands/init.ts | 68 +++++- packages/cli-v3/src/commands/skills.ts | 40 ++++ 4 files changed, 316 insertions(+), 11 deletions(-) create mode 100644 .changeset/cli-init-ai-tooling.md create mode 100644 packages/cli-v3/skills/getting-started/SKILL.md diff --git a/.changeset/cli-init-ai-tooling.md b/.changeset/cli-init-ai-tooling.md new file mode 100644 index 00000000000..a7a4c8be14a --- /dev/null +++ b/.changeset/cli-init-ai-tooling.md @@ -0,0 +1,5 @@ +--- +"trigger.dev": patch +--- + +`trigger init` now sets up your AI coding assistant as part of project setup: pick the MCP server, the agent skills, or both, then scaffold with the CLI or hand off to your assistant. Adds a new `getting-started` agent skill that teaches assistants how to bootstrap Trigger.dev (install the SDK, write `trigger.config.ts`, create a first task, run `trigger dev`), so the AI-driven setup path works end to end. It ships in the CLI alongside the existing skills, version-matched to your SDK. diff --git a/packages/cli-v3/skills/getting-started/SKILL.md b/packages/cli-v3/skills/getting-started/SKILL.md new file mode 100644 index 00000000000..a84243bd398 --- /dev/null +++ b/packages/cli-v3/skills/getting-started/SKILL.md @@ -0,0 +1,214 @@ +--- +name: getting-started +description: > + Bootstrap Trigger.dev into an existing project from scratch: authenticate the + CLI, install @trigger.dev/sdk and @trigger.dev/build, write trigger.config.ts + with the project ref and task dirs, scaffold a /trigger directory with a first + task, wire tsconfig and .gitignore, set TRIGGER_SECRET_KEY, and run the dev + server. Load this when a project has no trigger.config.ts yet and the user + asks to "add Trigger.dev", "set up Trigger.dev", "initialize Trigger.dev", or + get a first task running, including in a monorepo. Once the project is set up + and you are writing task code, switch to the authoring-tasks skill. +type: core +library: trigger.dev +library_version: "{{TRIGGER_SDK_VERSION}}" +sources: + - docs/quick-start.mdx + - docs/manual-setup.mdx + - docs/config/config-file.mdx + - docs/triggering.mdx +--- + +# Getting started with Trigger.dev + +Set up Trigger.dev in an existing project. The end state is: the SDK installed, a +`trigger.config.ts` pointing at a project ref, a `/trigger` directory with at least +one exported task, and `trigger dev` running so the task shows up in the dashboard. + +The fastest path is the CLI's own wizard, which performs every mechanical step below +and also offers to install the MCP server and these agent skills: + +```bash +npx trigger.dev@latest init +``` + +Prefer `init` when you can. Do the manual steps further down when `init` does not fit +(monorepos, an existing config to extend, or a non-interactive environment). + +## Two steps need the human + +Most of setup is automatable, but two steps require a person and cannot be done +headlessly. When you reach them, stop and ask the user to do them, then continue: + +1. **Authenticating the CLI.** `npx trigger.dev@latest login` opens a browser for the + user to sign in. If they have no account, point them to https://cloud.trigger.dev + (or a self-hosted instance) first. You cannot complete this for them. +2. **The secret key and project ref.** `TRIGGER_SECRET_KEY` and the project ref + (`proj_...`) come from the dashboard. Ask the user to copy the **DEV** secret key + from the project's API Keys page, and to pick or create the project so you have its + ref. `trigger init` can select the project interactively once the user is logged in. + +Treat these as handoffs: state exactly what you need, wait for the user, then resume. + +## Manual setup + +### 1. Authenticate (human step) + +```bash +npx trigger.dev@latest login +# self-hosted: +npx trigger.dev@latest login --api-url https://your-trigger-instance.com +``` + +### 2. Install the packages + +`@trigger.dev/sdk` is a runtime dependency; `@trigger.dev/build` is a dev dependency. +Pin both to the same version as the `trigger.dev` CLI you run; the CLI warns on a +mismatch during `dev`/`deploy`. + +```bash +npm add @trigger.dev/sdk@latest +npm add --save-dev @trigger.dev/build@latest +``` + +### 3. Write `trigger.config.ts` + +Create it in the project root (or `trigger.config.mjs` for JavaScript). The `project` +ref and `dirs` are the only required fields. + +```ts +import { defineConfig } from "@trigger.dev/sdk"; + +export default defineConfig({ + project: "", // e.g. "proj_abc123", from the dashboard + dirs: ["./src/trigger"], // where your tasks live + maxDuration: 3600, + retries: { + enabledInDev: false, + default: { maxAttempts: 3, factor: 2, minTimeoutInMs: 1000, maxTimeoutInMs: 10000, randomize: true }, + }, +}); +``` + +Use the Bun runtime by adding `runtime: "bun"`. Build extensions (`prismaExtension`, +`puppeteer`, `additionalFiles`, etc.) come from `@trigger.dev/build` and go in +`build.extensions`. + +### 4. Add a first task + +Create the directory that matches `dirs` and export a task from it. Every task must be +a named export with a project-unique `id`. + +```ts +// src/trigger/example.ts +import { task } from "@trigger.dev/sdk"; + +export const helloWorld = task({ + id: "hello-world", + run: async (payload: { name: string }) => { + return { message: `Hello ${payload.name}!` }; + }, +}); +``` + +### 5. Wire tsconfig and gitignore + +Add `trigger.config.ts` to the `include` array in `tsconfig.json`, and add `.trigger` +to `.gitignore` (the CLI writes local dev state there). + +```jsonc +// tsconfig.json +{ "include": ["trigger.config.ts" /* ...existing */] } +``` + +```bash +# .gitignore +.trigger +``` + +### 6. Set the secret key (human step) + +For triggering from your own code, set `TRIGGER_SECRET_KEY` to the DEV key from the +dashboard's API Keys page. Self-hosted users also set `TRIGGER_API_URL`. + +```bash +# .env (or .env.local for Next.js) +TRIGGER_SECRET_KEY=tr_dev_xxxxxxxx +``` + +### 7. Run the dev server + +```bash +npx trigger.dev@latest dev +``` + +Leave it running. Tasks register with the dashboard, where the user can fire a test run +from the task's test page. On first run the CLI offers to install the MCP server and +agent skills; recommend both. + +## Triggering from your app + +Once a task exists, trigger it from backend code with a **type-only** import so the +task code is never bundled into your app. Trigger by id, not by calling the task object. + +```ts +import { tasks } from "@trigger.dev/sdk"; +import type { helloWorld } from "@/trigger/example"; // type-only + +const handle = await tasks.trigger("hello-world", { name: "Ada" }); +``` + +`TRIGGER_SECRET_KEY` must be set wherever this runs. Framework specifics live in the +Next.js / Remix / Node.js guides. + +## Monorepos + +Two layouts, both supported: put tasks in a shared package (`@repo/tasks` with its own +`trigger.config.ts`, consumed via `workspace:*`), or install Trigger.dev directly in the +app that needs it. Run `trigger dev` from the directory that holds `trigger.config.ts`. +See the manual setup docs for full Turborepo examples before scaffolding either. + +## Common mistakes + +1. **Trying to do the human-only steps headlessly.** You cannot complete `trigger login` + or read the dashboard secret key for the user. + - Wrong: spawning `trigger login` and waiting on it to finish in an agent session. + - Correct: ask the user to log in and to paste the DEV key, then continue. + +2. **Mismatched CLI and SDK versions.** A `trigger.dev` CLI on a different major than + `@trigger.dev/sdk` breaks dev/deploy. + - Wrong: `npx trigger.dev@latest dev` against an old pinned SDK. + - Correct: keep `trigger.dev`, `@trigger.dev/sdk`, and `@trigger.dev/build` on the same version. + +3. **Importing from `@trigger.dev/sdk/v3` or using `client.defineJob()`.** Both are old. + - Correct: always import from `@trigger.dev/sdk`; define work with `task()`. + +4. **Tasks not exported, or outside `dirs`.** A task that is not a named export inside a + configured directory will not be picked up. + - Correct: `export const ... = task({ ... })` in a file under a `dirs` path. + +5. **Importing the task instance into backend code.** This bundles the task. + - Wrong: `import { helloWorld } from "@/trigger/example"` in a route handler. + - Correct: `import type { helloWorld }` plus `tasks.trigger("hello-world", payload)`. + +6. **Forgetting `TRIGGER_SECRET_KEY`.** Triggering from your app fails without it; the + `dev` server itself works once the CLI is logged in. + +## References + +Sibling skills: + +- **authoring-tasks** for writing the tasks themselves once setup is done: retries, waits, + queues, scheduled tasks, triggering, and the full `trigger.config.ts`. +- **realtime-and-frontend** for showing live run status in a frontend. +- **authoring-chat-agent** and **chat-agent-advanced** for building AI chat agents. + +Docs: + +- [Quick start](https://trigger.dev/docs/quick-start) +- [Manual setup](https://trigger.dev/docs/manual-setup) +- [Configuration file](https://trigger.dev/docs/config/config-file) + +## Version + +Generated for @trigger.dev/sdk {{TRIGGER_SDK_VERSION}}. Re-run the trigger.dev skills installer after upgrading. diff --git a/packages/cli-v3/src/commands/init.ts b/packages/cli-v3/src/commands/init.ts index 4716db1f99c..1b388749386 100644 --- a/packages/cli-v3/src/commands/init.ts +++ b/packages/cli-v3/src/commands/init.ts @@ -1,4 +1,4 @@ -import { intro, isCancel, log, outro, select, text } from "@clack/prompts"; +import { intro, isCancel, log, multiselect, outro, select, text } from "@clack/prompts"; import { context, trace } from "@opentelemetry/api"; import { GetProjectResponseBody, @@ -43,6 +43,7 @@ import { writeConfigHasSeenMCPInstallPrompt, } from "../utilities/configFiles.js"; import { installMcpServer } from "./install-mcp.js"; +import { installSkillsFromInit, markSkillsPromptSeen } from "./skills.js"; const cliVersion = VERSION as string; const cliTag = cliVersion.includes("v4-beta") ? "v4-beta" : "latest"; @@ -147,27 +148,46 @@ async function _initCommand(dir: string, options: InitCommandOptions) { const hasSeenMCPInstallPrompt = readConfigHasSeenMCPInstallPrompt(); - // Skip the MCP-vs-CLI prompt when --yes is set: the user explicitly chose CLI - // by running `trigger.dev init` non-interactively, and the prompt would + // Skip the AI-tooling prompt when --yes is set: the user explicitly chose the CLI + // scaffold by running `trigger.dev init` non-interactively, and the prompt would // otherwise hang on a fresh machine where `hasSeenMCPInstallPrompt` is false. if (!hasSeenMCPInstallPrompt && !options.yes) { - const installChoice = await select({ - message: "Choose how you want to initialize your project:", + const tooling = await multiselect({ + message: "Set up AI tooling for your coding assistant? (optional, space to toggle)", options: [ { value: "mcp", - label: "Trigger.dev MCP", - hint: "Automatically install the Trigger.dev MCP server and then vibe your way to a new project.", + label: "MCP server", + hint: "live access to your project: trigger tasks, deploy, monitor runs", + }, + { + value: "skills", + label: "Agent skills", + hint: "teach your AI to write Trigger.dev code, version-matched to your SDK", }, - { value: "cli", label: "CLI", hint: "Continue with the CLI" }, ], + required: false, }); writeConfigHasSeenMCPInstallPrompt(true); - const continueWithCLI = isCancel(installChoice) || installChoice === "cli"; + const selectedTooling = isCancel(tooling) ? [] : tooling; - if (!continueWithCLI) { + // Skills are auth-free and bundled in the CLI. The user opted in here, so install + // straight away (no extra confirm). If they declined, still mark the prompt seen so + // `trigger dev` doesn't ask about skills a second time. + if (selectedTooling.includes("skills")) { + log.step("Installing the Trigger.dev agent skills"); + const [skillsError] = await tryCatch(installSkillsFromInit()); + if (skillsError) { + log.warn(`Skipped agent skills: ${skillsError.message}`); + } + } else { + await tryCatch(markSkillsPromptSeen()); + } + + // The MCP server is also auth-free. + if (selectedTooling.includes("mcp")) { log.step("Welcome to the Trigger.dev MCP server install wizard 🧙"); const [installError] = await tryCatch( @@ -182,8 +202,34 @@ async function _initCommand(dir: string, options: InitCommandOptions) { outro(`Failed to install MCP server: ${installError.message}`); return; } + } - return; + // Vibe path: once AI tooling is set up, the user can hand scaffolding to their + // assistant (the getting-started skill + MCP) instead of the CLI. Only offered when + // they installed something that can drive it. + if (selectedTooling.length > 0) { + const setupChoice = await select({ + message: "How do you want to set up your project?", + options: [ + { + value: "cli", + label: "Scaffold it now with the CLI", + hint: "log in, create trigger.config.ts and an example task", + }, + { + value: "ai", + label: "Let my AI assistant set it up", + hint: "use the getting-started skill and MCP server to bootstrap", + }, + ], + }); + + if (!isCancel(setupChoice) && setupChoice === "ai") { + outro( + "Your AI tooling is ready. Ask your assistant to set up Trigger.dev and it will use the getting-started skill to add the SDK, config, and your first task." + ); + return; + } } } diff --git a/packages/cli-v3/src/commands/skills.ts b/packages/cli-v3/src/commands/skills.ts index 9d5d2e6af66..d811099dc8a 100644 --- a/packages/cli-v3/src/commands/skills.ts +++ b/packages/cli-v3/src/commands/skills.ts @@ -208,6 +208,46 @@ export async function initiateSkillsInstallWizard(options: SkillsWizardOptions) } } +/** + * Mark the agent-skills install prompt as already seen at the current skills version. + * `trigger init` calls this after offering skills in its AI-tooling step (whether or not + * the user installs them) so `trigger dev` doesn't ask about skills again. Returns false + * if the CLI ships without bundled skills. + */ +export async function markSkillsPromptSeen(): Promise { + const manifest = await loadSkillsManifest(); + + if (!manifest) { + return false; + } + + writeConfigHasSeenRulesInstallPrompt(true); + writeConfigLastRulesInstallPromptVersion(manifest.currentVersion); + + return true; +} + +/** + * Install skills as part of `trigger init`. The user already opted in via init's AI-tooling + * prompt, so this skips the extra confirm and goes straight to target/skill selection, then + * marks the prompt seen so `trigger dev` won't re-prompt. Returns false if the CLI ships + * without bundled skills. + */ +export async function installSkillsFromInit(opts: SkillsWizardOptions = {}): Promise { + const manifest = await loadSkillsManifest(); + + if (!manifest) { + return false; + } + + writeConfigHasSeenRulesInstallPrompt(true); + writeConfigLastRulesInstallPromptVersion(manifest.currentVersion); + + await installSkills(manifest, opts); + + return true; +} + async function installSkills(manifest: RulesManifest, opts: SkillsWizardOptions) { const currentVersion = await manifest.getCurrentVersion(); From d07913fd7f5dfd1289af7c1ad0cb5ee6e9fe8d11 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 8 Jun 2026 22:13:43 +0100 Subject: [PATCH 2/4] fix(cli): gate init AI hand-off on actual install, fix profile flag The "let my AI set it up" hand-off was gated on what the user selected, not what installed, so a failed skills install still offered it and claimed the getting-started skill was ready. Gate it on actual installs and describe only the tooling that landed. Also fix an inverted profile flag in the next-steps message that printed `--profile undefined` when no profile was set. --- packages/cli-v3/src/commands/init.ts | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/cli-v3/src/commands/init.ts b/packages/cli-v3/src/commands/init.ts index 1b388749386..d229f69dd04 100644 --- a/packages/cli-v3/src/commands/init.ts +++ b/packages/cli-v3/src/commands/init.ts @@ -173,14 +173,21 @@ async function _initCommand(dir: string, options: InitCommandOptions) { const selectedTooling = isCancel(tooling) ? [] : tooling; + // Track what actually installed (not just what was selected), so the AI hand-off is + // only offered, and only described, in terms of tooling that really landed. + let installedSkills = false; + let installedMcp = false; + // Skills are auth-free and bundled in the CLI. The user opted in here, so install // straight away (no extra confirm). If they declined, still mark the prompt seen so // `trigger dev` doesn't ask about skills a second time. if (selectedTooling.includes("skills")) { log.step("Installing the Trigger.dev agent skills"); - const [skillsError] = await tryCatch(installSkillsFromInit()); + const [skillsError, installed] = await tryCatch(installSkillsFromInit()); if (skillsError) { log.warn(`Skipped agent skills: ${skillsError.message}`); + } else { + installedSkills = installed === true; } } else { await tryCatch(markSkillsPromptSeen()); @@ -202,12 +209,14 @@ async function _initCommand(dir: string, options: InitCommandOptions) { outro(`Failed to install MCP server: ${installError.message}`); return; } + + installedMcp = true; } - // Vibe path: once AI tooling is set up, the user can hand scaffolding to their - // assistant (the getting-started skill + MCP) instead of the CLI. Only offered when - // they installed something that can drive it. - if (selectedTooling.length > 0) { + // Vibe path: once AI tooling is actually installed, the user can hand scaffolding to + // their assistant instead of the CLI. Only offered when something landed, and the + // hand-off message names only the tooling that did. + if (installedSkills || installedMcp) { const setupChoice = await select({ message: "How do you want to set up your project?", options: [ @@ -219,14 +228,16 @@ async function _initCommand(dir: string, options: InitCommandOptions) { { value: "ai", label: "Let my AI assistant set it up", - hint: "use the getting-started skill and MCP server to bootstrap", + hint: "hand off and let your assistant bootstrap the project", }, ], }); if (!isCancel(setupChoice) && setupChoice === "ai") { outro( - "Your AI tooling is ready. Ask your assistant to set up Trigger.dev and it will use the getting-started skill to add the SDK, config, and your first task." + installedSkills + ? "Your AI tooling is ready. Ask your assistant to set up Trigger.dev and it will use the getting-started skill to add the SDK, config, and your first task." + : "The MCP server is installed. Ask your assistant to set up Trigger.dev using the MCP server." ); return; } @@ -334,7 +345,7 @@ async function _initCommand(dir: string, options: InitCommandOptions) { log.info("Next steps:"); log.info( ` 1. To start developing, run ${chalk.green( - `npx trigger.dev@${cliTag} dev${options.profile ? "" : ` --profile ${options.profile}`}` + `npx trigger.dev@${cliTag} dev${options.profile ? ` --profile ${options.profile}` : ""}` )} in your project directory` ); log.info(` 2. Visit your ${projectDashboard} to view your newly created tasks.`); From d3f6f7b31a4ac1641720d3e60c7dc48f268306c7 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Tue, 9 Jun 2026 06:14:53 +0100 Subject: [PATCH 3/4] fix(cli): resolve bundled skills from the package root, not the tshy dist stub The skills loader resolved the package.json nearest the bundled code, which is tshy's `dist/esm` dialect stub ({"type":"module"}), so it looked for skills in `dist/esm/skills` (which does not exist) and reported "No agent skills found" from every published build. It only worked when run from source via tsx. Walk up to the first package.json with a name (the real package root) instead, which resolves correctly both bundled and from source, and also fixes the version stamp falling back to 0.0.0. --- packages/cli-v3/src/commands/skills.test.ts | 61 +++++++++++++++++++++ packages/cli-v3/src/commands/skills.ts | 37 ++++++++++++- 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 packages/cli-v3/src/commands/skills.test.ts diff --git a/packages/cli-v3/src/commands/skills.test.ts b/packages/cli-v3/src/commands/skills.test.ts new file mode 100644 index 00000000000..01a6a46442b --- /dev/null +++ b/packages/cli-v3/src/commands/skills.test.ts @@ -0,0 +1,61 @@ +import { afterAll, describe, expect, it } from "vitest"; +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { resolveBundledPackageJSON } from "./skills.js"; + +/** + * Reproduces the published layout: tshy emits a dialect stub `package.json` + * ({"type":"module"}) in `dist/esm`, which shadows the real package root when resolving + * from the bundled code. The skills dir ships at the package root. Resolution must skip + * the stub and land on the root, otherwise `trigger skills` silently finds no skills. + */ +async function makeBundledPackage(): Promise<{ root: string; distEsm: string }> { + const root = await mkdtemp(join(tmpdir(), "bundled-cli-")); + + await writeFile( + join(root, "package.json"), + JSON.stringify({ name: "trigger.dev", version: "9.9.9-test.1" }) + ); + + await mkdir(join(root, "skills", "authoring-tasks"), { recursive: true }); + await writeFile(join(root, "skills", "authoring-tasks", "SKILL.md"), "# Authoring"); + + const distEsm = join(root, "dist", "esm"); + await mkdir(distEsm, { recursive: true }); + // The tshy dialect stub that caused the bug. + await writeFile(join(distEsm, "package.json"), JSON.stringify({ type: "module" })); + + return { root, distEsm }; +} + +describe("resolveBundledPackageJSON", () => { + const roots: string[] = []; + + afterAll(async () => { + await Promise.all(roots.map((d) => rm(d, { recursive: true, force: true }))); + }); + + it("skips the tshy dist/esm dialect stub and resolves the package root", async () => { + const { root, distEsm } = await makeBundledPackage(); + roots.push(root); + + const resolved = await resolveBundledPackageJSON(distEsm); + + // Must be the root package.json (which has `skills/` beside it), not the dist/esm stub. + expect(resolved).toBe(join(root, "package.json")); + expect(dirname(resolved!)).toBe(root); + }); + + it("resolves directly when started from a dir under the named package root (source/tsx path)", async () => { + const { root } = await makeBundledPackage(); + roots.push(root); + + const srcDir = join(root, "src"); + await mkdir(srcDir, { recursive: true }); + + const resolved = await resolveBundledPackageJSON(srcDir); + + expect(resolved).toBe(join(root, "package.json")); + }); +}); diff --git a/packages/cli-v3/src/commands/skills.ts b/packages/cli-v3/src/commands/skills.ts index d811099dc8a..d732207a678 100644 --- a/packages/cli-v3/src/commands/skills.ts +++ b/packages/cli-v3/src/commands/skills.ts @@ -100,10 +100,45 @@ export async function installSkillsCommand(options: unknown) { * `sourceDir` (the CLI's location) rather than the user's cwd. The CLI is the only source * of skills (there is no remote fallback), so this only returns null in the unexpected * case that the CLI ships without any skills. + * + * tshy emits a dialect stub `package.json` ({"type":"module"}) in `dist/esm`, so the + * package.json nearest the bundled code is NOT the package root and has no `skills/` + * beside it. We walk up to the first package.json that has a `name` (the real root); + * that resolves correctly both when bundled (`/dist/esm`) and from source + * (`/src`, run via tsx in dev/tests). */ +export async function resolveBundledPackageJSON(startDir: string = sourceDir): Promise< + string | null +> { + let searchDir = startDir; + + for (let i = 0; i < 10; i++) { + const candidate = await resolvePackageJSON(searchDir); + const pkg = await readPackageJSON(candidate); + + if (pkg.name) { + return candidate; + } + + // Climb above this (stub) package.json and keep looking for the real root. + const above = dirname(dirname(candidate)); + if (above === searchDir) { + return null; + } + searchDir = above; + } + + return null; +} + async function loadSkillsManifest(): Promise { try { - const packageJsonPath = await resolvePackageJSON(sourceDir); + const packageJsonPath = await resolveBundledPackageJSON(); + + if (!packageJsonPath) { + return null; + } + const pkg = await readPackageJSON(packageJsonPath); const skillsDir = join(dirname(packageJsonPath), "skills"); const version = typeof pkg.version === "string" ? pkg.version : "0.0.0"; From f4762fb97e1a47bc3fb74a9868dc2f3957b7450c Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Tue, 9 Jun 2026 09:30:25 +0100 Subject: [PATCH 4/4] fix(cli): harden init AI-tooling hand-off and report real skills install Warn and continue when MCP install fails instead of aborting init, so an already-installed skill and the project scaffold are not lost. installSkills now reports whether anything was actually written, so the AI hand-off is only offered and described when skills really landed (not when only an unsupported target was chosen). The hand-off message now covers skills, MCP, or both. --- packages/cli-v3/src/commands/init.ts | 17 ++++++++++------- packages/cli-v3/src/commands/skills.ts | 17 +++++++++++------ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/packages/cli-v3/src/commands/init.ts b/packages/cli-v3/src/commands/init.ts index d229f69dd04..08fe4ebf9eb 100644 --- a/packages/cli-v3/src/commands/init.ts +++ b/packages/cli-v3/src/commands/init.ts @@ -206,11 +206,12 @@ async function _initCommand(dir: string, options: InitCommandOptions) { ); if (installError) { - outro(`Failed to install MCP server: ${installError.message}`); - return; + // Don't abort init if MCP fails: skills may already be installed and the user + // still needs the project scaffolded. Warn and carry on. + log.warn(`Skipped MCP server: ${installError.message}`); + } else { + installedMcp = true; } - - installedMcp = true; } // Vibe path: once AI tooling is actually installed, the user can hand scaffolding to @@ -235,9 +236,11 @@ async function _initCommand(dir: string, options: InitCommandOptions) { if (!isCancel(setupChoice) && setupChoice === "ai") { outro( - installedSkills - ? "Your AI tooling is ready. Ask your assistant to set up Trigger.dev and it will use the getting-started skill to add the SDK, config, and your first task." - : "The MCP server is installed. Ask your assistant to set up Trigger.dev using the MCP server." + installedSkills && installedMcp + ? "Your AI tooling is ready. Ask your assistant to set up Trigger.dev; it can use the getting-started skill and the MCP server to add the SDK, config, and your first task." + : installedSkills + ? "Your AI tooling is ready. Ask your assistant to set up Trigger.dev and it will use the getting-started skill to add the SDK, config, and your first task." + : "The MCP server is installed. Ask your assistant to set up Trigger.dev using the MCP server." ); return; } diff --git a/packages/cli-v3/src/commands/skills.ts b/packages/cli-v3/src/commands/skills.ts index d732207a678..b2d4613b02d 100644 --- a/packages/cli-v3/src/commands/skills.ts +++ b/packages/cli-v3/src/commands/skills.ts @@ -278,19 +278,20 @@ export async function installSkillsFromInit(opts: SkillsWizardOptions = {}): Pro writeConfigHasSeenRulesInstallPrompt(true); writeConfigLastRulesInstallPromptVersion(manifest.currentVersion); - await installSkills(manifest, opts); - - return true; + // Returns true only if skills were actually written (false e.g. when the only target + // chosen is "unsupported"), so callers like `trigger init` don't claim skills are ready + // when nothing landed. + return await installSkills(manifest, opts); } -async function installSkills(manifest: RulesManifest, opts: SkillsWizardOptions) { +async function installSkills(manifest: RulesManifest, opts: SkillsWizardOptions): Promise { const currentVersion = await manifest.getCurrentVersion(); const targetNames = await resolveTargets(opts); if (targetNames.length === 1 && targetNames.includes("unsupported")) { handleUnsupportedTargetOnly(); - return; + return false; } const results = []; @@ -303,7 +304,9 @@ async function installSkills(manifest: RulesManifest, opts: SkillsWizardOptions) } } - if (results.some((r) => r.installations.length > 0 || r.pointer)) { + const installedAny = results.some((r) => r.installations.length > 0 || r.pointer); + + if (installedAny) { log.step("Installed the following skills:"); for (const r of results) { @@ -319,6 +322,8 @@ async function installSkills(manifest: RulesManifest, opts: SkillsWizardOptions) `${cliLink("Learn how to use Trigger.dev skills", "https://trigger.dev/docs/agents/rules/overview")}` ); } + + return installedAny; } function handleUnsupportedTargetOnly() {