Skip to content

Commit 452762b

Browse files
feat(cli): add agent skills setup flow
1 parent 7833e62 commit 452762b

33 files changed

Lines changed: 2942 additions & 14 deletions

docs/product/command-spec.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This file is authoritative for command group scope during beta.
1010

1111
The beta package includes these command groups:
1212

13+
- `agent`
1314
- `auth`
1415
- `project` (includes `project env` subgroup)
1516
- `git`
@@ -373,6 +374,83 @@ prisma-cli --version --json
373374

374375
`prisma-cli version` is the richer environment report; `prisma-cli --version` is the terse one-liner. Both report the same `cli.version`. Use the flag for quick checks, the subcommand for support tickets and bug reports.
375376

377+
## `<runner> @prisma/cli@latest agent install --agent <agent> --all-agents --skill <skill> --global --copy`
378+
379+
Purpose:
380+
381+
- install Prisma context for AI coding agents
382+
383+
Behavior:
384+
385+
- user-facing agent install commands use the package runner detected from the project: `bunx`, `pnpm dlx`, `yarn dlx`, or `npx -y`
386+
- installs Prisma skills from `prisma/skills` by invoking `skills@latest` through the detected project package manager:
387+
`<runner> skills@latest add prisma/skills --skill "*" --agent codex --agent claude-code --yes`
388+
- detects the runner from the current directory and its ancestors:
389+
- `bun.lock`, `bun.lockb`, or `packageManager: "bun@..."` -> `bunx`
390+
- `pnpm-lock.yaml`, `pnpm-workspace.yaml`, or `packageManager: "pnpm@..."` -> `pnpm dlx`
391+
- `yarn.lock` or `packageManager: "yarn@..."` -> `yarn dlx`
392+
- `package-lock.json`, `npm-shrinkwrap.json`, `packageManager: "npm@..."`, or no package-manager signal -> `npx -y`
393+
- defaults to the `codex` and `claude-code` agent targets because those are the primary local agent workflows during beta
394+
- accepts repeated `--agent <agent>` flags to target specific skills CLI agents, such as `codex`, `claude-code`, or `cursor`
395+
- accepts `--all-agents` to pass `--agent "*"` to the skills CLI
396+
- accepts repeated `--skill <skill>` flags to install a subset such as `prisma-compute`
397+
- accepts `--global` and `--copy` and forwards them to the skills CLI
398+
- accepts `--dry-run` to report the skills CLI command without spawning the installer
399+
400+
Examples:
401+
402+
```bash
403+
npx -y @prisma/cli@latest agent install
404+
pnpm dlx @prisma/cli@latest agent install --agent codex
405+
pnpm dlx @prisma/cli@latest agent install --agent codex --agent cursor
406+
pnpm dlx @prisma/cli@latest agent install --all-agents
407+
pnpm dlx @prisma/cli@latest agent install --skill prisma-compute
408+
```
409+
410+
## `<runner> @prisma/cli@latest agent update --agent <agent> --all-agents --skill <skill> --global --copy`
411+
412+
Purpose:
413+
414+
- refresh Prisma skills
415+
416+
Behavior:
417+
418+
- has the same flags and behavior as `agent install`
419+
- reruns the detected package-manager runner with `skills@latest add prisma/skills ...` instead of `skills update`, so it refreshes only Prisma's skills and works for project-scoped installs
420+
421+
Examples:
422+
423+
```bash
424+
npx -y @prisma/cli@latest agent update
425+
pnpm dlx @prisma/cli@latest agent update --agent codex
426+
pnpm dlx @prisma/cli@latest agent update --all-agents
427+
```
428+
429+
## `<runner> @prisma/cli@latest agent status --global`
430+
431+
Purpose:
432+
433+
- show whether Prisma skills are installed for the current project or globally
434+
435+
Behavior:
436+
437+
- resolves the Compute config directory before checking project skills, so running from an app subdirectory still checks the project root
438+
- runs `skills@latest list --json` through the detected package runner and reports installed Prisma skills from the skills CLI by default
439+
- accepts `--global` to run `skills@latest list -g --json` and report globally installed Prisma skills instead
440+
- filters the list to Prisma skills and includes each skill's name, path, scope, and agent targets in JSON output
441+
- for project status, falls back to whether `skills-lock.json` references `prisma/skills` when the skills CLI list command fails
442+
- for global status, reports the Skills CLI list failure instead of falling back to project-local state
443+
- reports whether the project has dismissed the interactive agent setup prompt
444+
- does not require authentication
445+
446+
Examples:
447+
448+
```bash
449+
npx -y @prisma/cli@latest agent status
450+
pnpm dlx @prisma/cli@latest agent status --json
451+
pnpm dlx @prisma/cli@latest agent status --global
452+
```
453+
376454
## `prisma-cli auth login`
377455

