@idapt/cli 1.8.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.
@@ -0,0 +1,380 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * v1 contract framework — the SINGLE SOURCE of truth for the public API surface.
5
+ *
6
+ * Each endpoint is described once as a {@link V1CommandSpec}: its zod request +
7
+ * response schemas (snake_case, matching the wire), its HTTP binding, the
8
+ * `idapt <resource> <verb>` grammar it maps to, and the surface flags that
9
+ * decide whether it appears in the public OpenAPI spec and/or as an agent tool.
10
+ *
11
+ * Consumers (all derive from these specs — nothing is hand-maintained twice):
12
+ * - v1 route handlers → import the request/response schemas for validation
13
+ * - `@idapt/sdk` → derives wire TYPES via `z.infer` (type-only import)
14
+ * - `shared/api/v1/openapi` → GENERATED from the `public` specs
15
+ * - `@idapt/cli` → builds its command catalog (grammar + HTTP binding
16
+ * + docs) from the `agentExposed` specs
17
+ *
18
+ * Wire convention: every request/response schema is **snake_case**, because the
19
+ * v1 response boundary runs `deepSnakeCase()` and request bodies/queries arrive
20
+ * snake_case. Backend transforms emit camelCase and are snake-cased at the
21
+ * envelope; the response schema here describes the POST-snake_case shape.
22
+ */
23
+
24
+ type HttpMethod = "GET" | "POST" | "PATCH" | "DELETE";
25
+ /** Where the non-path request fields are carried. */
26
+ type ArgLocation = "path" | "query" | "body" | "multipart";
27
+ /**
28
+ * Shape of the response `data` payload.
29
+ * - `single` → `{ data: T }`
30
+ * - `list` → `{ data: T[], pagination }`
31
+ * - `created` → `{ data: T }` with 201
32
+ * - `deleted` → `{ deleted: true, id }`
33
+ * - `binary` → raw bytes / stream (no JSON envelope; `response` is null)
34
+ */
35
+ type ResponseKind = "single" | "list" | "created" | "deleted" | "binary";
36
+ /** Wire auth mode — drives the OpenAPI `security` requirement + whether 401 is documented. */
37
+ type V1AuthMode = "standard" | "optional" | "public" | "bearer-only";
38
+ /** HTTP error statuses the v1 surface documents. */
39
+ type V1ErrorStatus = 401 | 402 | 403 | 404 | 409 | 422 | 429 | 500 | 503;
40
+ /**
41
+ * A bespoke error response. Use instead of a bare {@link V1ErrorStatus} when an
42
+ * op documents an error with op-specific prose / example / headers that the
43
+ * shared `components.responses` entry would not convey (e.g. the settings
44
+ * slug-cooldown 429 carrying a `Retry-After` header), OR to point a non-default
45
+ * status at a named shared response (`ref`, e.g. the `ModelNotAvailableForTier`
46
+ * 403 returned by image/speech generation).
47
+ */
48
+ interface V1ErrorOverride {
49
+ readonly status: number;
50
+ /** Name of a shared `components.responses` entry to $ref (XOR `description`). */
51
+ readonly ref?: string;
52
+ /** Inline response description (XOR `ref`). */
53
+ readonly description?: string;
54
+ readonly example?: unknown;
55
+ readonly headers?: Readonly<Record<string, {
56
+ readonly description?: string;
57
+ readonly schema: unknown;
58
+ }>>;
59
+ }
60
+ type V1OpError = V1ErrorStatus | V1ErrorOverride;
61
+ /**
62
+ * Optional documentation + binding metadata carried by every command. These
63
+ * feed the GENERATED OpenAPI spec (`scripts/generate-openapi.ts`); none affect
64
+ * runtime behaviour. Keeping them on the spec keeps the contract the single
65
+ * source for the public API surface (no second hand-maintained doc).
66
+ */
67
+ interface V1CommandDocFields {
68
+ /** Tier-2a contract help; falls back to schema-derived rendering when absent. */
69
+ readonly help?: string;
70
+ /** Human-CLI table columns; `field` is a dotted path into the `data` row. */
71
+ readonly columns?: readonly {
72
+ readonly header: string;
73
+ readonly field: string;
74
+ }[];
75
+ /** Per-op behavioural prose → the OpenAPI operation `description` (Markdown). */
76
+ readonly description?: string;
77
+ /** OpenAPI tag names; defaults to the resource→tag map when omitted. */
78
+ readonly tags?: readonly string[];
79
+ /** Stable OpenAPI `operationId`; defaults to camelCase(verb + Resource). */
80
+ readonly operationId?: string;
81
+ /** Wire auth mode (default `"standard"`). */
82
+ readonly auth?: V1AuthMode;
83
+ /**
84
+ * Documented error responses BEYOND the derived baseline (the generator adds
85
+ * 401 when authed, 404 when path-scoped, 422 when the op has a validated
86
+ * body/query). A number refs the shared `components.responses` entry; an
87
+ * object inlines a bespoke response.
88
+ */
89
+ readonly errors?: readonly V1OpError[];
90
+ /** Inline example for the OpenAPI `requestBody`. */
91
+ readonly requestExample?: unknown;
92
+ /** Inline example for the success-response body. */
93
+ readonly responseExample?: unknown;
94
+ /** For `binary` responses — the media types served (+ optional example/description). */
95
+ readonly binary?: {
96
+ readonly contentTypes: readonly string[];
97
+ readonly description?: string;
98
+ readonly example?: string;
99
+ };
100
+ /** A `created` op that is idempotent also documents a `200` (already-existed). */
101
+ readonly idempotent?: boolean;
102
+ /** Marks the operation `deprecated` in the spec. */
103
+ readonly deprecated?: boolean;
104
+ }
105
+ interface V1CommandSpec extends V1CommandDocFields {
106
+ /** Resource group, e.g. "drive". */
107
+ readonly resource: string;
108
+ /** Verb in the `idapt` grammar, e.g. "read", "list". */
109
+ readonly verb: string;
110
+ /** `${resource} ${verb}` — the canonical command string. */
111
+ readonly command: string;
112
+ readonly method: HttpMethod;
113
+ /** Path relative to `/api/v1`, with `:param` placeholders, e.g. `/drive/files/:id`. */
114
+ readonly path: string;
115
+ /** Request fields that fill `:param` placeholders (resourceIds). */
116
+ readonly pathParams: readonly string[];
117
+ /** Where the remaining request fields are carried. */
118
+ readonly argLocation: ArgLocation;
119
+ /** Request schema (snake_case). For multipart, describes the form fields. */
120
+ readonly request: z.ZodTypeAny;
121
+ /** Response `data` schema (snake_case), or null for `binary`. */
122
+ readonly response: z.ZodTypeAny | null;
123
+ readonly responseKind: ResponseKind;
124
+ /** Authorization gate — mirrors `defineRouteAuth`: canonical action id. */
125
+ readonly permission: readonly [string, string];
126
+ /** Appears in the curated public OpenAPI spec. */
127
+ readonly public: boolean;
128
+ /** Exposed as an agent tool (dispatcher / MCP / `idapt`). */
129
+ readonly agentExposed: boolean;
130
+ /** Long-running → goes through the async-operation layer. */
131
+ readonly async: boolean;
132
+ /** Tier-1 one-line decision hint (≤200 chars). */
133
+ readonly summary: string;
134
+ }
135
+ /**
136
+ * Resource-level playbook (tier-2b instructions) — the cross-cutting "when /
137
+ * why / anti-patterns" prose that no schema can hold. Hand-authored per
138
+ * resource and surfaced verbatim by `idapt instructions <resource>`.
139
+ */
140
+ interface V1ResourcePlaybook {
141
+ readonly resource: string;
142
+ readonly instructions: string;
143
+ }
144
+
145
+ /**
146
+ * v1 contract registry — the aggregated single source consumed by routes, the
147
+ * SDK (types), the OpenAPI generator, and `@idapt/cli`.
148
+ *
149
+ * As resources are migrated, add their `*Commands` / `*Playbook` exports here.
150
+ */
151
+
152
+ /** The playbook for one resource, if authored. */
153
+ declare function getResourcePlaybook(resource: string): V1ResourcePlaybook | undefined;
154
+
155
+ /**
156
+ * The cli command catalog — derived entirely from the shared v1
157
+ * contracts. Nothing here is hand-maintained; adding an `agentExposed` command
158
+ * to `shared/api/v1/contracts` makes it appear in the CLI, the dispatcher, and
159
+ * MCP automatically.
160
+ */
161
+
162
+ /** Every command exposed as an agent tool. */
163
+ declare function listCommands(): readonly V1CommandSpec[];
164
+ /** Distinct resource names, sorted. */
165
+ declare function listResources(): string[];
166
+ /** Commands for one resource. */
167
+ declare function commandsForResource(resource: string): readonly V1CommandSpec[];
168
+ /** Exact command lookup, e.g. "drive read". */
169
+ declare function findCommand(command: string): V1CommandSpec | undefined;
170
+ /**
171
+ * Resolve the longest catalog command that prefixes the given path tokens.
172
+ * Returns the matched spec and the leftover tokens (positional path-param
173
+ * values). E.g. ["drive","read","file_abc"] → { spec: drive read, rest:
174
+ * ["file_abc"] }.
175
+ */
176
+ declare function resolveCommand(pathTokens: readonly string[]): {
177
+ spec: V1CommandSpec;
178
+ rest: string[];
179
+ } | undefined;
180
+
181
+ /**
182
+ * Output formatting — one mode-parameterized renderer shared by every consumer.
183
+ * The CLI uses `auto`/`table`/`json`; the in-chat dispatcher uses `llm`
184
+ * (truncated for the model's context); MCP uses `json`. Per-command table
185
+ * columns come from the spec, so this stays generic.
186
+ */
187
+
188
+ type RenderMode = "json" | "jsonl" | "quiet" | "table" | "llm";
189
+ declare function render(data: unknown, spec: V1CommandSpec, mode: RenderMode): string;
190
+ /** Resolve the effective mode for a TTY/pipe context (CLI `auto` default). */
191
+ declare function autoMode(isTty: boolean, requested?: RenderMode): RenderMode;
192
+
193
+ /**
194
+ * Transport seam. `execute()` builds a neutral {@link AgentToolsRequest} from a
195
+ * command spec and hands it to a transport. The default {@link createFetchTransport}
196
+ * is a thin bearer-auth fetch wrapper; in the in-chat worker / MCP it is given a
197
+ * transport that injects the minted agent-principal token, and tests inject a
198
+ * mock.
199
+ *
200
+ * This is the single seam where the `@idapt/sdk` low-level `request()` will be
201
+ * plugged in once the SDK exports it — so cli reuses the SDK's HTTP
202
+ * behaviour instead of duplicating it long-term.
203
+ */
204
+ interface AgentToolsRequest {
205
+ readonly method: "GET" | "POST" | "PATCH" | "DELETE";
206
+ /** Absolute-from-origin path, e.g. "/api/v1/drive/files/file_abc". */
207
+ readonly path: string;
208
+ readonly query?: Record<string, string | number | boolean | undefined>;
209
+ /** JSON body (mutually exclusive with `multipart`). */
210
+ readonly body?: Record<string, unknown>;
211
+ /** Multipart form fields (for uploads). */
212
+ readonly multipart?: Record<string, string | Blob>;
213
+ /** Expect raw bytes/stream rather than a JSON envelope. */
214
+ readonly expectBinary?: boolean;
215
+ readonly signal?: AbortSignal;
216
+ }
217
+ interface AgentToolsResponse {
218
+ readonly status: number;
219
+ /** Parsed JSON body, when the response was JSON. */
220
+ readonly json?: unknown;
221
+ /** Raw text body, when not JSON. */
222
+ readonly text?: string;
223
+ /** Binary body, when `expectBinary` was requested. */
224
+ readonly blob?: Blob;
225
+ }
226
+ interface AgentToolsTransport {
227
+ request(req: AgentToolsRequest): Promise<AgentToolsResponse>;
228
+ }
229
+ interface FetchTransportOptions {
230
+ /** Origin, e.g. "https://idapt.app" or "http://localhost:3000". */
231
+ readonly baseUrl: string;
232
+ /** Bearer token (user API key, or a minted agent-principal token). */
233
+ readonly token: string;
234
+ /** Custom fetch (tests / non-browser runtimes). */
235
+ readonly fetch?: typeof fetch;
236
+ /**
237
+ * User-Agent to advertise (e.g. `idapt-cli/1.2.0`). Lets the server classify
238
+ * the caller (route-policy + analytics) and is the JS-client twin of the Go
239
+ * daemon's `idapt-computer/<v>` UA. Optional so the in-chat dispatcher / MCP
240
+ * transports stay unaffected.
241
+ */
242
+ readonly userAgent?: string;
243
+ }
244
+ /** Default bearer-auth fetch transport. */
245
+ declare function createFetchTransport(opts: FetchTransportOptions): AgentToolsTransport;
246
+
247
+ /**
248
+ * Execute an `idapt <resource> <verb> …` command against the v1 API.
249
+ *
250
+ * Pure translation: resolve the command spec, bind path params, validate the
251
+ * remaining args against the contract's request schema, build the REST request
252
+ * per the spec's `argLocation`, call the transport, unwrap the envelope by
253
+ * `responseKind`, and render per the chosen mode. No per-verb code — the spec
254
+ * (from `shared/api/v1/contracts`) drives everything.
255
+ */
256
+
257
+ interface ExecuteOptions {
258
+ readonly transport: AgentToolsTransport;
259
+ /** Render mode for `rendered`. Default "json". The in-chat dispatcher uses "llm". */
260
+ readonly mode?: RenderMode;
261
+ readonly signal?: AbortSignal;
262
+ /**
263
+ * For `async` verbs: when true, return the operation handle immediately
264
+ * instead of polling to completion. Defaults to the command's `--background`
265
+ * flag.
266
+ */
267
+ readonly background?: boolean;
268
+ /** Poll cadence for async operations (default 1500ms). */
269
+ readonly pollIntervalMs?: number;
270
+ /** Max poll attempts before giving up and returning the handle (default 120). */
271
+ readonly maxPollAttempts?: number;
272
+ /** Injectable delay (tests pass a no-op); defaults to real setTimeout. */
273
+ readonly sleep?: (ms: number) => Promise<void>;
274
+ }
275
+ interface ExecuteResult {
276
+ readonly ok: boolean;
277
+ readonly command: string;
278
+ readonly status: number;
279
+ /** Unwrapped payload (single row, array, deleted marker, text, or blob). */
280
+ readonly data: unknown;
281
+ /** Pagination, for list responses. */
282
+ readonly pagination?: {
283
+ has_more: boolean;
284
+ next_cursor: string | null;
285
+ };
286
+ /** Output rendered per `mode`. */
287
+ readonly rendered: string;
288
+ /** Error message when `ok` is false. */
289
+ readonly error?: string;
290
+ /** Set when an async verb returned (or is still running on) an operation. */
291
+ readonly operationId?: string;
292
+ /** True when a `--background` async verb returned a still-running handle. */
293
+ readonly pending?: boolean;
294
+ }
295
+ declare function execute(cmd: string, opts: ExecuteOptions): Promise<ExecuteResult>;
296
+ /**
297
+ * Execute a command addressed by its `"<resource> <verb>"` name with an already
298
+ * parsed args object — the seam the in-chat dispatcher uses (it resolves the
299
+ * agent's command + path args itself, then hands the v1 command + args here),
300
+ * and a first-class entry for SDK/programmatic callers who don't want to build
301
+ * a CLI string. Same translation as {@link execute}, minus the string parse.
302
+ */
303
+ declare function executeCommand(commandName: string, args: Record<string, unknown>, opts: ExecuteOptions): Promise<ExecuteResult>;
304
+
305
+ /**
306
+ * Contract help (tier-2a) and playbook (tier-2b) rendering.
307
+ *
308
+ * `help` is derived from the command spec (args/types/required, method, path).
309
+ * `instructions` is the hand-authored resource playbook from the contracts —
310
+ * the cross-cutting "when / why" prose no schema can hold.
311
+ */
312
+
313
+ declare function renderHelp(spec: V1CommandSpec): string;
314
+ declare function renderInstructions(resource: string): string;
315
+
316
+ /**
317
+ * Parse a model/CLI-facing `idapt <resource> <verb> …` command string into a
318
+ * neutral invocation. Reuses the shell-injection-safe tokenizer from
319
+ * `@idapt/api-contracts/idapt-command` (shared with the in-app dispatcher and the Go
320
+ * CLI), then normalizes flag keys to the **snake_case** the v1 contracts use.
321
+ *
322
+ * Positionals after the verb are preserved (command resolution binds them to a
323
+ * spec's path params), so both `idapt drive read file_abc` and
324
+ * `idapt drive read --id file_abc` work.
325
+ */
326
+ interface ParsedInvocation {
327
+ /** Path tokens after `idapt`, e.g. ["drive","read","file_abc"]. */
328
+ readonly pathTokens: readonly string[];
329
+ /** Flag + merged `--json` args, keys normalized to snake_case. */
330
+ readonly args: Record<string, unknown>;
331
+ readonly background: boolean;
332
+ readonly timeoutSeconds?: number;
333
+ readonly help: boolean;
334
+ readonly instructions: boolean;
335
+ }
336
+ type ParseResult = {
337
+ ok: true;
338
+ invocation: ParsedInvocation;
339
+ } | {
340
+ ok: false;
341
+ message: string;
342
+ };
343
+ /** kebab-case and camelCase → snake_case (the v1 wire convention). */
344
+ declare function toSnakeKey(key: string): string;
345
+ declare function parseInvocation(cmd: string): ParseResult;
346
+
347
+ /**
348
+ * Agent-grammar → v1-command reconciliation — SHARED by every consumer of the
349
+ * `idapt <resource> <verb>` grammar (the in-chat dispatcher and the MCP server).
350
+ *
351
+ * Callers emit the agent/skill grammar (e.g. `"drive read"`, `"computer
352
+ * tmux-run"`). Most paths already match a v1 catalog command 1:1; the rest are
353
+ * reconciled here (verb renames, op-collapsed verbs). `reconcileToV1` maps the
354
+ * verb; `mapArgsToV1` shapes the already-resolved args onto the v1 contract
355
+ * (snake_case, op injection, path-param filling). Both are pure functions with no
356
+ * runtime dependency on any product package, which is exactly why they live in
357
+ * the shared client: the dispatcher (worker) and MCP (gateway) reconcile
358
+ * identically without importing each other.
359
+ *
360
+ * (Carve-out detection — which verbs run locally vs over REST — is consumer-
361
+ * specific and stays with each consumer.)
362
+ */
363
+ /**
364
+ * Agent command `path` → v1 catalog command, where they differ. Identity is
365
+ * assumed when a path is absent here (e.g. `"drive read"` → `"drive read"`).
366
+ * Op-collapsed verbs (tmux-*, file-*) point at the single v1 verb that takes the
367
+ * operation as an argument — `mapArgsToV1` injects the op.
368
+ */
369
+ declare const VERB_OVERRIDES: Record<string, string>;
370
+ /** Resolve an agent command path to its v1 catalog command (or itself). */
371
+ declare function reconcileToV1(path: string): string;
372
+ /**
373
+ * Shape already-path-resolved args into the v1 command's args: snake_case the
374
+ * keys, inject the collapsed-verb `op`, and fill each of the v1 spec's path
375
+ * params from an alias key. `executeCommand`'s `bindPath` then consumes the
376
+ * path-param values + validates the rest against the contract.
377
+ */
378
+ declare function mapArgsToV1(path: string, pathParams: readonly string[], args: Record<string, unknown>): Record<string, unknown>;
379
+
380
+ export { type AgentToolsRequest, type AgentToolsResponse, type AgentToolsTransport, type ExecuteOptions, type ExecuteResult, type FetchTransportOptions, type ParsedInvocation, type RenderMode, type V1CommandSpec, VERB_OVERRIDES, autoMode, commandsForResource, createFetchTransport, execute, executeCommand, findCommand, getResourcePlaybook, listCommands, listResources, mapArgsToV1, parseInvocation, reconcileToV1, render, renderHelp, renderInstructions, resolveCommand, toSnakeKey };