Skip to content

Commit 5af072d

Browse files
Merge pull request #1280 from planetscale/cli/agent-onboarding
Make pscale agent-friendly: JSON auth, sql command, AGENTS.md
2 parents c018f92 + ad2015e commit 5af072d

30 files changed

Lines changed: 2493 additions & 212 deletions

AGENTS.md

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# PlanetScale CLI — agent guide
2+
3+
For **any** automated agent or script using `pscale`. Always pass **`--format json`**. Substitute placeholders from the user's request or from prior command output (`org list`, `database list`, `branch list`).
4+
5+
If you only have the installed `pscale` binary, start here:
6+
7+
```bash
8+
pscale agent-guide --format json
9+
pscale auth check --format json
10+
```
11+
12+
Use direct CLI automation for shell commands and scripts. Use the hosted PlanetScale MCP server for MCP clients.
13+
14+
This file documents **how to invoke `pscale`**. For database assessment, safety review, and operational workflows, install the [PlanetScale skills pack](https://github.com/planetscale/skills) (`14-pscale-cli-automation` covers CLI automation; `00-safe-orchestrator` runs the full review). In application repositories, add a separate **project** `AGENTS.md` with org, database, branch, and approval rules (see skill `09-mcp-agent-operating-model` in that repo).
15+
16+
| Placeholder | Meaning |
17+
|-------------|---------|
18+
| `<org>` | Organization name |
19+
| `<database>` | Database name |
20+
| `<branch>` | Branch name (pick one with `"ready": true` from `branch list`) |
21+
22+
## Flag placement
23+
24+
- **`--org`** is a flag on resource subcommands (`database`, `branch`, `sql`, `api`, …). It is **not** on root `pscale``pscale --org …` fails.
25+
- **`--format json`** is a global flag. It can go on `pscale` or on the subcommand.
26+
- Commands with positional args (`sql`, `branch list`, …): put **positionals first**, then flags.
27+
28+
```bash
29+
# Correct
30+
pscale auth check --format json
31+
pscale org list --format json
32+
pscale database list --org <org> --format json
33+
pscale branch list <database> --org <org> --format json
34+
pscale sql <database> <branch> --org <org> --format json --query "SELECT 1"
35+
36+
# Also valid — global --format
37+
pscale --format json database list --org <org>
38+
39+
# Wrong — unknown flag: --org
40+
pscale --org <org> database list --format json
41+
```
42+
43+
## Workflow
44+
45+
1. **Guide** — discover machine-readable conventions when you do not have this file:
46+
47+
```bash
48+
pscale agent-guide --format json
49+
```
50+
51+
2. **Auth** — check before anything else:
52+
53+
```bash
54+
pscale auth check --format json
55+
```
56+
57+
`"status": "ok"` and `"authenticated": true` with no blocking `issues` means proceed. `"status": "action_required"` exits non-zero — log in, pick an org, or fix credentials (see `issues` and `next_steps`).
58+
59+
3. **Login** (when not authenticated):
60+
61+
```bash
62+
pscale auth login --format json
63+
```
64+
65+
Pending JSON is written to **stderr** while waiting; **stdout** has a single final JSON object when login completes (`status: ok` or `action_required` if org setup fails after credentials are saved). Fields include `verification_url`, `user_code`, and `browser_opened`. Open `verification_url` manually if the browser does not open. Do not retry login in a loop without browser access.
66+
67+
4. **Organization** — use `"organization"` from `auth check`, ask the user, or list orgs:
68+
69+
```bash
70+
pscale org list --format json
71+
```
72+
73+
Pass `--org <org>` on resource commands (`database`, `branch`, `sql`, `api`, …). Not on `org list`.
74+
75+
5. **Discover resources** before SQL:
76+
77+
```bash
78+
pscale database list --org <org> --format json
79+
pscale branch list <database> --org <org> --format json
80+
```
81+
82+
6. **Query** (read-only default):
83+
84+
```bash
85+
pscale sql <database> <branch> --org <org> --format json --query "SELECT 1"
86+
```
87+
88+
## Flags
89+
90+
| Flag | Purpose |
91+
|------|---------|
92+
| `--format json` | JSON on stdout |
93+
| `--org <org>` | Organization (on resource subcommands only) |
94+
| `--api-url` | Non-production API base URL — pass on every command when not using production |
95+
96+
## Authentication
97+
98+
`pscale auth login` stores credentials in the OS keychain; agents on the same machine reuse them.
99+
100+
Headless / CI: pass `--service-token-id` and `--service-token` on the subcommand that needs auth.
101+
102+
## SQL
103+
104+
Non-interactive queries. Default **`--role` is `reader`** (unlike `pscale shell`, which defaults to admin). Use `pscale shell` for interactive sessions.
105+
106+
```bash
107+
# Read (default)
108+
pscale sql <database> <branch> --org <org> --format json --query "SELECT 1"
109+
110+
# Read from replica
111+
pscale sql <database> <branch> --org <org> --format json --replica --query "SELECT 1"
112+
113+
# PostgreSQL — optional --dbname (default postgres)
114+
pscale sql <database> <branch> --org <org> --format json --query "SELECT 1"
115+
116+
# MySQL multi-keyspace — optional --keyspace (default @primary)
117+
pscale sql <database> <branch> --org <org> --format json --keyspace <keyspace> --query "SELECT 1"
118+
```
119+
120+
| Flag | Purpose |
121+
|------|---------|
122+
| `--role` | `reader` (default), `writer`, `readwriter`, `admin` — same names as `pscale shell` |
123+
| `--replica` | Route reads to replicas |
124+
| `--dbname` | PostgreSQL database name (default `postgres`) |
125+
| `--keyspace` | MySQL keyspace (default `@primary`) |
126+
| `--force` | Allow destructive SQL after explicit user approval |
127+
128+
**`--role` by engine** (same as `pscale shell`):
129+
130+
| `--role` | MySQL (Vitess) | PostgreSQL |
131+
|----------|----------------|------------|
132+
| `reader` | Branch password, reader role | Ephemeral role inheriting `pg_read_all_data` |
133+
| `writer` | Branch password, writer role | Role inheriting `pg_write_all_data` |
134+
| `readwriter` | Branch password, readwriter role | Role inheriting read + write |
135+
| `admin` | Branch password, admin role | Role inheriting `postgres` |
136+
137+
### Destructive SQL
138+
139+
`DELETE`, `DROP`, and `TRUNCATE` anywhere in a query are blocked by default (word match, not substring — `deleted_at` is fine). Returns `"status": "action_required"` with `"query_kind": "destructive"`.
140+
141+
1. Ask the user to approve the query.
142+
2. Re-run with `--force` only after they approve:
143+
144+
```bash
145+
pscale sql <database> <branch> --org <org> --format json --force --query "DELETE FROM ..."
146+
```
147+
148+
Never use `--force` without explicit user approval.
149+
150+
### SQL JSON
151+
152+
Success: `status`, `database`, `branch`, `kind` (`mysql` or `postgresql`), `role`, `row_count`, `columns`, `rows`; `replica` when `--replica` was used.
153+
154+
MySQL may return synthetic column names (e.g. `:vtg1 /* INT64 */`). PostgreSQL may use names like `?column?`.
155+
156+
Error: one JSON object on stdout with `status: "error"`, `error`, and `next_steps`.
157+
158+
Destructive SQL without `--force`: `status: "action_required"`, `query_kind: "destructive"`, `issues`, and `next_steps` (includes `--force` retry command).
159+
160+
## API passthrough
161+
162+
```bash
163+
pscale api --org <org> organizations/<org>/databases
164+
```
165+
166+
## MCP
167+
168+
For MCP clients, use the hosted PlanetScale MCP server:
169+
170+
```text
171+
https://mcp.pscale.dev/mcp/planetscale
172+
```
173+
174+
See the current MCP docs: https://planetscale.com/docs/connect/mcp
175+
176+
Do not use the deprecated local `pscale mcp server` path unless you explicitly need backward compatibility with an old setup.
177+
178+
## PlanetScale agent skills
179+
180+
Operational workflows (inventory, safety review, Insights, schema recommendations, Traffic Control) live in the public skills repo — not in this file.
181+
182+
```bash
183+
git clone https://github.com/planetscale/skills.git && cd skills && script/setup
184+
# or: npx skills add planetscale/skills -g -y
185+
```
186+
187+
After installing skills, load `14-pscale-cli-automation` for CLI conventions (or re-run `pscale agent-guide --format json` from any `pscale` binary that includes agent onboarding). Use `00-safe-orchestrator` when the user asks for a full PlanetScale assessment.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,7 @@ And then use the `pscale` binary built in `cmd/pscale/` for testing:
124124
## Documentation
125125

126126
Please checkout our Documentation page: [planetscale.com/docs](https://planetscale.com/docs/reference/planetscale-cli)
127+
128+
For AI agents and automation, run `pscale agent-guide` or see [AGENTS.md](./AGENTS.md).
129+
For operational workflows (inventory, safety review, schema recommendations), install
130+
[planetscale/skills](https://github.com/planetscale/skills).

agentguide_content.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package cli
2+
3+
import _ "embed"
4+
5+
// AgentGuide is the embedded agent guide shipped with the pscale binary.
6+
//
7+
//go:embed AGENTS.md
8+
var AgentGuide string
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package agentguide
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
6+
clicontent "github.com/planetscale/cli"
7+
"github.com/planetscale/cli/internal/cmdutil"
8+
"github.com/planetscale/cli/internal/printer"
9+
)
10+
11+
const (
12+
HostedMCPURL = "https://mcp.pscale.dev/mcp/planetscale"
13+
MCPDocsURL = "https://planetscale.com/docs/connect/mcp"
14+
SkillsRepoURL = "https://github.com/planetscale/skills"
15+
SkillsSetupCmd = "git clone https://github.com/planetscale/skills.git && cd skills && script/setup"
16+
SkillsNPXInstall = "npx skills add planetscale/skills -g -y"
17+
SkillsCLIAutomation = "14-pscale-cli-automation"
18+
)
19+
20+
type response struct {
21+
Status string `json:"status"`
22+
Guide string `json:"guide"`
23+
FirstCommand string `json:"first_command"`
24+
AgentGuideCommand string `json:"agent_guide_command"`
25+
HostedMCPURL string `json:"hosted_mcp_url"`
26+
MCPDocsURL string `json:"mcp_docs_url"`
27+
SkillsRepoURL string `json:"skills_repo_url"`
28+
SkillsSetupCommand string `json:"skills_setup_command"`
29+
SkillsNPXCommand string `json:"skills_npx_command"`
30+
SkillsCLIAutomation string `json:"skills_cli_automation_skill"`
31+
SupportedEngines []string `json:"supported_engines"`
32+
Conventions []string `json:"conventions"`
33+
NextSteps []string `json:"next_steps"`
34+
}
35+
36+
// AgentGuideCmd prints the embedded guide for agents and automation.
37+
func AgentGuideCmd(ch *cmdutil.Helper) *cobra.Command {
38+
cmd := &cobra.Command{
39+
Use: "agent-guide",
40+
Short: "Show guidance for AI agents and automation",
41+
Long: `Show guidance for AI agents and automation using pscale.
42+
43+
Use --format json for a machine-readable bootstrap response with first commands,
44+
supported engines, hosted MCP details, and PlanetScale skills install hints.`,
45+
Args: cobra.NoArgs,
46+
RunE: func(cmd *cobra.Command, args []string) error {
47+
if ch.Printer.Format() == printer.JSON {
48+
return ch.Printer.PrintJSON(response{
49+
Status: "ok",
50+
Guide: clicontent.AgentGuide,
51+
FirstCommand: cmdutil.AgentAuthCheckCmd(),
52+
AgentGuideCommand: cmdutil.AgentGuideCmd(),
53+
HostedMCPURL: HostedMCPURL,
54+
MCPDocsURL: MCPDocsURL,
55+
SkillsRepoURL: SkillsRepoURL,
56+
SkillsSetupCommand: SkillsSetupCmd,
57+
SkillsNPXCommand: SkillsNPXInstall,
58+
SkillsCLIAutomation: SkillsCLIAutomation,
59+
SupportedEngines: []string{"mysql", "postgresql"},
60+
Conventions: []string{
61+
"Always pass --format json for automation",
62+
"Put --org on resource subcommands, not on pscale root",
63+
"Put positional arguments before flags for commands like sql and branch list",
64+
"Use hosted MCP for MCP clients; direct CLI automation should start with auth check",
65+
"Install planetscale/skills for operational workflows; this guide covers pscale invocation only",
66+
"Project AGENTS.md is for org/database/branch targeting — separate from the CLI agent guide",
67+
},
68+
NextSteps: []string{
69+
cmdutil.AgentAuthCheckCmd(),
70+
SkillsSetupCmd,
71+
},
72+
})
73+
}
74+
75+
ch.Printer.Print(clicontent.AgentGuide)
76+
return nil
77+
},
78+
}
79+
80+
return cmd
81+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package agentguide
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"testing"
7+
8+
"github.com/planetscale/cli/internal/cmdutil"
9+
"github.com/planetscale/cli/internal/printer"
10+
)
11+
12+
func TestAgentGuideJSONBootstrap(t *testing.T) {
13+
format := printer.JSON
14+
var out bytes.Buffer
15+
ch := &cmdutil.Helper{
16+
Printer: printer.NewPrinter(&format),
17+
}
18+
ch.Printer.SetResourceOutput(&out)
19+
20+
cmd := AgentGuideCmd(ch)
21+
if err := cmd.Execute(); err != nil {
22+
t.Fatalf("execute: %v", err)
23+
}
24+
25+
var resp response
26+
if err := json.Unmarshal(out.Bytes(), &resp); err != nil {
27+
t.Fatalf("json: %v", err)
28+
}
29+
if resp.Status != "ok" {
30+
t.Fatalf("status = %q", resp.Status)
31+
}
32+
if resp.FirstCommand != cmdutil.AgentAuthCheckCmd() {
33+
t.Fatalf("first command = %q", resp.FirstCommand)
34+
}
35+
if resp.HostedMCPURL != HostedMCPURL {
36+
t.Fatalf("hosted MCP URL = %q", resp.HostedMCPURL)
37+
}
38+
if resp.SkillsRepoURL != SkillsRepoURL {
39+
t.Fatalf("skills repo URL = %q", resp.SkillsRepoURL)
40+
}
41+
if resp.SkillsSetupCommand != SkillsSetupCmd {
42+
t.Fatalf("skills setup command = %q", resp.SkillsSetupCommand)
43+
}
44+
if resp.SkillsCLIAutomation != SkillsCLIAutomation {
45+
t.Fatalf("skills CLI automation skill = %q", resp.SkillsCLIAutomation)
46+
}
47+
if len(resp.NextSteps) < 2 || resp.NextSteps[1] != SkillsSetupCmd {
48+
t.Fatalf("next_steps = %#v", resp.NextSteps)
49+
}
50+
if resp.Guide == "" {
51+
t.Fatal("expected embedded guide")
52+
}
53+
}

0 commit comments

Comments
 (0)