@martini-film/cli 0.1.0 → 0.2.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/README.md +41 -16
- package/dist/api.js +11 -14
- package/dist/api.js.map +1 -1
- package/dist/approve.js +83 -0
- package/dist/approve.js.map +1 -0
- package/dist/args.js +3 -7
- package/dist/args.js.map +1 -1
- package/dist/cli.js +124 -48
- package/dist/cli.js.map +1 -1
- package/dist/commands/assets.js +100 -27
- package/dist/commands/assets.js.map +1 -1
- package/dist/commands/context.js +42 -8
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/files.js +23 -15
- package/dist/commands/files.js.map +1 -1
- package/dist/commands/login.js +145 -27
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/tools.js +71 -10
- package/dist/commands/tools.js.map +1 -1
- package/dist/config.js +5 -19
- package/dist/config.js.map +1 -1
- package/dist/generated/manifest.json +10118 -792
- package/dist/help.js +188 -17
- package/dist/help.js.map +1 -1
- package/dist/host.js +0 -6
- package/dist/host.js.map +1 -1
- package/dist/manifest.js +4 -9
- package/dist/manifest.js.map +1 -1
- package/dist/output.js +107 -19
- package/dist/output.js.map +1 -1
- package/dist/schema.js +70 -29
- package/dist/schema.js.map +1 -1
- package/dist/wait.js +62 -23
- package/dist/wait.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ in. One HTTPS request per call, a static key, no MCP session.
|
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
9
|
npx @martini-film/cli --help # or: pnpm add -g @martini-film/cli → martini
|
|
10
|
-
martini login # pair this device through the browser (90-day key)
|
|
10
|
+
martini login # pair this device through the browser (90-day key); --no-wait to return at once, rerun to collect
|
|
11
11
|
martini get_board_overview --project <id> # any MCP tool, flags from its arguments
|
|
12
12
|
martini get_asset --asset-id <id> --fields asset.status,asset.imageUrl
|
|
13
13
|
martini upload ./ref.png ./take.mp4 --canvas <id>
|
|
@@ -19,16 +19,20 @@ martini probe <path|asset-id>… # duration, resolution, fps, c
|
|
|
19
19
|
|
|
20
20
|
## How commands are generated
|
|
21
21
|
|
|
22
|
-
`martini <tool>` exists for every entry of the tool manifest
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
object
|
|
27
|
-
|
|
22
|
+
`martini <tool>` exists for every entry of the tool manifest the build carries — the same list the Martini
|
|
23
|
+
MCP server publishes; `martini tools` says when the server has a newer one. Flags are derived from each tool's JSON Schema: `assetId` becomes `--asset-id`, arrays repeat the flag
|
|
24
|
+
(or take a JSON list), objects take JSON, a `string | null` argument takes the literal `null` to clear it, a
|
|
25
|
+
`"start" | "end" | {…}` union takes the bare word, and `--args '{…}'` / `--args @args.json` passes the whole
|
|
26
|
+
arguments object (`--json` is an alias, except on a tool whose own argument is named `json`) — so a plan a script
|
|
27
|
+
computed is submitted without retyping it. `--no-context` sends only what you typed, for tools that refuse a
|
|
28
|
+
`projectId` next to another selector. `martini <tool> --help` prints the tool's own description, every flag with
|
|
29
|
+
its type and whether it is required, the result fields when the tool declares them (as `--fields` paths), and one
|
|
30
|
+
example.
|
|
28
31
|
|
|
29
32
|
`projectId`, `canvasId`, and `runId` are filled from context when a tool takes them and the caller did not say:
|
|
30
33
|
`--project` / `--canvas` / `--run`, else `MARTINI_PROJECT_ID` / `MARTINI_CANVAS_ID` / `MARTINI_RUN_ID`, else the
|
|
31
|
-
config file.
|
|
34
|
+
config file. A typed selector that replaces one of them keeps it out (`get_board_overview --project-query`,
|
|
35
|
+
`run_workflow --workflow-id`, which runs a saved workflow on a canvas of its own).
|
|
32
36
|
|
|
33
37
|
## Credentials and context
|
|
34
38
|
|
|
@@ -37,7 +41,10 @@ Most specific first:
|
|
|
37
41
|
1. Flags: `--api-key`, `--api-url`, `--profile`.
|
|
38
42
|
2. Environment: `MARTINI_API_KEY`, `MARTINI_API_URL`, `MARTINI_PROFILE`, `MARTINI_CONFIG_FILE`.
|
|
39
43
|
3. The nearest `.martini/config.json` above the working directory — what a workflow run's sandbox has.
|
|
40
|
-
4. `~/.config/martini/config.json` — the profiles `martini login` saves, one per organization, with a default.
|
|
44
|
+
4. `~/.config/martini/config.json` — the profiles `martini login` saves, one per organization (named after it, `test-team`), with a default.
|
|
45
|
+
|
|
46
|
+
`--project-id`, `--canvas-id`, and `--run-id` (the tools' spelling) are accepted wherever `--project`, `--canvas`,
|
|
47
|
+
and `--run` are.
|
|
41
48
|
|
|
42
49
|
A workflow run's sandbox therefore needs nothing but its working directory; a laptop needs one `martini login`;
|
|
43
50
|
a script sets `MARTINI_API_KEY`. A raw Martini API key (`mtn_live_…`) always works.
|
|
@@ -45,7 +52,10 @@ a script sets `MARTINI_API_KEY`. A raw Martini API key (`mtn_live_…`) always w
|
|
|
45
52
|
## Output and exit codes
|
|
46
53
|
|
|
47
54
|
Results are JSON on stdout — compact when piped, indented on a terminal. `--fields a,b.c` keeps only those
|
|
48
|
-
dotted paths, applied on the server so a large listing never crosses the wire
|
|
55
|
+
dotted paths, applied on the server so a large listing never crosses the wire; a `*` segment steps into every value of
|
|
56
|
+
an id-keyed map (`assets.*.status`) and is applied in the CLI. With `--wait` (including default
|
|
57
|
+
waits), the CLI keeps the submission identifiers for polling and applies `--fields` to the final result.
|
|
58
|
+
`--out <path>` writes the result
|
|
49
59
|
(or, with `--raw`, the media bytes of tools like `view_asset`) to a file and prints one summary line. `--quiet`
|
|
50
60
|
prints nothing on success. Errors are one JSON object on stderr: `{ "error": { "code", "message", "hint", "tool",
|
|
51
61
|
"exitCode" } }`, with the tool's own MCP error code (`ASSET_NOT_READY`, `NODE_NOT_FOUND`, …).
|
|
@@ -63,14 +73,30 @@ prints nothing on success. Errors are one JSON object on stderr: `{ "error": { "
|
|
|
63
73
|
|
|
64
74
|
Generation and rendering are asynchronous. `--wait` blocks until the produced assets (or the render) settle and
|
|
65
75
|
adds `waited` to the result; it is on by default for single-item generation and rendering tools and off for batch
|
|
66
|
-
tools, where you fan out and then `martini wait <ids…>` once.
|
|
67
|
-
|
|
76
|
+
tools, where you fan out and then `martini wait <ids…>` once. `waited` has one shape whatever was waited on:
|
|
77
|
+
`{ kind, status, assets? | job? | run? | delivery? }` — `status` is the terminal status (for assets, `failed` when
|
|
78
|
+
any failed) and the settled records sit under the key that names them; `martini wait` prints the same object.
|
|
79
|
+
`--timeout <seconds>` (default `MARTINI_WAIT_TIMEOUT_SECONDS` when set, else 50; a malformed value is a usage error) ends the wait with exit 4: the
|
|
80
|
+
result the command produced is still printed on stdout, with `pending` (and `settled`) ids, and the error on
|
|
81
|
+
stderr names the `martini wait …` command that resumes it. Size `--timeout` to what your shell step allows — a
|
|
82
|
+
video generation takes minutes.
|
|
83
|
+
|
|
84
|
+
## Approving a quote
|
|
85
|
+
|
|
86
|
+
`generate_nodes`, `upscale_assets`, and `enhance_draft` answer a quote first (`approvalNeeded: true`, `planHash`,
|
|
87
|
+
the estimated olives) and start nothing until called again with that `planHash` (plus `batchRequestId` for
|
|
88
|
+
upscales) and the approval argument. `--approve --max-olives <n>` does both in one command: it takes the quote,
|
|
89
|
+
refuses when the estimated total is over the cap (`PLAN_OVER_BUDGET`) or has no number (`PLAN_UNPRICED`), with
|
|
90
|
+
the quote on stdout and nothing started, and otherwise re-calls the tool with exactly what it asked for; the
|
|
91
|
+
result carries `approvedPlan`. The cap is required: an approval always states how much it is for. A tool with an
|
|
92
|
+
approval argument but no quote step (`create_nodes_and_generate`) is only attested by `--approve` and cannot be
|
|
93
|
+
capped — for a capped batch, `create_nodes` then `generate_nodes --approve --max-olives <n>`.
|
|
68
94
|
|
|
69
95
|
## Compound commands
|
|
70
96
|
|
|
71
97
|
| Command | Does |
|
|
72
98
|
| ------------------------------------ | ----------------------------------------------------------------------------------------------------- |
|
|
73
|
-
| `login`, `logout`, `whoami` | device-code pairing, forget a profile, who the CLI acts as
|
|
99
|
+
| `login`, `logout`, `whoami` | device-code pairing (a started pairing is resumed by running `login` again; `--no-wait` returns at once), forget a profile, who the CLI acts as |
|
|
74
100
|
| `tools`, `models` | the manifest this build knows (and whether the server has a newer one); the cached model catalog |
|
|
75
101
|
| `upload <paths…>` | prepare → PUT → complete from local files, then wait for processing |
|
|
76
102
|
| `download <ids…>` | presigned URL → bytes → file |
|
|
@@ -89,8 +115,7 @@ executing answers `RUN_NOT_ACTIVE` (exit 4).
|
|
|
89
115
|
## Single-file binary
|
|
90
116
|
|
|
91
117
|
`pnpm run build:binary` compiles `dist/bin.js` into `dist/martini-linux-x64` with Bun for environments without
|
|
92
|
-
Node (the agent-environment sandbox). Bun is only needed on the build machine. Every `cli-
|
|
93
|
-
|
|
94
|
-
(`.github/workflows/cli-release.yml`); the npm package is published by hand (`AGENTS.md` → Releasing).
|
|
118
|
+
Node (the agent-environment sandbox). Bun is only needed on the build machine. Every release publishes it at `https://downloads.martini.film/cli-releases/<version>/martini-linux-x64`;
|
|
119
|
+
the npm package is published separately.
|
|
95
120
|
|
|
96
121
|
Docs: <https://www.martini.film/docs/cli>.
|
package/dist/api.js
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The wire: one HTTPS request per call against the API-key surface —
|
|
3
|
-
* `POST /v1/tools/{name}` for every tool, `/v1/tools` for the manifest,
|
|
4
|
-
* `/v1/me`, and the two login routes. Static key, no session. The CLI names
|
|
5
|
-
* itself with `X-Martini-Client` and a User-Agent that also carries the host
|
|
6
|
-
* agent it runs inside, so analytics can tell "the CLI under Claude Code" from
|
|
7
|
-
* "Claude Code over MCP".
|
|
8
|
-
*/
|
|
9
1
|
import { detectHostAgent } from './host.js';
|
|
10
|
-
export const CLI_VERSION = '0.
|
|
2
|
+
export const CLI_VERSION = '0.2.0';
|
|
11
3
|
export class CliApiError extends Error {
|
|
12
4
|
code;
|
|
13
5
|
status;
|
|
14
6
|
tool;
|
|
7
|
+
result;
|
|
15
8
|
constructor(message, details) {
|
|
16
9
|
super(message);
|
|
17
10
|
this.name = 'CliApiError';
|
|
@@ -35,6 +28,10 @@ export function createApiClient(config, options = {}) {
|
|
|
35
28
|
};
|
|
36
29
|
if (config.apiKey)
|
|
37
30
|
base.Authorization = `Bearer ${config.apiKey}`;
|
|
31
|
+
if (config.workerId)
|
|
32
|
+
base['X-Martini-Worker'] = config.workerId;
|
|
33
|
+
if (config.executionId)
|
|
34
|
+
base['X-Martini-Execution'] = config.executionId;
|
|
38
35
|
return base;
|
|
39
36
|
}
|
|
40
37
|
async function readError(response, tool) {
|
|
@@ -44,7 +41,11 @@ export function createApiClient(config, options = {}) {
|
|
|
44
41
|
body = JSON.parse(text);
|
|
45
42
|
}
|
|
46
43
|
catch {
|
|
47
|
-
|
|
44
|
+
const html = /^\s*<(!doctype|html)/i.test(text);
|
|
45
|
+
const title = html ? /<title>([^<]*)<\/title>/i.exec(text)?.[1]?.trim() : undefined;
|
|
46
|
+
body = {
|
|
47
|
+
error: html ? `The API did not answer (HTTP ${response.status}${title ? `: ${title}` : ''}). Try again.` : text,
|
|
48
|
+
};
|
|
48
49
|
}
|
|
49
50
|
const message = typeof body.error === 'string' && body.error ? body.error : `Request failed (${response.status})`;
|
|
50
51
|
return new CliApiError(message, {
|
|
@@ -77,7 +78,6 @@ export function createApiClient(config, options = {}) {
|
|
|
77
78
|
}
|
|
78
79
|
return {
|
|
79
80
|
config,
|
|
80
|
-
/** `POST /v1/tools/{name}` → the tool's JSON result. */
|
|
81
81
|
async callTool(name, args, options = {}) {
|
|
82
82
|
requireKey();
|
|
83
83
|
const query = new URLSearchParams();
|
|
@@ -91,7 +91,6 @@ export function createApiClient(config, options = {}) {
|
|
|
91
91
|
...(options.signal ? { signal: options.signal } : {}),
|
|
92
92
|
});
|
|
93
93
|
},
|
|
94
|
-
/** `POST /v1/tools/{name}?format=raw` → the media bytes of a single-media result. */
|
|
95
94
|
async callToolRaw(name, args) {
|
|
96
95
|
requireKey();
|
|
97
96
|
const response = await fetchImpl(`${config.apiUrl}/v1/tools/${encodeURIComponent(name)}?format=raw`, {
|
|
@@ -131,7 +130,6 @@ export function createApiClient(config, options = {}) {
|
|
|
131
130
|
async pollLogin(pollToken) {
|
|
132
131
|
return requestJson(`/v1/cli/login/${encodeURIComponent(pollToken)}`, { method: 'GET' });
|
|
133
132
|
},
|
|
134
|
-
/** A plain GET of an absolute URL (presigned downloads), with no Martini headers. */
|
|
135
133
|
async fetchBytes(url) {
|
|
136
134
|
const response = await fetchImpl(url);
|
|
137
135
|
if (!response.ok) {
|
|
@@ -145,7 +143,6 @@ export function createApiClient(config, options = {}) {
|
|
|
145
143
|
contentType: response.headers.get('content-type') ?? 'application/octet-stream',
|
|
146
144
|
};
|
|
147
145
|
},
|
|
148
|
-
/** A plain PUT of bytes to a presigned URL with the exact headers the server signed. */
|
|
149
146
|
async putBytes(url, bytes, putHeaders) {
|
|
150
147
|
const response = await fetchImpl(url, { method: 'PUT', headers: putHeaders, body: bytes });
|
|
151
148
|
if (!response.ok) {
|
package/dist/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/approve.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { CliApiError } from './api.js';
|
|
2
|
+
import { ArgumentError, kebab } from './schema.js';
|
|
3
|
+
export function approvalSupport(tool) {
|
|
4
|
+
const properties = tool.inputSchema.properties ?? {};
|
|
5
|
+
const argument = 'batchApproval' in properties ? 'batchApproval' : 'approval' in properties ? 'approval' : null;
|
|
6
|
+
if (!argument)
|
|
7
|
+
return null;
|
|
8
|
+
return {
|
|
9
|
+
kind: 'planHash' in properties ? 'quote' : 'attest',
|
|
10
|
+
argument,
|
|
11
|
+
echoesBatchRequestId: 'batchRequestId' in properties,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function isQuote(result) {
|
|
15
|
+
if (!result || typeof result !== 'object' || Array.isArray(result))
|
|
16
|
+
return false;
|
|
17
|
+
const record = result;
|
|
18
|
+
return record.approvalNeeded === true || record.generated === false;
|
|
19
|
+
}
|
|
20
|
+
function quotedOlives(quote) {
|
|
21
|
+
const total = quote.totalEstimatedOlives ?? quote.estimatedOlives;
|
|
22
|
+
return typeof total === 'number' && Number.isFinite(total) ? total : null;
|
|
23
|
+
}
|
|
24
|
+
function itemCount(quote, args) {
|
|
25
|
+
if (Array.isArray(quote?.items))
|
|
26
|
+
return quote.items.length;
|
|
27
|
+
for (const key of ['nodeIds', 'nodes'])
|
|
28
|
+
if (Array.isArray(args[key]))
|
|
29
|
+
return args[key].length;
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
export async function runWithApproval(tool, client, args, support, options) {
|
|
33
|
+
const { maxOlives } = options;
|
|
34
|
+
if (support.kind === 'attest') {
|
|
35
|
+
if (maxOlives !== undefined) {
|
|
36
|
+
throw new ArgumentError(`${tool.name} has no quote step, so --max-olives cannot be enforced on it. For a capped batch, create the drafts (create_nodes) and run generate_nodes --approve --max-olives ${maxOlives}.`);
|
|
37
|
+
}
|
|
38
|
+
const count = itemCount(undefined, args);
|
|
39
|
+
const summary = `Approved by martini CLI --approve: ${count} item(s), no server quote.`;
|
|
40
|
+
const approval = support.argument === 'batchApproval' ? { approved: true, summary, itemCount: count } : { approved: true, summary };
|
|
41
|
+
return client.callTool(tool.name, { ...args, [support.argument]: approval });
|
|
42
|
+
}
|
|
43
|
+
if (maxOlives === undefined) {
|
|
44
|
+
throw new ArgumentError(`--approve needs --max-olives <olives> on ${tool.name}: the quote is approved only when its estimated total is at or under that cap.`);
|
|
45
|
+
}
|
|
46
|
+
const quote = await client.callTool(tool.name, args);
|
|
47
|
+
if (!isQuote(quote))
|
|
48
|
+
return quote;
|
|
49
|
+
const total = quotedOlives(quote);
|
|
50
|
+
if (total === null) {
|
|
51
|
+
const error = new CliApiError(`${tool.name} quoted no total (an item has no server-side estimate), so --max-olives ${maxOlives} cannot be checked; nothing was started. Approve by hand: re-run with --plan-hash and --${kebab(support.argument)}.`, { code: 'PLAN_UNPRICED', status: 0, tool: tool.name });
|
|
52
|
+
error.result = quote;
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
if (total > maxOlives) {
|
|
56
|
+
const error = new CliApiError(`${tool.name} quoted ${total} olives, over the --max-olives ${maxOlives} cap; nothing was started.`, { code: 'PLAN_OVER_BUDGET', status: 0, tool: tool.name });
|
|
57
|
+
error.result = quote;
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
const count = itemCount(quote, args);
|
|
61
|
+
const summary = `Approved by martini CLI --approve: ${count} item(s), ${total} olives estimated, cap ${maxOlives}.`;
|
|
62
|
+
const approval = support.argument === 'batchApproval'
|
|
63
|
+
? { approved: true, summary, itemCount: count, estimatedTotalCost: String(total) }
|
|
64
|
+
: { approved: true, summary, estimatedCost: String(total) };
|
|
65
|
+
const batchRequestId = support.echoesBatchRequestId && typeof quote.batchRequestId === 'string' ? quote.batchRequestId : undefined;
|
|
66
|
+
const result = await client.callTool(tool.name, {
|
|
67
|
+
...args,
|
|
68
|
+
planHash: quote.planHash,
|
|
69
|
+
...(batchRequestId ? { batchRequestId } : {}),
|
|
70
|
+
[support.argument]: approval,
|
|
71
|
+
});
|
|
72
|
+
const approvedPlan = {
|
|
73
|
+
planHash: quote.planHash,
|
|
74
|
+
...(batchRequestId ? { batchRequestId } : {}),
|
|
75
|
+
itemCount: count,
|
|
76
|
+
estimatedOlives: total,
|
|
77
|
+
maxOlives,
|
|
78
|
+
};
|
|
79
|
+
return result && typeof result === 'object' && !Array.isArray(result)
|
|
80
|
+
? { ...result, approvedPlan }
|
|
81
|
+
: { result, approvedPlan };
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=approve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approve.js","sourceRoot":"","sources":["../src/approve.ts"],"names":[],"mappings":"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"}
|
package/dist/args.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Argument parsing without a framework: `martini <command> [positionals] [--flag value | --flag=value | --bool]`.
|
|
3
|
-
* Repeated flags collect into arrays. Everything after `--` is positional.
|
|
4
|
-
*/
|
|
5
|
-
export function parseArgs(argv) {
|
|
1
|
+
export function parseArgs(argv, isBoolean = () => false) {
|
|
6
2
|
const flags = new Map();
|
|
7
3
|
const positionals = [];
|
|
8
4
|
let command;
|
|
@@ -43,7 +39,8 @@ export function parseArgs(argv) {
|
|
|
43
39
|
continue;
|
|
44
40
|
}
|
|
45
41
|
const next = argv[i + 1];
|
|
46
|
-
|
|
42
|
+
const boolean = isBoolean(body, command);
|
|
43
|
+
if (next !== undefined && !next.startsWith('--') && (!boolean || /^(true|false|1|0|yes|no)$/.test(next))) {
|
|
47
44
|
push(body, next);
|
|
48
45
|
i += 1;
|
|
49
46
|
}
|
|
@@ -59,7 +56,6 @@ export function parseArgs(argv) {
|
|
|
59
56
|
}
|
|
60
57
|
return { command, positionals, flags };
|
|
61
58
|
}
|
|
62
|
-
/** One value for a flag, or undefined; an array flag yields its last value. */
|
|
63
59
|
export function flagString(flags, name) {
|
|
64
60
|
const value = flags.get(name);
|
|
65
61
|
if (value === undefined || value === true)
|
package/dist/args.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -1,22 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* single tool does) or any tool from the manifest by its MCP name — the same
|
|
4
|
-
* vocabulary agents already know from the MCP, so nothing has to be translated
|
|
5
|
-
* (docs/plans/agent-cli.md, decision 18).
|
|
6
|
-
*/
|
|
7
|
-
import { createApiClient } from './api.js';
|
|
1
|
+
import { CliApiError, createApiClient } from './api.js';
|
|
2
|
+
import { approvalSupport, runWithApproval } from './approve.js';
|
|
8
3
|
import { flagBoolean, flagString, parseArgs } from './args.js';
|
|
9
4
|
import { download, upload, wait } from './commands/assets.js';
|
|
10
|
-
import { selectedFields, timeoutMs, wantsWait } from './commands/context.js';
|
|
5
|
+
import { assertKnownFlags, compoundBooleanFlags, CONTEXT_FLAG_ALIASES, needsLocalProjection, selectedFields, timeoutMs, wantsWait, } from './commands/context.js';
|
|
11
6
|
import { files } from './commands/files.js';
|
|
12
7
|
import { login, logout, whoami } from './commands/login.js';
|
|
13
8
|
import { init, models, probe, tools, version } from './commands/tools.js';
|
|
14
9
|
import { resolveConfig } from './config.js';
|
|
15
|
-
import { renderHelp, renderToolHelp } from './help.js';
|
|
10
|
+
import { renderCompoundHelp, renderHelp, renderToolHelp } from './help.js';
|
|
16
11
|
import { findTool, loadManifest } from './manifest.js';
|
|
17
|
-
import { EXIT, printBytes, printError, printResult } from './output.js';
|
|
18
|
-
import { ArgumentError, buildArguments } from './schema.js';
|
|
19
|
-
import { producedAssetIds, waitForAssets, waitForDelivery } from './wait.js';
|
|
12
|
+
import { EXIT, printBytes, printError, printResult, selectFields, warnNoFieldsMatched, } from './output.js';
|
|
13
|
+
import { ArgumentError, buildArguments, flagSpecs } from './schema.js';
|
|
14
|
+
import { assetsOutcome, handleOutcome, producedAssetIds, waitForAssets, waitForDelivery, waitForJob, waitForRun, WaitTimeoutError, } from './wait.js';
|
|
20
15
|
export const COMPOUND_COMMANDS = [
|
|
21
16
|
login,
|
|
22
17
|
logout,
|
|
@@ -31,7 +26,6 @@ export const COMPOUND_COMMANDS = [
|
|
|
31
26
|
init,
|
|
32
27
|
version,
|
|
33
28
|
];
|
|
34
|
-
/** Flags the dispatcher owns; a tool never sees them as arguments. */
|
|
35
29
|
export const GLOBAL_FLAG_NAMES = new Set([
|
|
36
30
|
'help',
|
|
37
31
|
'version',
|
|
@@ -47,8 +41,15 @@ export const GLOBAL_FLAG_NAMES = new Set([
|
|
|
47
41
|
'api-key',
|
|
48
42
|
'api-url',
|
|
49
43
|
'profile',
|
|
44
|
+
'context',
|
|
45
|
+
'approve',
|
|
46
|
+
'max-olives',
|
|
47
|
+
]);
|
|
48
|
+
const TOOL_ONLY_GLOBAL_FLAGS = new Set(['approve', 'max-olives']);
|
|
49
|
+
const GLOBAL_FLAGS_FOR_COMPOUNDS = new Set([
|
|
50
|
+
...[...GLOBAL_FLAG_NAMES].filter(flag => !TOOL_ONLY_GLOBAL_FLAGS.has(flag)),
|
|
51
|
+
...CONTEXT_FLAG_ALIASES,
|
|
50
52
|
]);
|
|
51
|
-
/** Single-item generation and rendering block by default; batches fan out and `martini wait` later (decision 5). */
|
|
52
53
|
const WAIT_BY_DEFAULT = new Set([
|
|
53
54
|
'create_node_and_generate',
|
|
54
55
|
'generate',
|
|
@@ -57,7 +58,7 @@ const WAIT_BY_DEFAULT = new Set([
|
|
|
57
58
|
'upscale_assets',
|
|
58
59
|
'render_blender_take',
|
|
59
60
|
]);
|
|
60
|
-
const
|
|
61
|
+
const BOOLEAN_GLOBAL_FLAGS = new Set(['help', 'version', 'wait', 'quiet', 'raw', 'context', 'approve']);
|
|
61
62
|
export async function main(options) {
|
|
62
63
|
const env = options.env ?? process.env;
|
|
63
64
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -66,36 +67,47 @@ export async function main(options) {
|
|
|
66
67
|
...(options.stderr ? { stderr: options.stderr } : {}),
|
|
67
68
|
...(options.isTTY !== undefined ? { isTTY: options.isTTY } : {}),
|
|
68
69
|
};
|
|
69
|
-
const parsed = parseArgs(options.argv);
|
|
70
70
|
const manifest = options.manifest ?? loadManifest();
|
|
71
|
+
const parsed = parseArgs(options.argv, (flag, command) => {
|
|
72
|
+
if (BOOLEAN_GLOBAL_FLAGS.has(flag))
|
|
73
|
+
return true;
|
|
74
|
+
const compound = COMPOUND_COMMANDS.find(candidate => candidate.name === command);
|
|
75
|
+
if (compound)
|
|
76
|
+
return compoundBooleanFlags(compound).includes(flag);
|
|
77
|
+
const tool = command ? findTool(command, manifest) : undefined;
|
|
78
|
+
return tool ? flagSpecs(tool).some(spec => spec.flag === flag && spec.kind === 'boolean') : false;
|
|
79
|
+
});
|
|
71
80
|
try {
|
|
81
|
+
if (flagBoolean(parsed.flags, 'version') && parsed.command === undefined)
|
|
82
|
+
parsed.command = 'version';
|
|
72
83
|
if (parsed.command === undefined ||
|
|
73
84
|
parsed.command === 'help' ||
|
|
74
85
|
(flagBoolean(parsed.flags, 'help') && !parsed.command)) {
|
|
75
86
|
printResult(renderHelp(manifest, COMPOUND_COMMANDS), output);
|
|
76
87
|
return EXIT.ok;
|
|
77
88
|
}
|
|
78
|
-
if (parsed.command === '--version' || (flagBoolean(parsed.flags, 'version') && parsed.command === undefined)) {
|
|
79
|
-
parsed.command = 'version';
|
|
80
|
-
}
|
|
81
89
|
const compound = COMPOUND_COMMANDS.find(command => command.name === parsed.command);
|
|
82
90
|
const tool = compound ? undefined : findTool(parsed.command, manifest);
|
|
83
91
|
if (!compound && !tool) {
|
|
84
92
|
throw new ArgumentError(`Unknown command "${parsed.command}". Run martini --help for the list.`);
|
|
85
93
|
}
|
|
86
94
|
if (flagBoolean(parsed.flags, 'help')) {
|
|
87
|
-
printResult(tool ? renderToolHelp(tool) :
|
|
95
|
+
printResult(tool ? renderToolHelp(tool) : renderCompoundHelp(compound), output);
|
|
88
96
|
return EXIT.ok;
|
|
89
97
|
}
|
|
98
|
+
if (compound)
|
|
99
|
+
assertKnownFlags(compound, parsed.flags, GLOBAL_FLAGS_FOR_COMPOUNDS);
|
|
90
100
|
const config = resolveConfig(parsed.flags, { env, cwd });
|
|
91
|
-
const
|
|
92
|
-
(resolved => createApiClient(resolved, { ...(options.fetch ? { fetch: options.fetch } : {}), env }))
|
|
101
|
+
const clientFor = options.createClient ??
|
|
102
|
+
(resolved => createApiClient(resolved, { ...(options.fetch ? { fetch: options.fetch } : {}), env }));
|
|
93
103
|
const out = flagString(parsed.flags, 'out');
|
|
94
104
|
if (out)
|
|
95
105
|
output.out = out;
|
|
106
|
+
output.inRun = Boolean(config.runId);
|
|
96
107
|
output.quiet = flagBoolean(parsed.flags, 'quiet') ?? false;
|
|
97
108
|
const context = {
|
|
98
|
-
client,
|
|
109
|
+
client: clientFor(config),
|
|
110
|
+
clientFor,
|
|
99
111
|
config,
|
|
100
112
|
manifest,
|
|
101
113
|
flags: parsed.flags,
|
|
@@ -106,58 +118,122 @@ export async function main(options) {
|
|
|
106
118
|
now: options.now ?? Date.now,
|
|
107
119
|
sleep: options.sleep ?? (ms => new Promise(resolve => setTimeout(resolve, ms))),
|
|
108
120
|
};
|
|
121
|
+
const select = selectedFields(context);
|
|
122
|
+
timeoutMs(context);
|
|
109
123
|
if (compound) {
|
|
110
124
|
const result = await compound.run(context);
|
|
111
|
-
|
|
125
|
+
const projected = select.length && result && typeof result === 'object' ? selectFields(result, select) : result;
|
|
126
|
+
warnNoFieldsMatched(projected, select, output);
|
|
127
|
+
printResult(projected, output);
|
|
112
128
|
return EXIT.ok;
|
|
113
129
|
}
|
|
114
|
-
|
|
130
|
+
const result = await runTool(tool, context, parsed.flags);
|
|
131
|
+
warnNoFieldsMatched(result, select, output, knownResultFields(tool));
|
|
132
|
+
printResult(result, output);
|
|
115
133
|
return EXIT.ok;
|
|
116
134
|
}
|
|
117
135
|
catch (error) {
|
|
136
|
+
if (error instanceof CliApiError && error.result !== undefined)
|
|
137
|
+
printResult(error.result, output);
|
|
118
138
|
return printError(error, output);
|
|
119
139
|
}
|
|
120
140
|
}
|
|
121
141
|
async function runTool(tool, context, flags) {
|
|
142
|
+
const properties = tool.inputSchema.properties ?? {};
|
|
143
|
+
const ignore = new Set(GLOBAL_FLAG_NAMES);
|
|
144
|
+
for (const alias of CONTEXT_FLAG_ALIASES) {
|
|
145
|
+
if (!(alias.replace(/-id$/, 'Id') in properties))
|
|
146
|
+
ignore.add(alias);
|
|
147
|
+
}
|
|
148
|
+
const useContext = flagBoolean(flags, 'context') !== false;
|
|
122
149
|
const args = buildArguments(tool, flags, {
|
|
123
|
-
ignore
|
|
124
|
-
context:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
150
|
+
ignore,
|
|
151
|
+
context: useContext
|
|
152
|
+
? {
|
|
153
|
+
...(context.config.projectId ? { projectId: context.config.projectId } : {}),
|
|
154
|
+
...(context.config.canvasId ? { canvasId: context.config.canvasId } : {}),
|
|
155
|
+
...(context.config.runId ? { runId: context.config.runId } : {}),
|
|
156
|
+
}
|
|
157
|
+
: {},
|
|
129
158
|
});
|
|
130
159
|
if (flagBoolean(flags, 'raw')) {
|
|
131
160
|
const raw = await context.client.callToolRaw(tool.name, args);
|
|
132
161
|
printBytes(raw.bytes, { ...context.output, contentType: raw.contentType });
|
|
133
|
-
// The bytes are the result; nothing else goes to the file or stdout.
|
|
134
162
|
delete context.output.out;
|
|
135
163
|
context.output.quiet = true;
|
|
136
164
|
return raw.meta;
|
|
137
165
|
}
|
|
138
166
|
const select = selectedFields(context);
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
167
|
+
const shouldWait = wantsWait(context, WAIT_BY_DEFAULT.has(tool.name));
|
|
168
|
+
const approve = approvalFlags(tool, flags);
|
|
169
|
+
const local = shouldWait || approve !== null || needsLocalProjection(select);
|
|
170
|
+
const result = approve
|
|
171
|
+
? await runWithApproval(tool, context.client, args, approve.support, approve.maxOlives === undefined ? {} : { maxOlives: approve.maxOlives })
|
|
172
|
+
: await context.client.callTool(tool.name, args, !local && select.length ? { select } : {});
|
|
173
|
+
if (!shouldWait)
|
|
174
|
+
return local ? selectFields(result, select) : result;
|
|
142
175
|
const projectId = typeof args.projectId === 'string' ? args.projectId : context.config.projectId;
|
|
143
|
-
|
|
144
|
-
return result;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
176
|
+
try {
|
|
177
|
+
return selectFields(await waitForToolResult(result, context, projectId), select);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (error instanceof CliApiError) {
|
|
181
|
+
const kept = selectFields(result, select);
|
|
182
|
+
const base = kept && typeof kept === 'object' && !Array.isArray(kept) ? kept : { result: kept };
|
|
183
|
+
error.result =
|
|
184
|
+
error instanceof WaitTimeoutError
|
|
185
|
+
? { ...base, pending: error.pending, ...(error.settled.length ? { settled: error.settled } : {}) }
|
|
186
|
+
: { ...base, waitError: error.code };
|
|
151
187
|
}
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async function waitForToolResult(result, context, projectId) {
|
|
192
|
+
if (!result || typeof result !== 'object')
|
|
152
193
|
return result;
|
|
194
|
+
const record = result;
|
|
195
|
+
if (record.approvalNeeded === true || record.generated === false)
|
|
196
|
+
return result;
|
|
197
|
+
const waitOptions = { timeoutMs: timeoutMs(context), now: context.now, sleep: context.sleep };
|
|
198
|
+
if (typeof record.runId === 'string') {
|
|
199
|
+
return { ...record, waited: handleOutcome('run', await waitForRun(context.client, record.runId, waitOptions)) };
|
|
200
|
+
}
|
|
201
|
+
if (typeof record.renderId === 'string' || typeof record.exportId === 'string') {
|
|
202
|
+
const ids = {
|
|
203
|
+
...(typeof record.renderId === 'string' ? { renderId: record.renderId } : {}),
|
|
204
|
+
...(typeof record.exportId === 'string' ? { exportId: record.exportId } : {}),
|
|
205
|
+
};
|
|
206
|
+
return { ...record, waited: handleOutcome('render', await waitForDelivery(context.client, ids, waitOptions)) };
|
|
207
|
+
}
|
|
208
|
+
if (typeof record.jobId === 'string') {
|
|
209
|
+
return { ...record, waited: handleOutcome('job', await waitForJob(context.client, record.jobId, waitOptions)) };
|
|
153
210
|
}
|
|
211
|
+
if (!projectId)
|
|
212
|
+
return result;
|
|
154
213
|
const ids = producedAssetIds(result);
|
|
155
214
|
if (ids.length === 0)
|
|
156
215
|
return result;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
216
|
+
return { ...record, waited: assetsOutcome(await waitForAssets(context.client, projectId, ids, waitOptions)) };
|
|
217
|
+
}
|
|
218
|
+
function approvalFlags(tool, flags) {
|
|
219
|
+
const approve = flagBoolean(flags, 'approve') === true;
|
|
220
|
+
const rawMax = flagString(flags, 'max-olives');
|
|
221
|
+
if (!approve && rawMax === undefined)
|
|
222
|
+
return null;
|
|
223
|
+
const support = approvalSupport(tool);
|
|
224
|
+
if (!support) {
|
|
225
|
+
throw new ArgumentError(`${tool.name} takes no approval; --approve and --max-olives apply to generate_nodes, upscale_assets, enhance_draft, and the tools with an approval argument (see --help).`);
|
|
226
|
+
}
|
|
227
|
+
if (!approve)
|
|
228
|
+
throw new ArgumentError('--max-olives only applies together with --approve.');
|
|
229
|
+
if (rawMax === undefined)
|
|
230
|
+
return { support };
|
|
231
|
+
const maxOlives = Number(rawMax);
|
|
232
|
+
if (!Number.isFinite(maxOlives) || maxOlives < 0)
|
|
233
|
+
throw new ArgumentError('--max-olives must be a number of olives.');
|
|
234
|
+
return { support, maxOlives };
|
|
235
|
+
}
|
|
236
|
+
function knownResultFields(tool) {
|
|
237
|
+
return Object.keys(tool.outputSchema?.properties ?? {});
|
|
162
238
|
}
|
|
163
239
|
//# sourceMappingURL=cli.js.map
|