Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/product/command-principles.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Use the other convention docs for adjacent concerns:
The long-term command surface grows through workflow groups such as:

- `init`
- `agent`
- `auth`
- `project`
- `branch`
Expand All @@ -38,7 +39,7 @@ The long-term command surface grows through workflow groups such as:
- `app`
- `git`

The preview implements only `auth`, `project`, `git`, `branch`, `database`, and `app`.
The preview implements only `agent`, `auth`, `project`, `git`, `branch`, `database`, and `app`.

## Stable Nouns

Expand Down
83 changes: 83 additions & 0 deletions docs/product/command-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This file is authoritative for command group scope during beta.

The beta package includes these command groups:

- `agent`
- `auth`
- `project` (includes `project env` subgroup)
- `git`
Expand Down Expand Up @@ -376,6 +377,83 @@ prisma-cli --version --json

`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.

## `<runner> @prisma/cli@latest agent install --agent <agent> --all-agents --skill <skill> --global --copy`

Purpose:

- install Prisma context for AI coding agents

Behavior:

- user-facing agent install commands use the package runner detected from the project: `bunx`, `pnpm dlx`, `yarn dlx`, or `npx -y`
- installs Prisma skills from `prisma/skills` by invoking `skills@latest` through the detected project package manager:
`<runner> skills@latest add prisma/skills --skill "*" --agent codex --agent claude-code --yes`
- detects the runner from the current directory and its ancestors:
- `bun.lock`, `bun.lockb`, or `packageManager: "bun@..."` -> `bunx`
- `pnpm-lock.yaml`, `pnpm-workspace.yaml`, or `packageManager: "pnpm@..."` -> `pnpm dlx`
- `yarn.lock` or `packageManager: "yarn@..."` -> `yarn dlx`
- `package-lock.json`, `npm-shrinkwrap.json`, `packageManager: "npm@..."`, or no package-manager signal -> `npx -y`
- defaults to the `codex` and `claude-code` agent targets because those are the primary local agent workflows during beta
- accepts repeated `--agent <agent>` flags to target specific skills CLI agents, such as `codex`, `claude-code`, or `cursor`
- accepts `--all-agents` to pass `--agent "*"` to the skills CLI
- accepts repeated `--skill <skill>` flags to install a subset such as `prisma-compute`
- accepts `--global` and `--copy` and forwards them to the skills CLI
- accepts `--dry-run` to report the skills CLI command without spawning the installer

Examples:

```bash
npx -y @prisma/cli@latest agent install
pnpm dlx @prisma/cli@latest agent install --agent codex
pnpm dlx @prisma/cli@latest agent install --agent codex --agent cursor
pnpm dlx @prisma/cli@latest agent install --all-agents
pnpm dlx @prisma/cli@latest agent install --skill prisma-compute
```

## `<runner> @prisma/cli@latest agent update --agent <agent> --all-agents --skill <skill> --global --copy`

Purpose:

- refresh Prisma skills

Behavior:

- has the same flags and behavior as `agent install`
- 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

Examples:

```bash
npx -y @prisma/cli@latest agent update
pnpm dlx @prisma/cli@latest agent update --agent codex
pnpm dlx @prisma/cli@latest agent update --all-agents
```

## `<runner> @prisma/cli@latest agent status --global`

Purpose:

- show whether Prisma skills are installed for the current project or globally

Behavior:

- resolves the Compute config directory before checking project skills, so running from an app subdirectory still checks the project root
- runs `skills@latest list --json` through the detected package runner and reports installed Prisma skills from the skills CLI by default
- accepts `--global` to run `skills@latest list -g --json` and report globally installed Prisma skills instead
- filters the list to Prisma skills and includes each skill's name, path, scope, and agent targets in JSON output
- for project status, falls back to whether `skills-lock.json` references `prisma/skills` when the skills CLI list command fails
- for global status, reports the Skills CLI list failure instead of falling back to project-local state
- reports whether the project has dismissed the interactive agent setup prompt
- does not require authentication

Examples:

```bash
npx -y @prisma/cli@latest agent status
pnpm dlx @prisma/cli@latest agent status --json
pnpm dlx @prisma/cli@latest agent status --global
```

## `prisma-cli auth login`

Purpose:
Expand All @@ -389,6 +467,9 @@ Behavior:
- resolves active workspace when required
- confirms successful browser authentication and directs the user back to the terminal
- returns the current auth state after login
- 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`
- does not install skills during auth; auth is not project-scoped and must not mutate the project
- does not show the agent setup tip in `--json`, `--quiet`, CI, non-TTY output, or directories that do not look like projects