378456
Purpose:
@@ -386,6 +464,9 @@ Behavior:
386464
- resolves active workspace when required
387465
- confirms successful browser authentication and directs the user back to the terminal
388466
- returns the current auth state after login
467+
- in human output, when run from a project directory that does not already have Prisma skills and has not dismissed the setup prompt, suggests the detected package-runner command, such as `pnpm dlx @prisma/cli@latest agent install`
468+
- does not install skills during auth; auth is not project-scoped and must not mutate the project
469+
- does not show the agent setup tip in `--json`, `--quiet`, CI, non-TTY output, or directories that do not look like projects
389470

390471
Examples:
391472

@@ -1066,6 +1147,8 @@ Behavior:
10661147
- maps user-facing framework names to deploy build strategies
10671148
- does not accept `--build-command` or `--output-directory`; custom build settings live in the `build` block of `prisma.compute.ts`
10681149
- deploys arbitrary framework output with `framework: "custom"` when the config provides a built output directory and entrypoint; `build.command` is optional for prebuilt artifacts
1150+
- in interactive human deploys, prompts once to install the Prisma Compute skill for the project when `prisma-compute` is missing; accepting installs `prisma-compute` from `prisma/skills` from the project directory, equivalent to the detected package-runner command such as `pnpm dlx @prisma/cli@latest agent install --skill prisma-compute`; declining records the prompt as dismissed in local CLI state
1151+
- does not prompt for agent setup in `--json`, `--quiet`, CI, `--no-interactive`, or `--yes`
10691152
- uses `src/index.ts` as the Hono deploy entrypoint when the app has no `package.json#main` or `package.json#module` and that file exists
10701153
- supports vanilla Bun apps with `--framework bun` using `package.json#main` or `package.json#module`, or with `--entry <path>`
10711154
- treats `--entry <path>` without `--framework` as a Bun app deploy

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"colorette": "^2.0.20",
5252
"commander": "^14.0.3",
5353
"dotenv": "^17.4.2",
54+
"execa": "^9.6.1",
5455
"magicast": "^0.5.3",
5556
"open": "^11.0.0",
5657
"string-width": "^8.2.1",

packages/cli/src/adapters/local-state.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export interface LocalState {
2222
selectedByProject: Record<string, SelectedAppState>;
2323
knownLiveDeploymentByProject: Record<string, Record<string, string>>;
2424
};
25+
agent: {
26+
setupPromptDismissedAt: string | null;
27+
};
2528
}
2629

2730
export interface SelectedAppState {
@@ -49,6 +52,9 @@ const DEFAULT_STATE: LocalState = {
4952
selectedByProject: {},
5053
knownLiveDeploymentByProject: {},
5154
},
55+
agent: {
56+
setupPromptDismissedAt: null,
57+
},
5258
};
5359

5460
export const DEFAULT_STATE_FILE_NAME = "state.json";
@@ -91,6 +97,9 @@ export class LocalStateStore {
9197
knownLiveDeploymentByProject:
9298
parsed.app?.knownLiveDeploymentByProject ?? {},
9399
},
100+
agent: {
101+
setupPromptDismissedAt: parsed.agent?.setupPromptDismissedAt ?? null,
102+
},
94103
};
95104
} catch (error) {
96105
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
@@ -253,4 +262,18 @@ export class LocalStateStore {
253262
await this.write(state);
254263
return state;
255264
}
265+
266+
async readAgentSetupPromptDismissedAt(): Promise<string | null> {
267+
const state = await this.read();
268+
return state.agent.setupPromptDismissedAt;
269+
}
270+
271+
async setAgentSetupPromptDismissedAt(
272+
dismissedAt: string,
273+
): Promise<LocalState> {
274+
const state = await this.read();
275+
state.agent.setupPromptDismissedAt = dismissedAt;
276+
await this.write(state);
277+
return state;
278+
}
256279
}

packages/cli/src/cli.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import process from "node:process";
22

33
import { Command, CommanderError, Option } from "commander";
44

