Skip to content

Commit 8dd0cd5

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

19 files changed

Lines changed: 1784 additions & 3 deletions

File tree

docs/product/command-spec.md

Lines changed: 81 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,81 @@ 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+
## `prisma-cli 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+
- writes managed Prisma sections to `AGENTS.md` and `CLAUDE.md`
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 `--skip-skills` to only write managed project files
399+
- accepts `--skip-project-files` to only install skills
400+
- accepts `--dry-run` to report planned project-file changes and the skills CLI command without writing files or spawning the installer
401+
- is idempotent; existing managed sections are replaced and unrelated user-authored content is preserved
402+
403+
Examples:
404+
405+
```bash
406+
prisma-cli agent install
407+
prisma-cli agent install --agent codex
408+
prisma-cli agent install --agent codex --agent cursor
409+
prisma-cli agent install --all-agents
410+
prisma-cli agent install --skill prisma-compute
411+
```
412+
413+
## `prisma-cli agent update --agent <agent> --all-agents --skill <skill> --global --copy`
414+
415+
Purpose:
416+
417+
- refresh Prisma agent files and Prisma skills
418+
419+
Behavior:
420+
421+
- has the same flags and behavior as `agent install`
422+
- 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
423+
424+
Examples:
425+
426+
```bash
427+
prisma-cli agent update
428+
prisma-cli agent update --agent codex
429+
prisma-cli agent update --all-agents
430+
```
431+
432+
## `prisma-cli agent status`
433+
434+
Purpose:
435+
436+
- show whether Prisma agent context is installed in the current project
437+
438+
Behavior:
439+
440+
- reports whether `AGENTS.md` and `CLAUDE.md` contain Prisma-managed sections
441+
- reports whether `skills-lock.json` references `prisma/skills`
442+
- reports whether the project has dismissed the interactive agent setup prompt
443+
- does not access the network and does not require authentication
444+
445+
Examples:
446+
447+
```bash
448+
prisma-cli agent status
449+
prisma-cli agent status --json
450+
```
451+
376452
## `prisma-cli auth login`
377453

378454
Purpose:
@@ -386,6 +462,9 @@ Behavior:
386462
- resolves active workspace when required
387463
- confirms successful browser authentication and directs the user back to the terminal
388464
- returns the current auth state after login
465+
- in human output, when run from a project directory that does not already have Prisma agent context and has not dismissed the setup prompt, suggests `prisma-cli agent install`
466+
- does not install agent files during auth; auth is not project-scoped and must not mutate project guidance files
467+
- does not show the agent setup tip in `--json`, `--quiet`, CI, non-TTY output, or directories that do not look like projects
389468

390469
Examples:
391470

@@ -1066,6 +1145,8 @@ Behavior:
10661145
- maps user-facing framework names to deploy build strategies
10671146
- does not accept `--build-command` or `--output-directory`; custom build settings live in the `build` block of `prisma.compute.ts`
10681147
- deploys arbitrary framework output with `framework: "custom"` when the config provides a built output directory and entrypoint; `build.command` is optional for prebuilt artifacts
1148+
- in interactive human deploys, prompts once to install Prisma agent guidance for the project when it is missing; accepting runs `prisma-cli agent install` behavior from the project directory, declining records the prompt as dismissed in local CLI state
1149+
- does not prompt for agent setup in `--json`, `--quiet`, CI, `--no-interactive`, or `--yes`, and does not treat `--yes` as permission to mutate agent guidance files
10691150
- 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
10701151
- supports vanilla Bun apps with `--framework bun` using `package.json#main` or `package.json#module`, or with `--entry <path>`
10711152
- treats `--entry <path>` without `--framework` as a Bun app deploy

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