Examples:

Expand Down Expand Up @@ -1072,6 +1153,8 @@ Behavior:
- maps user-facing framework names to deploy build strategies
- does not accept `--build-command` or `--output-directory`; custom build settings live in the `build` block of `prisma.compute.ts`
- deploys arbitrary framework output with `framework: "custom"` when the config provides a built output directory and entrypoint; `build.command` is optional for prebuilt artifacts
- 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
- does not prompt for agent setup in `--json`, `--quiet`, CI, `--no-interactive`, or `--yes`
- 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
- supports vanilla Bun apps with `--framework bun` using `package.json#main` or `package.json#module`, or with `--entry <path>`
- treats `--entry <path>` without `--framework` as a Bun app deploy
Expand Down
4 changes: 3 additions & 1 deletion docs/product/error-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ Rules:
- `ok` is always `false`
- `command` is always present
- `error.code` is stable and machine-readable
- `error.domain` is a stable logical area such as `cli`, `auth`, `project`, `branch`, `app`, or `database`
- `error.domain` is a stable logical area such as `cli`, `agent`, `auth`, `project`, `branch`, `app`, or `database`
- `error.severity` is stable and machine-readable
- `error.summary` is the short human-readable headline
- `error.why` explains the immediate cause when known
Expand All @@ -161,6 +161,7 @@ These codes are the minimum stable set for the MVP:
- `USAGE_ERROR`
- `AUTH_REQUIRED`
- `AUTH_CONFIG_INVALID`
- `AGENT_SKILLS_INSTALL_FAILED`
- `WORKSPACE_SWITCH_UNAVAILABLE`
- `WORKSPACE_NOT_AUTHENTICATED`
- `WORKSPACE_AMBIGUOUS`
Expand Down Expand Up @@ -222,6 +223,7 @@ Recommended meanings:
- `USAGE_ERROR`: invalid arguments or invalid command combination
- `AUTH_REQUIRED`: command needs an authenticated session
- `AUTH_CONFIG_INVALID`: environment auth configuration is present but unusable, such as an empty `PRISMA_SERVICE_TOKEN`
- `AGENT_SKILLS_INSTALL_FAILED`: installing Prisma skills through the external skills CLI failed; callers should inspect the command, exit code, and stderr in `error.meta`
- `WORKSPACE_SWITCH_UNAVAILABLE`: `PRISMA_SERVICE_TOKEN` is the active auth source, so local OAuth workspace switching cannot apply
- `WORKSPACE_NOT_AUTHENTICATED`: requested workspace is not present in the local OAuth credentials store for a switch/logout operation; callers should run `auth login` for that workspace
- `WORKSPACE_AMBIGUOUS`: requested workspace name matches more than one local OAuth workspace; callers should switch by workspace id
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"colorette": "^2.0.20",
"commander": "^14.0.3",
"dotenv": "^17.4.2",
"execa": "^9.6.1",
"magicast": "^0.5.3",
"open": "^11.0.0",
"string-width": "^8.2.1",
Expand Down
23 changes: 23 additions & 0 deletions packages/cli/src/adapters/local-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export interface LocalState {
selectedByProject: Record<string, SelectedAppState>;
knownLiveDeploymentByProject: Record<string, Record<string, string>>;
};
agent: {
setupPromptDismissedAt: string | null;
};
}

export interface SelectedAppState {
Expand Down Expand Up @@ -49,6 +52,9 @@ const DEFAULT_STATE: LocalState = {
selectedByProject: {},
knownLiveDeploymentByProject: {},
},
agent: {
setupPromptDismissedAt: null,
},
};

export const DEFAULT_STATE_FILE_NAME = "state.json";
Expand Down Expand Up @@ -91,6 +97,9 @@ export class LocalStateStore {
knownLiveDeploymentByProject:
parsed.app?.knownLiveDeploymentByProject ?? {},
},
agent: {
setupPromptDismissedAt: parsed.agent?.setupPromptDismissedAt ?? null,
},
};
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
Expand Down Expand Up @@ -253,4 +262,18 @@ export class LocalStateStore {
await this.write(state);
return state;
}

async readAgentSetupPromptDismissedAt(): Promise<string | null> {
const state = await this.read();
return state.agent.setupPromptDismissedAt;
}