5+
import { createAgentCommand } from "./commands/agent";
56
import { createAppCommand } from "./commands/app";
67
import { createAuthCommand } from "./commands/auth";
78
import { createBranchCommand } from "./commands/branch";
@@ -81,6 +82,7 @@ export function createProgram(runtime: CliRuntime): Command {
8182
program.name("prisma").showSuggestionAfterError();
8283

8384
program.addCommand(createVersionCommand(runtime));
85+
program.addCommand(createAgentCommand(runtime));
8486
program.addCommand(createAuthCommand(runtime));
8587
program.addCommand(createProjectCommand(runtime));
8688
program.addCommand(createGitCommand(runtime));
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { Command, Option } from "commander";
2+
3+
import {
4+
type AgentInstallOptions,
5+
type AgentStatusOptions,
6+
runAgentInstall,
7+
runAgentStatus,
8+
} from "../../controllers/agent";
9+
import {
10+
renderAgentInstall,
11+
renderAgentStatus,
12+
serializeAgentInstall,
13+
serializeAgentStatus,
14+
} from "../../presenters/agent";
15+
import { attachCommandDescriptor } from "../../shell/command-meta";
16+
import { runCommand } from "../../shell/command-runner";
17+
import {
18+
addCompactGlobalFlags,
19+
addGlobalFlags,
20+
} from "../../shell/global-flags";
21+
import { type CliRuntime, configureRuntimeCommand } from "../../shell/runtime";
22+
import type { AgentInstallResult, AgentStatusResult } from "../../types/agent";
23+
24+
export function createAgentCommand(runtime: CliRuntime): Command {
25+
const command = attachCommandDescriptor(
26+
configureRuntimeCommand(new Command("agent"), runtime),
27+
"agent",
28+
);
29+
30+
addCompactGlobalFlags(command);
31+
32+
command.addCommand(createAgentInstallCommand(runtime));
33+
command.addCommand(createAgentUpdateCommand(runtime));
34+
command.addCommand(createAgentStatusCommand(runtime));
35+
36+
return command;
37+
}
38+
39+
function createAgentInstallCommand(runtime: CliRuntime): Command {
40+
const command = attachCommandDescriptor(
41+
configureRuntimeCommand(new Command("install"), runtime),
42+
"agent.install",
43+
);
44+
45+
addAgentInstallOptions(command);
46+
47+
addGlobalFlags(command);
48+
49+
command.action(async (options) => {
50+
await runCommand<AgentInstallResult>(
51+
runtime,
52+
"agent.install",
53+
options as Record<string, unknown>,
54+
(context) =>
55+
runAgentInstall(context, options as AgentInstallOptions, "install"),
56+
{
57+
renderHuman: (context, descriptor, result) =>
58+
renderAgentInstall(context, descriptor, result),
59+
renderJson: (result) => serializeAgentInstall(result),
60+
},
61+
);
62+
});
63+
64+
return command;
65+
}
66+
67+
function createAgentUpdateCommand(runtime: CliRuntime): Command {
68+
const command = attachCommandDescriptor(
69+
configureRuntimeCommand(new Command("update"), runtime),
70+
"agent.update",
71+
);
72+
73+
addAgentInstallOptions(command);
74+
addGlobalFlags(command);
75+
76+
command.action(async (options) => {
77+
await runCommand<AgentInstallResult>(
78+
runtime,
79+
"agent.update",
80+
options as Record<string, unknown>,
81+
(context) =>
82+
runAgentInstall(context, options as AgentInstallOptions, "update"),
83+
{
84+
renderHuman: (context, descriptor, result) =>
85+
renderAgentInstall(context, descriptor, result),
86+
renderJson: (result) => serializeAgentInstall(result),
87+
},
88+
);
89+
});
90+
91+
return command;
92+
}
93+
94+
function createAgentStatusCommand(runtime: CliRuntime): Command {
95+
const command = attachCommandDescriptor(
96+
configureRuntimeCommand(new Command("status"), runtime),
97+
"agent.status",
98+
);
99+
100+
command.addOption(
101+
new Option(
102+
"--global",
103+
"Check globally installed Prisma skills instead of project skills",
104+
),
105+
);
106+
addGlobalFlags(command);
107+
108+
command.action(async (options) => {
109+
await runCommand<AgentStatusResult>(
110+
runtime,
111+
"agent.status",
112+
options as Record<string, unknown>,
113+
(context) => runAgentStatus(context, options as AgentStatusOptions),
114+
{
115+
renderHuman: (context, descriptor, result) =>
116+
renderAgentStatus(context, descriptor, result),
117+
renderJson: (result) => serializeAgentStatus(result),
118+
},
119+
);
120+
});
121+
122+
return command;
123+
}
124+
125+
function addAgentInstallOptions(command: Command): void {
126+
command
127+
.addOption(
128+
new Option(
129+
"--agent <agent>",
130+
"Agent target for Prisma skills; repeat for multiple agents",
131+
).argParser(collectValues),
132+
)
133+
.addOption(
134+
new Option(
135+
"--all-agents",
136+
"Install Prisma skills for every agent supported by the skills CLI",
137+
),
138+
)
139+
.addOption(
140+
new Option(
141+
"--skill <skill>",
142+
"Prisma skill to install; repeat for multiple skills",
143+
).argParser(collectValues),
144+
)
145+
.addOption(
146+
new Option(
147+
"--global",
148+
"Install skills into the user directory instead of the project",
149+
),
150+
)
151+
.addOption(
152+
new Option(
153+
"--copy",
154+
"Ask the skills CLI to copy files instead of symlinking them",
155+
),
156+
)
157+
.addOption(new Option("--dry-run", "Show changes without writing files"));
158+
}
159+
160+
function collectValues(value: string, previous: string[] | undefined) {
161+
return [...(previous ?? []), value];
162+
}

0 commit comments

Comments
 (0)