Skip to content

Commit 6dc2cc3

Browse files
crowlbotclaude
andcommitted
docs: add AGENTS.md for non-interactive CLI usage
Reference for AI agents and CI systems driving `deno deploy` without a human at the terminal. Documents: - Token authentication (env var, --token, keychain) and the "never opens a browser" guarantee when a token is supplied explicitly. - The global flag set: --json, --non-interactive (with -y/--yes aliases), --quiet, --debug, --endpoint, --token, --config. - The exit-code taxonomy (OK=0, GENERIC=1, USAGE=2, AUTH=3, NOT_FOUND=4, CONFLICT=5, NETWORK=6) and how to consume it. - The structured error envelope on stderr. - JSON output schemas for every command that already emits JSON: whoami, orgs list, apps list, deployments list, env list, database list/query, publish, create (--dry-run and the github source), and the NDJSON shape for logs. - Non-interactive flag coverage matrix per subcommand. - Stdio discipline rules. - Five copy-pasteable example flows: auth check, create + publish, re-deploy to existing app, env load, pagination, AWS setup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent fa27fdd commit 6dc2cc3

1 file changed

Lines changed: 387 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 387 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
1+
# AGENTS.md
2+
3+
This document is for **AI agents and CI systems** driving `deno deploy`
4+
non-interactively. It complements the per-command `--help` output by documenting
5+
the global conventions that hold across every subcommand.
6+
7+
If you are a human running this CLI from a terminal, you can ignore this file —
8+
interactive mode is the default and behaves as it always has.
9+
10+
## Token authentication
11+
12+
The CLI reads a Deno Deploy access token from, in priority order:
13+
14+
1. The `--token <token>` flag.
15+
2. The `DENO_DEPLOY_TOKEN` environment variable.
16+
3. The OS keychain entry written by an earlier interactive `deno deploy`
17+
session.
18+
19+
When the token comes from `--token` or `DENO_DEPLOY_TOKEN`, the CLI **never**
20+
opens a browser. An invalid or expired token surfaces as:
21+
22+
```json
23+
{
24+
"error": {
25+
"code": "AUTH_INVALID_TOKEN",
26+
"message": "...",
27+
"hint": "Generate a new token at https://console.deno.com/account/tokens and re-export DENO_DEPLOY_TOKEN."
28+
}
29+
}
30+
```
31+
32+
with exit code `3`. Use `deno deploy whoami --json` to verify a token without
33+
side effects (see Examples below).
34+
35+
## Global flags
36+
37+
Every subcommand of `deno deploy` and `deno deploy sandbox` honors:
38+
39+
| Flag | Effect |
40+
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
41+
| `--json` | Emit a single JSON object (or NDJSON for streaming commands) on **stdout**. Suppress ANSI color, spinners, and progress bars. Errors become a structured envelope on **stderr**. |
42+
| `-y`, `--non-interactive`, `--yes` | Refuse to prompt. Any missing input that would normally prompt fails fast with a clear error naming the flag to pass instead. |
43+
| `-q`, `--quiet` | Suppress non-essential human-progress output. Final result still printed. |
44+
| `--debug` | Print stack traces and verbose diagnostics on stderr. Off by default. |
45+
| `--endpoint <url>` | Override the API endpoint. Also reads `DENO_DEPLOY_ENDPOINT`. |
46+
| `--token <token>` | Override the token from env/keychain. |
47+
| `--config <path>` | Path to a config file (defaults to `deno.json`/`deno.jsonc` in the working directory). |
48+
49+
`--non-interactive` and `--json` are independent: an agent can run with `--json`
50+
alone and still get prompted on a TTY (rare), or `--non-interactive` alone and
51+
still get human-readable text. The recommended agent invocation is
52+
`--json --non-interactive` together.
53+
54+
## Exit codes
55+
56+
The CLI returns one of a small, stable set of exit codes. Agents should
57+
pattern-match on the exit code first, then parse stderr if non-zero.
58+
59+
| Code | Name | Meaning |
60+
| ---- | --------- | ------------------------------------------------------------------------------------- |
61+
| `0` | OK | Success. |
62+
| `1` | GENERIC | Unclassified failure. |
63+
| `2` | USAGE | Bad flag, missing required value, or `--non-interactive` short-circuit. |
64+
| `3` | AUTH | Token missing, invalid, expired, or rejected by the backend. |
65+
| `4` | NOT_FOUND | The targeted org / app / database / revision / etc. doesn't exist or isn't reachable. |
66+
| `5` | CONFLICT | A resource with the supplied name already exists. |
67+
| `6` | NETWORK | Backend 5xx, transport failure, or unreachable endpoint. |
68+
69+
`ValidationError` from Cliffy (typos in flag values) currently exits via
70+
Cliffy's own handler with exit code `1` and a usage hint on stderr; this will
71+
move to `2` in a follow-up.
72+
73+
## Structured error envelope
74+
75+
In `--json` mode, every error is written to **stderr** as a single line:
76+
77+
```json
78+
{
79+
"error": {
80+
"code": "AUTH_INVALID_TOKEN",
81+
"message": "The token specified via 'DENO_DEPLOY_TOKEN' or the '--token' flag is invalid or expired.",
82+
"hint": "Generate a new token at https://console.deno.com/account/tokens and re-export DENO_DEPLOY_TOKEN.",
83+
"traceId": "abc123"
84+
}
85+
}
86+
```
87+
88+
Fields:
89+
90+
- `code` — Stable string identifier. Examples: `AUTH_INVALID_TOKEN`,
91+
`NON_INTERACTIVE_REQUIRED`, `MISSING_FLAG`, `SLUG_ALREADY_IN_USE`,
92+
`POSTGRES_ERROR`. New codes may be added; agents should treat unknown codes as
93+
opaque.
94+
- `message` — Human-readable description.
95+
- `hint` — Optional. If present, suggests a concrete next step.
96+
- `traceId` — Optional. Server-side trace identifier from the `x-deno-trace-id`
97+
response header. Useful for bug reports.
98+
99+
Human-mode errors go to stderr too, formatted with ANSI markers, but without the
100+
JSON envelope.
101+
102+
## JSON output schemas (per command)
103+
104+
These shapes are stable; new fields may be added but existing fields will not be
105+
removed without a version bump.
106+
107+
### `deno deploy whoami --json`
108+
109+
```json
110+
{
111+
"authenticated": true,
112+
"user": null,
113+
"orgs": [
114+
{ "id": "...", "slug": "myorg", "name": "My Org", "plan": "pro" }
115+
]
116+
}
117+
```
118+
119+
`user` is currently `null`; future backend support will populate it with
120+
`{ id, name, email, ... }`. Agents reading `authenticated` / `orgs[]` will keep
121+
working.
122+
123+
### `deno deploy orgs list --json`
124+
125+
```json
126+
[
127+
{ "id": "...", "slug": "myorg", "name": "My Org", "plan": "pro" }
128+
]
129+
```
130+
131+
### `deno deploy apps list --json`
132+
133+
```json
134+
{
135+
"items": [
136+
{
137+
"id": "...",
138+
"slug": "my-app",
139+
"createdAt": "2026-05-12T14:40:00.000Z",
140+
"updatedAt": "2026-05-12T14:40:00.000Z",
141+
"layers": ["base"]
142+
}
143+
],
144+
"nextCursor": null,
145+
"org": "myorg"
146+
}
147+
```
148+
149+
### `deno deploy deployments list --json`
150+
151+
```json
152+
{
153+
"items": [
154+
{
155+
"id": "rev_...",
156+
"status": "routed",
157+
"prod": true,
158+
"createdAt": "...",
159+
"updatedAt": "...",
160+
"lastStep": "deployed"
161+
}
162+
],
163+
"nextCursor": null,
164+
"org": "myorg",
165+
"app": "my-app"
166+
}
167+
```
168+
169+
### `deno deploy env list --json`
170+
171+
```json
172+
[
173+
{
174+
"id": "...",
175+
"key": "DATABASE_URL",
176+
"value": "postgres://...",
177+
"isSecret": false,
178+
"contexts": ["production"]
179+
},
180+
{
181+
"id": "...",
182+
"key": "API_KEY",
183+
"value": null,
184+
"isSecret": true,
185+
"contexts": null
186+
}
187+
]
188+
```
189+
190+
`value` is `null` for secrets; `contexts: null` means "all contexts".
191+
192+
### `deno deploy database list --json`
193+
194+
```json
195+
[
196+
{
197+
"name": "my-db",
198+
"engine": "postgresql",
199+
"createdAt": "...",
200+
"assignments": ["my-app"],
201+
"connection": {
202+
"hostname": "db.example.com",
203+
"port": 5432,
204+
"username": "deploy",
205+
"customCertificate": false
206+
},
207+
"databases": [{ "name": "main", "status": "ready", "createdAt": "..." }]
208+
}
209+
]
210+
```
211+
212+
### `deno deploy database query --json`
213+
214+
```json
215+
{ "rows": [{ "column1": "value1", "column2": 42 }] }
216+
```
217+
218+
On query failure the structured error envelope appears on stderr with
219+
`errorCode: "POSTGRES_ERROR"` or `"QUERY_ERROR"`.
220+
221+
### `deno deploy publish --json`
222+
223+
```json
224+
{
225+
"org": "myorg",
226+
"app": "my-app",
227+
"revisionId": "rev_...",
228+
"url": "https://console.deno.com/myorg/my-app/builds/rev_...",
229+
"status": "ready",
230+
"timelines": [
231+
{ "partition": "production", "domains": ["https://my-app.deno.dev"] }
232+
]
233+
}
234+
```
235+
236+
### `deno deploy create --json --dry-run`
237+
238+
```json
239+
{
240+
"dryRun": true,
241+
"org": "myorg",
242+
"app": "my-app",
243+
"repo": null,
244+
"buildDirectory": ".",
245+
"buildConfig": {
246+
"frameworkPreset": "astro",
247+
"mode": "static",
248+
"staticDir": "dist",
249+
"singlePageApp": false
250+
},
251+
"buildTimeout": 5,
252+
"buildMemoryLimit": 1024,
253+
"region": "us"
254+
}
255+
```
256+
257+
### `deno deploy create --json` (non-dry-run, GitHub source)
258+
259+
```json
260+
{
261+
"org": "myorg",
262+
"app": "my-app",
263+
"url": "https://console.deno.com/myorg/my-app",
264+
"revisionId": "rev_...",
265+
"source": "github"
266+
}
267+
```
268+
269+
For local source, the command delegates to `publish --json` and emits its
270+
envelope instead.
271+
272+
### `deno deploy logs --json`
273+
274+
NDJSON, one record per line on stdout:
275+
276+
```json
277+
{"timestamp":"2026-05-12T14:40:00.000Z","traceId":"...","spanId":"...","severity":"INFO","severityNumber":9,"body":"hello","scope":"app","revision":"rev_...","attributes":{...}}
278+
```
279+
280+
## Non-interactive flag coverage
281+
282+
| Subcommand | Required flags under `--non-interactive` |
283+
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
284+
| `deno deploy --prod` | `--org`, `--app` (or pre-existing `deno.json` config) |
285+
| `deno deploy create` | `--org`, `--app`, `--source`, `--region`, plus per-source / per-mode flags |
286+
| `deno deploy env list` | `--org`, `--app` |
287+
| `deno deploy env load` | `--replace` or `--skip-existing` when existing keys overlap |
288+
| `deno deploy database *` | `--org`, plus per-action flags |
289+
| `deno deploy setup-aws` | `--org`, `--app`, `--policies <arn>` (repeatable), optional `--role-name` |
290+
| `deno deploy setup-gcp` | `--org`, `--app`, `--roles <role>` (repeatable), optional `--service-account-name`, `--enable-apis` to bypass the API-enable confirmation |
291+
| `deno deploy whoami` | (none; reads token only) |
292+
| `deno deploy apps list` | `--org` |
293+
| `deno deploy orgs list` | (none) |
294+
| `deno deploy deployments list` | `--org`, `--app` |
295+
296+
When a required flag is missing, the CLI exits `2` (USAGE) with a structured
297+
envelope naming the missing flag.
298+
299+
## Stdio discipline
300+
301+
- **stdout** carries the result of the command. In `--json` mode this is a
302+
single object/array (or NDJSON for streaming). In human mode it is the
303+
formatted table / URL / etc.
304+
- **stderr** carries human progress, prompts, and the structured error envelope.
305+
`--quiet` suppresses progress but keeps the final result on stdout.
306+
307+
This lets you pipe cleanly:
308+
309+
```sh
310+
deno deploy publish --json | jq -r '.url'
311+
deno deploy logs --json --app my-app | jq -c 'select(.severityNumber >= 17)'
312+
deno deploy env list --json | jq '.[] | select(.isSecret == false)'
313+
```
314+
315+
## Examples
316+
317+
### Verify auth
318+
319+
```sh
320+
export DENO_DEPLOY_TOKEN=ddo_...
321+
deno deploy whoami --json
322+
# {"authenticated":true,"user":null,"orgs":[{"id":"...","slug":"myorg","name":"My Org","plan":"pro"}]}
323+
```
324+
325+
### Create an app + deploy from local source
326+
327+
```sh
328+
deno deploy create --json --non-interactive \
329+
--org myorg --app my-app \
330+
--source local --app-directory . \
331+
--runtime-mode static --static-dir dist \
332+
--region us
333+
# (single JSON object: org, app, url, revisionId, status, timelines)
334+
```
335+
336+
### Deploy to an existing app
337+
338+
```sh
339+
deno deploy --json --non-interactive --org myorg --app my-app --prod .
340+
```
341+
342+
### Load secrets from .env, idempotently
343+
344+
```sh
345+
deno deploy env load --org myorg --app my-app --non-interactive --replace .env.production
346+
```
347+
348+
### List failed deployments for an app
349+
350+
```sh
351+
deno deploy deployments list --json --org myorg --app my-app --status failed | jq '.items[] | .id'
352+
```
353+
354+
### Page through all apps
355+
356+
```sh
357+
cursor=""
358+
while :; do
359+
out=$(deno deploy apps list --json --org myorg ${cursor:+--cursor "$cursor"})
360+
echo "$out" | jq '.items[] | .slug'
361+
cursor=$(echo "$out" | jq -r '.nextCursor // empty')
362+
[ -z "$cursor" ] && break
363+
done
364+
```
365+
366+
### Cloud setup (AWS) without prompts
367+
368+
```sh
369+
deno deploy setup-aws --json --non-interactive \
370+
--org myorg --app my-app \
371+
--policies arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
372+
--policies arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess \
373+
--role-name DenoDeploy-myorg-my-app
374+
```
375+
376+
The `--role-name` makes the operation idempotent: re-running with the same name
377+
will surface `SLUG_ALREADY_IN_USE` (`CONFLICT=5`) rather than silently creating
378+
a second resource with a different random suffix.
379+
380+
## Compatibility & versioning
381+
382+
- New fields may be added to any JSON shape. Agents should ignore unknown
383+
fields.
384+
- Exit-code values and `error.code` strings are stable; new values may be
385+
introduced.
386+
- The flag set is stable; new flags may be added but the existing ones will not
387+
be renamed or repurposed.

0 commit comments

Comments
 (0)