@datumos/datum-cli 0.0.0 → 0.588.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +8 -0
- package/README.md +37 -0
- package/cli/a2aCommand.js +225 -0
- package/cli/artifactsPublish.js +356 -0
- package/cli/authCommands.js +168 -0
- package/cli/bundleSignature.js +111 -0
- package/cli/commandTree.js +243 -0
- package/cli/credentialProcess.js +54 -0
- package/cli/credentials.js +93 -0
- package/cli/datum.js +814 -0
- package/cli/entraLogin.js +254 -0
- package/cli/help.js +509 -0
- package/cli/invoke.js +427 -0
- package/cli/mcpServe.js +361 -0
- package/cli/outputsDownload.js +126 -0
- package/cli/previewCommand.js +333 -0
- package/cli/satellitePairToken.js +48 -0
- package/cli/secureOrigin.js +37 -0
- package/cli/selfUpdate.js +779 -0
- package/cli/spaceManifestCommand.js +495 -0
- package/cli/spaceManifestDir.js +481 -0
- package/cli/spaceRepoTemplate.js +463 -0
- package/cli/updateCommand.js +184 -0
- package/npm-shrinkwrap.json +1231 -0
- package/openapi/spec.js +51199 -0
- package/package.json +25 -3
- package/spaces/reservedHostLabels.js +66 -0
package/cli/help.js
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Help rendering for `datum` — every line here is DERIVED from the OpenAPI
|
|
3
|
+
* spec (summaries, parameter/field descriptions, enums, required flags),
|
|
4
|
+
* so `--help` stays current with zero CLI maintenance. If a help page reads
|
|
5
|
+
* poorly, fix the spec (server/src/openapi/spec.ts), not this file.
|
|
6
|
+
*/
|
|
7
|
+
/** ANSI styling, disabled when stdout is not a TTY (pipes stay clean). */
|
|
8
|
+
export function createStyle(colors) {
|
|
9
|
+
const wrap = (code) => (s) => colors ? `\x1b[${code}m${s}\x1b[0m` : s;
|
|
10
|
+
return {
|
|
11
|
+
bold: wrap("1"),
|
|
12
|
+
dim: wrap("2"),
|
|
13
|
+
cyan: wrap("36"),
|
|
14
|
+
yellow: wrap("33"),
|
|
15
|
+
green: wrap("32"),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The hand-written commands — the CLI's own and the person sign-in
|
|
20
|
+
* (docs/developer-access.md). They are not in the OpenAPI-derived command
|
|
21
|
+
* tree, so `renderCommandHelp` never sees them: the root help, `datum
|
|
22
|
+
* commands` and `datum <cmd> --help` all read THIS table.
|
|
23
|
+
*/
|
|
24
|
+
export const BUILTIN_COMMANDS = [
|
|
25
|
+
{
|
|
26
|
+
tokens: ["login"],
|
|
27
|
+
usage: "datum login [--base-url <url>]",
|
|
28
|
+
summary: "sign in with your Microsoft account (device code)",
|
|
29
|
+
detail: "Runs the OAuth device-code flow against the deployment's Entra app registration and stores the\n" +
|
|
30
|
+
"tokens in credentials.json (0600). The login becomes the default --base-url. Note: an API key\n" +
|
|
31
|
+
"(--api-key / DATUM_API_KEY / MCA_API_KEY) always wins over the sign-in — `datum whoami` tells\n" +
|
|
32
|
+
"you which credential the next API call will use.",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
tokens: ["update"],
|
|
36
|
+
usage: "datum update [--check | --to <version|package> | --auto on|off]",
|
|
37
|
+
summary: "update the CLI to the release the server runs (it also updates itself in the background)",
|
|
38
|
+
detail: "Asks the server (GET /api/cli/latest) for the release it runs and installs that CLI bundle next to the\n" +
|
|
39
|
+
"current one (verified by sha512, smoke-tested with --version); the NEXT `datum` runs it. Without this\n" +
|
|
40
|
+
"command the CLI checks at most every 6 h in the background, and updates in the foreground when it is\n" +
|
|
41
|
+
"older than the server's minimum. A failed install of the same bundle is retried automatically after\n" +
|
|
42
|
+
"15 min, 6 h, then daily; this command tries at once. --check prints what runs here, what the server\n" +
|
|
43
|
+
"offers and the last failed install; --to switches to an installed version (rollback), `--to package`\n" +
|
|
44
|
+
"back to the npm-installed one; --auto off opts this user out. DATUM_AUTOUPDATE=on|off|min-only and\n" +
|
|
45
|
+
"DATUM_NO_AUTOUPDATE=1 win; CI=true means off.",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
tokens: ["whoami"],
|
|
49
|
+
usage: "datum whoami [--space <slug>]",
|
|
50
|
+
summary: "who the server says you are, your role, and which credential the next call uses",
|
|
51
|
+
detail: "GET /api/me with your sign-in on the org host (or the space host with --space / DATUM_SPACE):\n" +
|
|
52
|
+
"identity, effective role (the same as in the panel), role source. `nextCallUses` says whether API\n" +
|
|
53
|
+
"commands would ride your sign-in (/api) or an API key found in the flags/environment (/public/api).",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
tokens: ["logout"],
|
|
57
|
+
usage: "datum logout [--base-url <url>]",
|
|
58
|
+
summary: "forget the local sign-in",
|
|
59
|
+
detail: "Deletes the stored tokens for that origin; the Microsoft session itself is untouched.",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
tokens: ["a2a"],
|
|
63
|
+
usage: "datum a2a <card get | send \"…\" | task get|list|cancel …> [a2a flags] [--space <slug>] [--agent <nick>]",
|
|
64
|
+
summary: "delegate a task to the space's agent (or a named one) over A2A — the official a2a CLI, as you, token never shown",
|
|
65
|
+
detail: "Runs the official a2a CLI (github.com/a2aproject/a2a-cli) against the space's A2A channel (#1382) with\n" +
|
|
66
|
+
"your sign-in: the space host comes from --space (or the host of the login), the access token goes only\n" +
|
|
67
|
+
"into the a2a process, the nearest .env is replaced by an empty config, and flags that would move the\n" +
|
|
68
|
+
"destination or the credential (-a, --agent-card, --endpoint, --auth, --svc-param, --config, --tenant,\n" +
|
|
69
|
+
"--insecure) are refused. The verb comes first: card, send or task.\n" +
|
|
70
|
+
"--agent <nick> (env DATUM_A2A_AGENT) addresses that agent of the space instead of its default one\n" +
|
|
71
|
+
"(#1647) — same host, same sign-in; datum consumes the flag, a2a never sees it.\n" +
|
|
72
|
+
' datum a2a send --space adops --stream "Czemu das-bidder ma wzrost 5xx od 14:00?"\n' +
|
|
73
|
+
' datum a2a send --space adops --agent bidder-doctor --async "Zbadaj 5xx das-biddera"\n' +
|
|
74
|
+
" datum a2a task get <taskId> --wait --timeout 30m --space adops",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
tokens: ["mcp", "serve"],
|
|
78
|
+
usage: "datum mcp serve [--space <slug>]",
|
|
79
|
+
summary: "stdio MCP server for Claude Code — the space's agent tools",
|
|
80
|
+
detail: "Proxies Claude Code (stdio) to POST <space host>/api/mcp with your sign-in. Not signed in?\n" +
|
|
81
|
+
"It serves `datum_login` whose result carries the device code. Logs go to stderr (stdout is the\n" +
|
|
82
|
+
"protocol). Registered by the `datum` Claude Code plugin's .mcp.json; a project pins its space with\n" +
|
|
83
|
+
'`"args": ["mcp", "serve", "--space", "<slug>"]`.',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
tokens: ["artifacts", "publish"],
|
|
87
|
+
usage: "datum artifacts publish <dir> [--key <k>] [--title <t>] [--description <d>] [--entry <file>] [--space <slug>]",
|
|
88
|
+
summary: "publish a directory (a page plus its assets) as an artifact version",
|
|
89
|
+
detail: "Walks <dir> by the collector's rules (no dotfiles, no node_modules, no symlinks, at most 200 files),\n" +
|
|
90
|
+
"reads <dir>/manifest.json when it is there ({ key, title, description, entry }; the flags win), then\n" +
|
|
91
|
+
"picks the transport: up to 8 MiB the files ride ONE request body, above it the two-step direct upload\n" +
|
|
92
|
+
"(plan → a presigned PUT per file → commit), which is how the runtime ships a run's outputs. The `key`\n" +
|
|
93
|
+
"is the artifact's identity in the space — the same key appends a version to the SAME link.\n" +
|
|
94
|
+
"Visibility is never set here: a new artifact is space-private until a human climbs the ladder in the\n" +
|
|
95
|
+
"panel, and a new version of an artifact ALREADY shared beyond its space comes back as a draft awaiting\n" +
|
|
96
|
+
"an operator's approval (#589, docs/artifacts.md). Hand-written because it is a walk plus several calls;\n" +
|
|
97
|
+
"`datum artifacts publish plan` / `… commit` are the derived commands for the raw two-step endpoints.",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
tokens: ["outputs", "download"],
|
|
101
|
+
usage: "datum outputs download <id> [--out <file>] [--space <slug>]",
|
|
102
|
+
summary: "download an output file's bytes (to --out, or to stdout)",
|
|
103
|
+
detail: "Reads the output's metadata (GET /api/outputs/<id>) and fetches its bytes from /outputs/<id>/<name> with\n" +
|
|
104
|
+
"the same credential (#570, #2680) — the only way a run gets a file an earlier run made without touching\n" +
|
|
105
|
+
"storage. Without --out the bytes go to stdout. Find the id with a search:\n" +
|
|
106
|
+
" datum outputs list --name Adserver_koszty --limit 5\n" +
|
|
107
|
+
" datum outputs download <id> --out prognoza.xlsx\n" +
|
|
108
|
+
"The size and sha256 are checked against the metadata; a mismatch fails and leaves no file.",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
tokens: ["preview", "up"],
|
|
112
|
+
usage: "datum preview up --port <port> [--vhost <vhost>] [--machine <name>] [--space <slug>]",
|
|
113
|
+
summary: "preview a port of your own machine through the Accelerator gateway (Ctrl+C closes it)",
|
|
114
|
+
detail: "The gateway's `cde app start` (#2928): the platform routes the vhost's gateway variant to 127.0.0.1:<port>\n" +
|
|
115
|
+
"of your personal machine, and its satellite holds the machine's preview link — run `datum satellite up --pair`\n" +
|
|
116
|
+
"once on this computer and keep the daemon running. Prints the link to open and the headers your own requests\n" +
|
|
117
|
+
"(curl, Playwright) send through the edge, then follows the preview until Ctrl+C (or a closed terminal),\n" +
|
|
118
|
+
"which closes it. The preview lives on a 3-minute lease this command renews: if it dies, the port leaves\n" +
|
|
119
|
+
"the edge within minutes. --vhost may be left out when the space has one vhost; without --machine the\n" +
|
|
120
|
+
"machine is THIS computer (another computer of yours is previewed only by name).\n" +
|
|
121
|
+
"Needs `datum login` in person (an API key or \"Login as\" is refused).",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
tokens: ["preview", "down"],
|
|
125
|
+
usage: "datum preview down <id> [--space <slug>]",
|
|
126
|
+
summary: "close one of your terminal previews",
|
|
127
|
+
detail: "Removes the route (the id from `datum previews`). The machine's preview link ends with its last preview.",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
tokens: ["preview", "trace"],
|
|
131
|
+
usage: "datum preview trace <id> [--follow] [--limit <n>] [--space <slug>]",
|
|
132
|
+
summary: "the gateway's last requests to a preview (the gateway's `cde trace`)",
|
|
133
|
+
detail: "One line per request the gateway routed: time, method (WS for a WebSocket), path WITHOUT the query, status\n" +
|
|
134
|
+
"(no answer = the link failed or the machine refused the port), time and size. --follow keeps printing new\n" +
|
|
135
|
+
"ones (a request shows up within a few seconds). The path keeps its segments — `;jsessionid=…` or a token IN\n" +
|
|
136
|
+
"the path (/reset/<token>) is recorded as typed; the query string never is. For a preview of yours or one\n" +
|
|
137
|
+
"that ends on your machine.",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
tokens: ["previews"],
|
|
141
|
+
usage: "datum previews [--json] [--space <slug>]",
|
|
142
|
+
summary: "your previews and every route on your machines, with their links (the gateway's `cde interfaces`)",
|
|
143
|
+
detail: "Lists your `datum preview up` previews and the gateway routes a session opened on your machines: id, vhost,\n" +
|
|
144
|
+
"machine, port, state, owner, expiry and the link to open — then your machines and whether their daemon\n" +
|
|
145
|
+
"offers the preview link. --json prints the API's answer.",
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
tokens: ["space-manifest", "pull"],
|
|
149
|
+
usage: "datum space-manifest pull <dir> [--space <slug>]",
|
|
150
|
+
summary: "write the space's live configuration into <dir> as reviewable files (space admin)",
|
|
151
|
+
detail: "GET /api/space-manifest, split into files (#1809, docs/space-admin/space-repository.md): <dir>/space.json\n" +
|
|
152
|
+
"(apiVersion + scalar kinds), <dir>/<kind>.json for map kinds, <dir>/<kind>/<entry>.json per list entry,\n" +
|
|
153
|
+
"prompts as <entry>.prompt.md and schemas / flow definitions as <entry>.<field>.json referenced with\n" +
|
|
154
|
+
'{ "$file": … }. Organisation-tier kinds (features, budget, members, grants) and empty kinds are left out.\n' +
|
|
155
|
+
"<dir> must be empty or one pull wrote before: its layout files are replaced, so an entry gone from the\n" +
|
|
156
|
+
"space disappears; other files are never touched. Needs the operator role in the space (#1833) — a\n" +
|
|
157
|
+
"member's `datum login` sign-in, or a key bound to the space (the CI).",
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
tokens: ["space-manifest", "push"],
|
|
161
|
+
usage: "datum space-manifest push <dir> [--dry-run] [--prune] [--expect-empty] [--markdown <file>] [--raw] [--space <slug>]",
|
|
162
|
+
summary: "assemble <dir> into the space manifest and plan it (--dry-run, operator) or apply it (space admin)",
|
|
163
|
+
detail: "Reads the files `pull` writes, refuses organisation-tier kinds and anything that looks like key\n" +
|
|
164
|
+
"material, then PUT /api/space-manifest: --dry-run plans against the live space, without it the plan\n" +
|
|
165
|
+
"is applied. Prints the plan as Markdown (the diff of a prompt as changed lines) — --markdown also\n" +
|
|
166
|
+
"writes it to a file for a pull-request comment, --raw prints the server's JSON answer instead.\n" +
|
|
167
|
+
"--prune deletes list entries <dir> does not name, within the kinds it declares (default: orphans, left\n" +
|
|
168
|
+
"alone). A dry run always goes first and must answer for the space <dir>/.space-id names (pull writes it).\n" +
|
|
169
|
+
"--expect-empty fails (exit 1) when the plan would write anything — the convergence check after an apply.",
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
tokens: ["space-manifest", "init"],
|
|
173
|
+
usage: "datum space-manifest init <dir> --space <slug> [--refresh]",
|
|
174
|
+
summary: "turn <dir> into a space repository: CI plan/apply, daily sync, AGENTS.md (offline)",
|
|
175
|
+
detail: "Writes .github/workflows/datum-space.yml (plan on a pull request, apply + convergence on main; a manual\n" +
|
|
176
|
+
"run can prune), .github/workflows/datum-space-sync.yml (import, then panel edits back as a pull request),\n" +
|
|
177
|
+
"AGENTS.md, CLAUDE.md and README.md for the space. An existing file is kept — except the block Datum owns\n" +
|
|
178
|
+
"in AGENTS.md / CLAUDE.md (between the datum:managed markers), refreshed on every run so the rules follow\n" +
|
|
179
|
+
"the Datum release; --refresh also replaces the workflows with the current version. The workflows read\n" +
|
|
180
|
+
"the repository variable DATUM_BASE_URL and the secret DATUM_SPACE_API_KEY (a key bound to the space,\n" +
|
|
181
|
+
"role admin). No call to the server.",
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
tokens: ["space-manifest", "validate"],
|
|
185
|
+
usage: "datum space-manifest validate <dir> [--space <slug>]",
|
|
186
|
+
summary: "check the files of <dir> against the manifest's shape before a pull request (any member)",
|
|
187
|
+
detail: "Assembles <dir> the way `push` does ($file references, the local refusal of key material and\n" +
|
|
188
|
+
"organisation-tier kinds), then POST /api/space-manifest/validate on the space's host. For a space admin:\n" +
|
|
189
|
+
"exactly the checks of the dry run (every kind's validator against the live space — a bad cron, an unknown\n" +
|
|
190
|
+
"credential), without the plan. Below admin: the schema, closed vocabularies and duplicate keys only, and\n" +
|
|
191
|
+
"the CLI says \"shape only\" — the kind validators read data a member may not see; a space admin's\n" +
|
|
192
|
+
"`push <dir> --dry-run` (or the pull request's CI) gives the full check. Nothing is written; any member may\n" +
|
|
193
|
+
"run it, with a sign-in (`datum login`) or a key. Exit 1 lists the\n" +
|
|
194
|
+
"issues with their document paths.",
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
tokens: ["space-manifest", "schema"],
|
|
198
|
+
usage: "datum space-manifest schema [kind] [--space <slug>]",
|
|
199
|
+
summary: "the manifest's JSON Schema — whole, or one kind's fields with their descriptions (any member)",
|
|
200
|
+
detail: "GET /api/space-manifest/schema on the space's host: one property per manifest kind this instance\n" +
|
|
201
|
+
"offers, with field descriptions and the x-datum-tier / x-datum-key / x-datum-file-fields metadata.\n" +
|
|
202
|
+
"With a kind (routines, flows, agents, queues, …) prints only that kind — the shape to write before\n" +
|
|
203
|
+
"creating a new entry. An unknown kind lists the kinds.",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
tokens: ["satellite"],
|
|
207
|
+
usage: "datum satellite <up|pair|lint|try> [...]",
|
|
208
|
+
summary: "the satellite daemon on THIS machine (pass-through to the installed `satellite` binary)",
|
|
209
|
+
detail: "Runs the installed @datum/datum-satellite CLI with the remaining arguments. The personal path (#504):\n" +
|
|
210
|
+
"`datum satellite up --pair <token> --server <url>` redeems the one-time token from Account →\n" +
|
|
211
|
+
"Development → \"Register my machine\", writes the key and starts the daemon — your own machine\n" +
|
|
212
|
+
"becomes an execution target of your sessions. Install the daemon first:\n" +
|
|
213
|
+
"`npm i -g @datum/datum-satellite` (from your organisation's npm registry).",
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
/** `datum <builtin> --help`: usage, summary and the longer note. */
|
|
217
|
+
export function renderBuiltinHelp(cmd, style) {
|
|
218
|
+
const lines = [style.bold(cmd.usage), ` ${cmd.summary}`];
|
|
219
|
+
if (cmd.detail)
|
|
220
|
+
lines.push("", ...cmd.detail.split("\n").map((l) => ` ${l}`));
|
|
221
|
+
lines.push("", `Global options: ${style.dim("datum --help")}`);
|
|
222
|
+
return lines.join("\n");
|
|
223
|
+
}
|
|
224
|
+
export const GLOBAL_OPTIONS = [
|
|
225
|
+
{
|
|
226
|
+
flag: "--base-url <url>",
|
|
227
|
+
description: "Server origin (env DATUM_BASE_URL; default http://localhost:3001)",
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
flag: "--api-key <key>",
|
|
231
|
+
description: "API key datum_… (env DATUM_API_KEY). Without one, the CLI uses the " +
|
|
232
|
+
"sign-in from `datum login` (your Microsoft account). A key rides the " +
|
|
233
|
+
"public surface and pins your identity for audit (panel: Settings → " +
|
|
234
|
+
"API keys)",
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
flag: "--space <slug>",
|
|
238
|
+
description: "Address a SPACE (env DATUM_SPACE): the call goes to " +
|
|
239
|
+
"https://<slug>.<base-url host>, the space's own host",
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
flag: "--env <variant>",
|
|
243
|
+
description: "Target deployment variant (env DATUM_ENV): PROD, STAGE, TEST or a " +
|
|
244
|
+
"personal dev variant — pins the Ring Accelerator route via " +
|
|
245
|
+
"x-oa-variant",
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
flag: "--data <json|@file|->",
|
|
249
|
+
description: "Full JSON request body (like curl -d): inline JSON, @path to a " +
|
|
250
|
+
"file, or - for stdin. Field flags override matching body keys",
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
flag: "--fields <a,b,c>",
|
|
254
|
+
description: "Keep only these top-level keys of the JSON response (per element " +
|
|
255
|
+
"for arrays) — tames huge list responses",
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
flag: "--curl",
|
|
259
|
+
description: "Print the equivalent curl instead of calling. A body over 2 KB is " +
|
|
260
|
+
"named (-d @file), not expanded",
|
|
261
|
+
},
|
|
262
|
+
{ flag: "--raw", description: "Raw response body (no pretty-printing)" },
|
|
263
|
+
{
|
|
264
|
+
flag: "--full",
|
|
265
|
+
description: "Print an oversized response instead of parking it in a file, and " +
|
|
266
|
+
"expand a large --curl body instead of naming it — off by default, " +
|
|
267
|
+
"because printing costs more than reading does",
|
|
268
|
+
},
|
|
269
|
+
{ flag: "--help, -h", description: "Help for the command (or this overview)" },
|
|
270
|
+
{ flag: "--version", description: "CLI/app version" },
|
|
271
|
+
];
|
|
272
|
+
function columns(rows, indent, style, leftStyle = style.cyan) {
|
|
273
|
+
const width = Math.max(0, ...rows.map(([l]) => l.length));
|
|
274
|
+
return rows.map(([l, r]) => `${indent}${leftStyle(l.padEnd(width))}${r ? ` ${r}` : ""}`);
|
|
275
|
+
}
|
|
276
|
+
function flagLabel(f) {
|
|
277
|
+
if (f.type === "boolean")
|
|
278
|
+
return `--${f.flag}[=false]`;
|
|
279
|
+
const hint = f.enum
|
|
280
|
+
? f.enum.join("|")
|
|
281
|
+
: f.type === "array"
|
|
282
|
+
? `${f.flag}…`
|
|
283
|
+
: f.type === "object"
|
|
284
|
+
? "json"
|
|
285
|
+
: f.type === "string"
|
|
286
|
+
? f.flag
|
|
287
|
+
: f.type;
|
|
288
|
+
return `--${f.flag} <${hint}>`;
|
|
289
|
+
}
|
|
290
|
+
function flagDescription(f, style) {
|
|
291
|
+
const parts = [];
|
|
292
|
+
if (f.description)
|
|
293
|
+
parts.push(f.description);
|
|
294
|
+
const meta = [];
|
|
295
|
+
if (f.required)
|
|
296
|
+
meta.push("required");
|
|
297
|
+
if (f.type === "array")
|
|
298
|
+
meta.push("repeatable; a {…}/[…] value is parsed as JSON");
|
|
299
|
+
if (f.type === "number" || f.type === "integer")
|
|
300
|
+
meta.push(f.type);
|
|
301
|
+
if (f.type === "object")
|
|
302
|
+
meta.push("JSON value");
|
|
303
|
+
if (meta.length > 0)
|
|
304
|
+
parts.push(style.yellow(`(${meta.join(", ")})`));
|
|
305
|
+
return parts.join(" ");
|
|
306
|
+
}
|
|
307
|
+
function exampleValue(f) {
|
|
308
|
+
if (f.enum?.length)
|
|
309
|
+
return f.enum[0];
|
|
310
|
+
if (f.type === "boolean")
|
|
311
|
+
return "";
|
|
312
|
+
if (f.type === "number" || f.type === "integer")
|
|
313
|
+
return "10";
|
|
314
|
+
if (f.type === "object")
|
|
315
|
+
return "'{…}'";
|
|
316
|
+
return `<${f.flag}>`;
|
|
317
|
+
}
|
|
318
|
+
/** `datum` / `datum --help`: overview + command groups. */
|
|
319
|
+
export function renderRootHelp(spec, commands, style) {
|
|
320
|
+
const groups = new Map();
|
|
321
|
+
for (const c of commands) {
|
|
322
|
+
const g = c.tokens[0] ?? c.verb;
|
|
323
|
+
groups.set(g, (groups.get(g) ?? 0) + 1);
|
|
324
|
+
}
|
|
325
|
+
const rows = [...groups.entries()]
|
|
326
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
327
|
+
.map(([g, n]) => [g, `${n} command${n === 1 ? "" : "s"}`]);
|
|
328
|
+
const lines = [
|
|
329
|
+
`${style.bold("datum")} — ${spec.info.title} CLI`,
|
|
330
|
+
"",
|
|
331
|
+
"Every command is derived live from the server's OpenAPI contract",
|
|
332
|
+
"(openapi.json) — this help is always in sync with the API.",
|
|
333
|
+
"",
|
|
334
|
+
style.bold("Usage"),
|
|
335
|
+
` datum <group> [subgroup] <verb> [positionals] [flags]`,
|
|
336
|
+
` datum <group> --help commands in a group`,
|
|
337
|
+
` datum <command…> --help full help for one command`,
|
|
338
|
+
` datum commands flat list of every command`,
|
|
339
|
+
` datum spec the OpenAPI spec (over 1 MB — written to a file; --full to print)`,
|
|
340
|
+
"",
|
|
341
|
+
// The hand-written commands: the sign-in (developer access), the
|
|
342
|
+
// satellite pass-through and `artifacts publish` (#589). They are not
|
|
343
|
+
// derived from the contract, so they are named here or nowhere.
|
|
344
|
+
style.bold("Hand-written commands (sign-in, this machine, publishing)"),
|
|
345
|
+
...columns(BUILTIN_COMMANDS.map((c) => [c.usage, c.summary]), " ", style),
|
|
346
|
+
"",
|
|
347
|
+
style.bold("Command groups"),
|
|
348
|
+
...columns(rows, " ", style),
|
|
349
|
+
"",
|
|
350
|
+
style.bold("Global options"),
|
|
351
|
+
...columns(GLOBAL_OPTIONS.map((o) => [o.flag, o.description]), " ", style),
|
|
352
|
+
"",
|
|
353
|
+
style.bold("Verbs"),
|
|
354
|
+
" list/get read, create adds, update patches fields, set replaces,",
|
|
355
|
+
" delete removes; other verbs are actions (escalate, sync, publish…).",
|
|
356
|
+
"",
|
|
357
|
+
`Docs: docs/cli.md · docs/public-api.md`,
|
|
358
|
+
];
|
|
359
|
+
return lines.join("\n");
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Closest existing commands for a mistyped prefix, by token overlap with
|
|
363
|
+
* command names (hyphens split too, so `status-page incident` still finds
|
|
364
|
+
* `status-systems incidents …`). Agents guess names like `status-page
|
|
365
|
+
* incident create` — pointing at the real command beats a bare error.
|
|
366
|
+
*/
|
|
367
|
+
function suggestCommands(words, commands) {
|
|
368
|
+
const tokens = [
|
|
369
|
+
...new Set(words
|
|
370
|
+
.flatMap((w) => w.toLowerCase().split("-"))
|
|
371
|
+
.filter((t) => t.length >= 3)),
|
|
372
|
+
];
|
|
373
|
+
if (tokens.length === 0)
|
|
374
|
+
return [];
|
|
375
|
+
const scored = commands
|
|
376
|
+
.map((c) => ({
|
|
377
|
+
command: c,
|
|
378
|
+
score: tokens.filter((t) => c.name.includes(t)).length,
|
|
379
|
+
}))
|
|
380
|
+
.filter(({ score }) => score > 0)
|
|
381
|
+
.sort((a, b) => b.score - a.score || a.command.name.length - b.command.name.length);
|
|
382
|
+
if (scored.length === 0)
|
|
383
|
+
return [];
|
|
384
|
+
const best = scored[0].score;
|
|
385
|
+
return scored
|
|
386
|
+
.filter((s) => s.score === best)
|
|
387
|
+
.slice(0, 5)
|
|
388
|
+
.map((s) => s.command);
|
|
389
|
+
}
|
|
390
|
+
/** `datum cases --help` (or bare `datum cases`): every command in the group. */
|
|
391
|
+
export function renderGroupHelp(group, commands, style) {
|
|
392
|
+
const prefix = group.join(" ");
|
|
393
|
+
const matching = commands.filter((c) => c.name === prefix || c.name.startsWith(`${prefix} `));
|
|
394
|
+
if (matching.length === 0) {
|
|
395
|
+
const groups = [...new Set(commands.map((c) => c.tokens[0] ?? c.verb))];
|
|
396
|
+
const suggestions = suggestCommands(group, commands);
|
|
397
|
+
return [
|
|
398
|
+
`Unknown command: ${style.bold(prefix)}`,
|
|
399
|
+
"",
|
|
400
|
+
...(suggestions.length > 0
|
|
401
|
+
? [
|
|
402
|
+
"Did you mean:",
|
|
403
|
+
...columns(suggestions.map((c) => [
|
|
404
|
+
[c.name, ...c.positionals.map((p) => `<${p.name}>`)].join(" "),
|
|
405
|
+
c.summary,
|
|
406
|
+
]), " ", style),
|
|
407
|
+
"",
|
|
408
|
+
]
|
|
409
|
+
: []),
|
|
410
|
+
`Groups: ${groups.sort().join(", ")}`,
|
|
411
|
+
`Try ${style.cyan("datum --help")} or ${style.cyan("datum commands")}.`,
|
|
412
|
+
].join("\n");
|
|
413
|
+
}
|
|
414
|
+
// A hand-written command under this prefix (`datum artifacts publish
|
|
415
|
+
// <dir>`) belongs in the group's listing too — it is the one a person
|
|
416
|
+
// actually reaches for, and the derived siblings would otherwise be the
|
|
417
|
+
// only thing `datum artifacts` names.
|
|
418
|
+
const builtins = BUILTIN_COMMANDS.filter((b) => b.tokens.length > group.length && group.every((g, i) => b.tokens[i] === g));
|
|
419
|
+
const rows = [
|
|
420
|
+
...builtins.map((b) =>
|
|
421
|
+
// Tokens and positionals only — the optional flags belong on the
|
|
422
|
+
// command's own --help, not in a group listing's column.
|
|
423
|
+
[b.usage.replace(/^datum /, "").split(" [")[0], b.summary]),
|
|
424
|
+
...matching.map((c) => [
|
|
425
|
+
[c.name, ...c.positionals.map((p) => `<${p.name}>`)].join(" "),
|
|
426
|
+
c.summary,
|
|
427
|
+
]),
|
|
428
|
+
];
|
|
429
|
+
const total = matching.length + builtins.length;
|
|
430
|
+
return [
|
|
431
|
+
`${style.bold(`datum ${prefix}`)} — ${total} command${total === 1 ? "" : "s"}`,
|
|
432
|
+
"",
|
|
433
|
+
...columns(rows, " ", style),
|
|
434
|
+
"",
|
|
435
|
+
`Run ${style.cyan(`datum ${matching[0].name} --help`)} for flags and examples.`,
|
|
436
|
+
].join("\n");
|
|
437
|
+
}
|
|
438
|
+
/** Full help page for one command — the "phenomenal --help". */
|
|
439
|
+
export function renderCommandHelp(command, style) {
|
|
440
|
+
const usage = [
|
|
441
|
+
"datum",
|
|
442
|
+
command.name,
|
|
443
|
+
...command.positionals.map((p) => `<${p.name}>`),
|
|
444
|
+
...(command.queryFlags.length + command.bodyFlags.length > 0
|
|
445
|
+
? ["[flags]"]
|
|
446
|
+
: []),
|
|
447
|
+
].join(" ");
|
|
448
|
+
const lines = [
|
|
449
|
+
`${style.bold(`datum ${command.name}`)} — ${command.summary}`,
|
|
450
|
+
style.dim(`${command.method} ${command.path}`),
|
|
451
|
+
];
|
|
452
|
+
if (command.description)
|
|
453
|
+
lines.push("", command.description);
|
|
454
|
+
lines.push("", style.bold("Usage"), ` ${usage}`);
|
|
455
|
+
if (command.positionals.length > 0) {
|
|
456
|
+
lines.push("", style.bold("Arguments"), ...columns(command.positionals.map((p) => [`<${p.name}>`, p.description || `path parameter`]), " ", style));
|
|
457
|
+
}
|
|
458
|
+
if (command.queryFlags.length > 0) {
|
|
459
|
+
lines.push("", style.bold("Query flags"), ...columns(command.queryFlags.map((f) => [flagLabel(f), flagDescription(f, style)]), " ", style));
|
|
460
|
+
}
|
|
461
|
+
if (command.bodyFlags.length > 0) {
|
|
462
|
+
const header = command.bodyDescription
|
|
463
|
+
? `Body flags ${style.dim(`— ${command.bodyDescription}`)}`
|
|
464
|
+
: "Body flags";
|
|
465
|
+
lines.push("", style.bold(header), ...columns([...command.bodyFlags]
|
|
466
|
+
.sort((a, b) => Number(b.required) - Number(a.required))
|
|
467
|
+
.map((f) => [flagLabel(f), flagDescription(f, style)]), " ", style));
|
|
468
|
+
if (command.bodyOpen) {
|
|
469
|
+
lines.push(style.dim(" The body accepts additional fields — pass them via --data."));
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
else if (command.hasBody) {
|
|
473
|
+
lines.push("", style.bold("Body"), ` ${command.bodyDescription || "JSON body"} — pass it via --data '<json>'.`);
|
|
474
|
+
}
|
|
475
|
+
if (command.responses.length > 0) {
|
|
476
|
+
lines.push("", style.bold("Responses"), ...columns(command.responses.map((r) => [r.status, r.description]), " ", style, style.green));
|
|
477
|
+
}
|
|
478
|
+
const exampleFlags = [
|
|
479
|
+
...command.queryFlags.filter((f) => f.required),
|
|
480
|
+
...command.bodyFlags.filter((f) => f.required),
|
|
481
|
+
]
|
|
482
|
+
.slice(0, 3)
|
|
483
|
+
.map((f) => f.type === "boolean" ? `--${f.flag}` : `--${f.flag} ${exampleValue(f)}`);
|
|
484
|
+
const example = [
|
|
485
|
+
"datum",
|
|
486
|
+
command.name,
|
|
487
|
+
...command.positionals.map((p) => `<${p.name}>`),
|
|
488
|
+
...exampleFlags,
|
|
489
|
+
].join(" ");
|
|
490
|
+
if (command.examples.length > 0) {
|
|
491
|
+
lines.push("", style.bold("Examples"), ...command.examples.map((e) => ` ${e}`));
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
lines.push("", style.bold("Example"), ` ${example}`);
|
|
495
|
+
}
|
|
496
|
+
lines.push("", style.dim("Global options: datum --help · equivalent curl: add --curl"));
|
|
497
|
+
return lines.join("\n");
|
|
498
|
+
}
|
|
499
|
+
/** `datum commands`: the flat list (grep-friendly). */
|
|
500
|
+
export function renderCommandList(commands, style) {
|
|
501
|
+
// The hand-written commands first — they are the ones a developer looks
|
|
502
|
+
// for when the plugin's MCP server is not starting.
|
|
503
|
+
const rows = [
|
|
504
|
+
...BUILTIN_COMMANDS.map((c) => [c.usage, c.summary]),
|
|
505
|
+
...commands.map((c) => [[c.name, ...c.positionals.map((p) => `<${p.name}>`)].join(" "), c.summary]),
|
|
506
|
+
];
|
|
507
|
+
return columns(rows, "", style).join("\n");
|
|
508
|
+
}
|
|
509
|
+
//# sourceMappingURL=help.js.map
|