async setAgentSetupPromptDismissedAt(
dismissedAt: string,
): Promise<LocalState> {
const state = await this.read();
state.agent.setupPromptDismissedAt = dismissedAt;
await this.write(state);
return state;
}
}
2 changes: 2 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import process from "node:process";

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

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

program.addCommand(createVersionCommand(runtime));
program.addCommand(createAgentCommand(runtime));
program.addCommand(createAuthCommand(runtime));
program.addCommand(createProjectCommand(runtime));
program.addCommand(createGitCommand(runtime));
Expand Down
162 changes: 162 additions & 0 deletions packages/cli/src/commands/agent/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { Command, Option } from "commander";

import {
type AgentInstallOptions,
type AgentStatusOptions,
runAgentInstall,
runAgentStatus,
} from "../../controllers/agent";
import {
renderAgentInstall,
renderAgentStatus,
serializeAgentInstall,
serializeAgentStatus,
} from "../../presenters/agent";
import { attachCommandDescriptor } from "../../shell/command-meta";
import { runCommand } from "../../shell/command-runner";
import {
addCompactGlobalFlags,
addGlobalFlags,
} from "../../shell/global-flags";
import { type CliRuntime, configureRuntimeCommand } from "../../shell/runtime";
import type { AgentInstallResult, AgentStatusResult } from "../../types/agent";

export function createAgentCommand(runtime: CliRuntime): Command {
const command = attachCommandDescriptor(
configureRuntimeCommand(new Command("agent"), runtime),
"agent",
);

addCompactGlobalFlags(command);

command.addCommand(createAgentInstallCommand(runtime));
command.addCommand(createAgentUpdateCommand(runtime));
command.addCommand(createAgentStatusCommand(runtime));

return command;
}

function createAgentInstallCommand(runtime: CliRuntime): Command {
const command = attachCommandDescriptor(
configureRuntimeCommand(new Command("install"), runtime),
"agent.install",
);

addAgentInstallOptions(command);

addGlobalFlags(command);

command.action(async (options) => {
await runCommand<AgentInstallResult>(
runtime,
"agent.install",
options as Record<string, unknown>,
(context) =>
runAgentInstall(context, options as AgentInstallOptions, "install"),
{
renderHuman: (context, descriptor, result) =>
renderAgentInstall(context, descriptor, result),
renderJson: (result) => serializeAgentInstall(result),
},
);
});

return command;
}

function createAgentUpdateCommand(runtime: CliRuntime): Command {
const command = attachCommandDescriptor(
configureRuntimeCommand(new Command("update"), runtime),
"agent.update",
);

addAgentInstallOptions(command);
addGlobalFlags(command);

command.action(async (options) => {
await runCommand<AgentInstallResult>(
runtime,
"agent.update",
options as Record<string, unknown>,
(context) =>
runAgentInstall(context, options as AgentInstallOptions, "update"),
{
renderHuman: (context, descriptor, result) =>
renderAgentInstall(context, descriptor, result),
renderJson: (result) => serializeAgentInstall(result),
},
);
});

return command;
}

function createAgentStatusCommand(runtime: CliRuntime): Command {
const command = attachCommandDescriptor(
configureRuntimeCommand(new Command("status"), runtime),
"agent.status",
);

command.addOption(
new Option(
"--global",
"Check globally installed Prisma skills instead of project skills",
),
);
addGlobalFlags(command);

command.action(async (options) => {
await runCommand<AgentStatusResult>(
runtime,
"agent.status",
options as Record<string, unknown>,
(context) => runAgentStatus(context, options as AgentStatusOptions),
{
renderHuman: (context, descriptor, result) =>
renderAgentStatus(context, descriptor, result),
renderJson: (result) => serializeAgentStatus(result),
},
);
});

return command;
}

function addAgentInstallOptions(command: Command): void {
command
.addOption(
new Option(
"--agent <agent>",
"Agent target for Prisma skills; repeat for multiple agents",
).argParser(collectValues),
)
.addOption(
new Option(
"--all-agents",
"Install Prisma skills for every agent supported by the skills CLI",
),
)
.addOption(
new Option(
"--skill <skill>",
"Prisma skill to install; repeat for multiple skills",
).argParser(collectValues),
)
.addOption(
new Option(
"--global",
"Install skills into the user directory instead of the project",
),
)
.addOption(
new Option(
"--copy",
"Ask the skills CLI to copy files instead of symlinking them",
),
)
.addOption(new Option("--dry-run", "Show changes without writing files"));
}

function collectValues(value: string, previous: string[] | undefined) {
return [...(previous ?? []), value];
}
Loading
Loading