@yolo-labs/yolo-cli 0.23.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 +57 -0
- package/dist/artifact-get.js +131 -0
- package/dist/artifact-list.js +133 -0
- package/dist/auth-context.js +60 -0
- package/dist/canonicalizer.js +208 -0
- package/dist/cli.js +1403 -0
- package/dist/context.js +58 -0
- package/dist/deploy-bundle.js +394 -0
- package/dist/deploy-cli.js +1341 -0
- package/dist/deploy-client.js +460 -0
- package/dist/deploy-config.js +301 -0
- package/dist/deploy-detect.js +498 -0
- package/dist/deploy-ship.js +389 -0
- package/dist/lockfile.js +203 -0
- package/dist/plan-diff.js +162 -0
- package/dist/plan-export.js +261 -0
- package/dist/plan-frontmatter.schema.json +184 -0
- package/dist/plan-get.js +244 -0
- package/dist/plan-import.js +420 -0
- package/dist/plan-list.js +153 -0
- package/dist/plan-open.js +100 -0
- package/dist/plan-state.js +228 -0
- package/dist/plan-validate.js +270 -0
- package/dist/revision.js +43 -0
- package/dist/run-get.js +130 -0
- package/dist/run-lifecycle.js +133 -0
- package/dist/run-list.js +148 -0
- package/dist/run-start.js +110 -0
- package/dist/run-transfer.js +128 -0
- package/dist/serve.js +227 -0
- package/dist/tileapp-developer.js +222 -0
- package/dist/tileapp-oci-assembler.js +591 -0
- package/dist/tileapp-personal.js +461 -0
- package/dist/tileapp-publisher.js +134 -0
- package/dist/tileapp-validator.js +239 -0
- package/dist/vendored/flexdb.mjs +1087 -0
- package/dist/webapp-url.js +61 -0
- package/dist/work-client.js +195 -0
- package/package.json +39 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1403 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `yolo` — YOLO Studio substrate CLI (Phase 8a Group 9 scaffold).
|
|
4
|
+
*
|
|
5
|
+
* Owns substrate-tooling subcommands (plan import/export/validate;
|
|
6
|
+
* future: workspace, artifact). v1 ships only `yolo --version` and
|
|
7
|
+
* `yolo context`; full plan import/export lands in Phase 8c.
|
|
8
|
+
*
|
|
9
|
+
* NOT the agent CLI. The agent CLI is `yolo-code`. NOT the LLM router
|
|
10
|
+
* client. The LLM router client is `yolo-router`.
|
|
11
|
+
*
|
|
12
|
+
* Auth contract (AUTH_AND_ONBOARDING Slice 0 — user-JWT-only):
|
|
13
|
+
* `SESSION_ID` + `YOLO_COMMON_API_URL` + a USER ACCESS JWT, resolved by
|
|
14
|
+
* `auth-context.ts` from `~/.config/yolo/token` (rotated) → `YOLO_API_TOKEN`.
|
|
15
|
+
* That JWT is the SOLE credential — it's sent as `Authorization: Bearer` and
|
|
16
|
+
* `X-Internal-Auth` is never sent. The `INTERNAL_API_KEY` fallback was REMOVED;
|
|
17
|
+
* the CLI fails with an auth error if no user token is available. The CLI exits
|
|
18
|
+
* with `session-required` if `SESSION_ID` is missing — outside-container
|
|
19
|
+
* invocation is out of scope for v1 (future external-login flow). Workspace
|
|
20
|
+
* binding is derived from the session record, not from env or args.
|
|
21
|
+
*/
|
|
22
|
+
import { readSessionContext, formatContext, ContextResolutionError } from './context.js';
|
|
23
|
+
import { validatePlanFile, formatErrors } from './plan-validate.js';
|
|
24
|
+
import { runPlanImport, formatSuccess as formatImportSuccess, exitCodeForFailure as importExitCode, } from './plan-import.js';
|
|
25
|
+
import { runPlanExport, formatSuccess as formatExportSuccess, exitCodeForFailure as exportExitCode, } from './plan-export.js';
|
|
26
|
+
import { runPlanStateTransition, formatSuccess as formatStateSuccess, exitCodeForFailure as stateExitCode, } from './plan-state.js';
|
|
27
|
+
import { runPlanGet, exitCodeForFailure as getExitCode, } from './plan-get.js';
|
|
28
|
+
import { runPlanOpen, exitCodeForFailure as openExitCode, } from './plan-open.js';
|
|
29
|
+
import { runPlanList, exitCodeForFailure as listExitCode, } from './plan-list.js';
|
|
30
|
+
import { runRunStart, exitCodeForFailure as runStartExitCode, } from './run-start.js';
|
|
31
|
+
import { runRunLifecycle, exitCodeForFailure as runLifecycleExitCode, } from './run-lifecycle.js';
|
|
32
|
+
import { runRunGet, exitCodeForFailure as runGetExitCode, } from './run-get.js';
|
|
33
|
+
import { runRunList, exitCodeForFailure as runListExitCode, } from './run-list.js';
|
|
34
|
+
import { runRunTransfer, exitCodeForFailure as runTransferExitCode, } from './run-transfer.js';
|
|
35
|
+
import { runArtifactGet, exitCodeForFailure as artifactGetExitCode, } from './artifact-get.js';
|
|
36
|
+
import { runArtifactList, exitCodeForFailure as artifactListExitCode, } from './artifact-list.js';
|
|
37
|
+
import { runTileAppSign, runTileAppPublish, exitCodeForFailure as tileAppExitCode, } from './tileapp-publisher.js';
|
|
38
|
+
// Resolved at startup from the package's own package.json so the
|
|
39
|
+
// `--version` output can never drift from the npm version. Touching
|
|
40
|
+
// only one of the two used to silently emit stale info.
|
|
41
|
+
//
|
|
42
|
+
// We read the file via `createRequire` rather than `import …
|
|
43
|
+
// assert/with { type: 'json' }` because our tsconfig pins module:
|
|
44
|
+
// Node16, which doesn't support import attributes. The file lives
|
|
45
|
+
// next to dist/cli.js in the installed package layout
|
|
46
|
+
// (/opt/yolo-cli/package.json + /opt/yolo-cli/dist/cli.js), so the
|
|
47
|
+
// `../package.json` relative path resolves correctly both during
|
|
48
|
+
// tests (src/) and in production (dist/).
|
|
49
|
+
import { createRequire } from 'module';
|
|
50
|
+
const PKG_VERSION = createRequire(import.meta.url)('../package.json').version;
|
|
51
|
+
/**
|
|
52
|
+
* Serialize an orchestration result for `--json` output. Strips the
|
|
53
|
+
* internal `ok` discriminant — exit code already conveys success vs
|
|
54
|
+
* failure, and a `kind` field on failures is enough for scripts to
|
|
55
|
+
* branch on. Leaves `--summary` outputs (the human-readable
|
|
56
|
+
* single-line success messages, `FAIL [kind]: …` errors) untouched.
|
|
57
|
+
*/
|
|
58
|
+
function formatJsonResult(result) {
|
|
59
|
+
const { ok: _ok, ...payload } = result;
|
|
60
|
+
return JSON.stringify(payload, null, 2);
|
|
61
|
+
}
|
|
62
|
+
function printHelp() {
|
|
63
|
+
process.stdout.write([
|
|
64
|
+
'Usage: yolo <command> [options]',
|
|
65
|
+
'',
|
|
66
|
+
'Commands:',
|
|
67
|
+
' context Print resolved session/workspace/API context.',
|
|
68
|
+
' plan validate <file> Validate a .yolo/plans/<slug>.md plan file (offline).',
|
|
69
|
+
' plan import <file> [opts] Import file → DB (work.create_plan / update_plan).',
|
|
70
|
+
' [--workspace <wsId>] Sanity-check the workspace bound to this session.',
|
|
71
|
+
' [--lockfile <name>] Use .imports.<name>.json instead of the gitignored default.',
|
|
72
|
+
' [--force] Override divergence (DB ahead of lockfile) or 409 on create.',
|
|
73
|
+
' [--json] Emit the result as JSON (for scripting).',
|
|
74
|
+
' plan export <planId> [opts] Export DB → file (work.get_plan + canonicalize).',
|
|
75
|
+
' [--workspace <wsId>] Sanity-check the workspace bound to this session.',
|
|
76
|
+
' [--lockfile <name>] Refresh .imports.<name>.json instead of the default.',
|
|
77
|
+
' [-o <file>] Output path. Default: <plansDir>/<planId>.md.',
|
|
78
|
+
' [--json] Emit the result as JSON.',
|
|
79
|
+
' plan activate <planId> [opts] Transition Plan.state → active (work.update_plan).',
|
|
80
|
+
' plan archive <planId> [opts] Transition Plan.state → archived (work.update_plan).',
|
|
81
|
+
' [--workspace <wsId>] Sanity-check the workspace bound to this session.',
|
|
82
|
+
' [--lockfile <name>] Refresh .imports.<name>.json lockfile entry post-update.',
|
|
83
|
+
' [--json] Emit the result as JSON.',
|
|
84
|
+
' plan get <planId> [opts] Read a Plan from the DB (work.get_plan, no file write).',
|
|
85
|
+
' [--workspace <wsId>] Sanity-check the workspace bound to this session.',
|
|
86
|
+
' [--waves] Group steps by topological wave (parallel-execution view).',
|
|
87
|
+
' [--json] Pretty-print raw JSON instead of the summary.',
|
|
88
|
+
' plan list [opts] List Plans in the current workspace (work.list_plans).',
|
|
89
|
+
' [--workspace <wsId>] Sanity-check the workspace bound to this session.',
|
|
90
|
+
' [--state <draft|active|archived>] Server-side Plan.state filter.',
|
|
91
|
+
' [--json] Pretty-print raw JSON instead of the table.',
|
|
92
|
+
' plan open <planId> [opts] Print the Plan-DAG URL for the given Plan (no Run).',
|
|
93
|
+
' [--workspace <wsId>] Sanity-check the workspace bound to this session.',
|
|
94
|
+
' [--json] Emit {planId, workspaceId, url} as JSON.',
|
|
95
|
+
' run start <planId> [opts] Bootstrap a Plan Run (work.start_run).',
|
|
96
|
+
' [--workspace <wsId>] Sanity-check the workspace bound to this session.',
|
|
97
|
+
' [--inputs <json>] JSON object of Plan inputs. Default: {}.',
|
|
98
|
+
' [--json] Emit the response as JSON.',
|
|
99
|
+
' run get <runId> [opts] Read a Plan Run (work.get_run, no mutation).',
|
|
100
|
+
' [--workspace <wsId>] Sanity-check the workspace bound to this session.',
|
|
101
|
+
' [--json] Pretty-print raw JSON instead of the summary.',
|
|
102
|
+
' run list [opts] List Plan Runs in the current workspace (work.list_runs).',
|
|
103
|
+
' [--workspace <wsId>] Sanity-check the workspace bound to this session.',
|
|
104
|
+
' [--state <pending|running|paused|...>] Server-side executionState filter.',
|
|
105
|
+
' [--json] Pretty-print raw JSON instead of the table.',
|
|
106
|
+
' run pause <runId> [opts] Pause a running Plan Run.',
|
|
107
|
+
' run resume <runId> [opts] Resume a paused Plan Run.',
|
|
108
|
+
' run cancel <runId> [opts] Cancel a Plan Run.',
|
|
109
|
+
' [--workspace <wsId>] Sanity-check the workspace bound to this session.',
|
|
110
|
+
' [--reason <text>] Optional reason (pause/cancel only). ≤1024 chars.',
|
|
111
|
+
' [--json] Emit the response as JSON.',
|
|
112
|
+
' run transfer <runId> [opts] Reassign the Operator (work.transfer_run_operator).',
|
|
113
|
+
' --to <agentId> Target Operator (Operator-tier: claude, codex).',
|
|
114
|
+
' --user-driven Transfer to user-driven (operatorAgentId=null). Mutex with --to.',
|
|
115
|
+
' [--workspace <wsId>] Sanity-check the workspace bound to this session.',
|
|
116
|
+
' [--json] Emit the response as JSON.',
|
|
117
|
+
' artifact get <key> [opts] Read a workspace artifact (work.get_artifact).',
|
|
118
|
+
' [--version <n>] Pin to a specific version. Default: latest.',
|
|
119
|
+
' [--workspace <wsId>] Sanity-check the workspace bound to this session.',
|
|
120
|
+
' [--content] Stdout the raw artifact body only (for piping).',
|
|
121
|
+
' [--json] Pretty-print the full record as JSON.',
|
|
122
|
+
' artifact list [opts] List workspace artifacts (work.list_artifacts).',
|
|
123
|
+
' [--prefix <prefix>] Server-side key prefix filter.',
|
|
124
|
+
' [--limit <n>] Cap result count (1-500, default 100).',
|
|
125
|
+
' [--workspace <wsId>] Sanity-check the workspace bound to this session.',
|
|
126
|
+
' [--json] Pretty-print raw JSON instead of the table.',
|
|
127
|
+
' tileapp init <name> Scaffold a personal app (./<name>/tileapp.json + index.html).',
|
|
128
|
+
' tileapp validate <manifest> [opts] Offline lint: manifest schema + bundle layout (no auth).',
|
|
129
|
+
' [--bundle-dir <dir>] Override the static-bundle directory to check.',
|
|
130
|
+
' tileapp dev <manifest> [opts] Serve the bundle locally + a mock broker (offline iterate).',
|
|
131
|
+
' [--port N] [--host H] Bind (default: 127.0.0.1:$PORT|3000).',
|
|
132
|
+
' [--bundle-dir <dir>] [--deny] Override bundle dir; --deny = mock broker denies all.',
|
|
133
|
+
' tileapp sign <manifest> --publisher <id> KMS-sign a tile-app manifest (POST /v1/publisher/sign).',
|
|
134
|
+
' [--key <keyId>] Pick a specific signing key (default: the only active one).',
|
|
135
|
+
' [--stdout] Print the signed manifest instead of rewriting the file.',
|
|
136
|
+
' tileapp publish <manifest> --personal Register a PERSONAL app (no signing/review). Pure-UI: uploads the',
|
|
137
|
+
' bundle. Runtime (manifest has `runtime`): builds the image in-pod',
|
|
138
|
+
' + mediated-push to your registry namespace.',
|
|
139
|
+
' [--bundle-dir <dir>] Pure-UI: override the static-bundle directory to upload.',
|
|
140
|
+
' [--context <dir>] [--dockerfile <f>] Runtime: build context + Dockerfile (default: the manifest dir).',
|
|
141
|
+
' [--builder auto|podman|skopeo] Runtime build engine. auto (default): skopeo assembler for RUN-less',
|
|
142
|
+
' Dockerfiles (no container engine — works in-pod), else podman.',
|
|
143
|
+
' tileapp publish <manifest> [opts] Submit a SIGNED manifest for marketplace review (POST /v1/publisher/publish).',
|
|
144
|
+
' [--channel beta|stable] Target channel (default: stable).',
|
|
145
|
+
' [--image-digest <d>] Consistency check: must equal the signed manifest image.digest.',
|
|
146
|
+
' serve <dir> [opts] Static file server (decision-preview / Gap 2a).',
|
|
147
|
+
' [--port <n>] Port (default: $PORT, else 3000).',
|
|
148
|
+
' [--host <h>] Bind host (default: 0.0.0.0).',
|
|
149
|
+
' [--spa] Serve index.html for unmatched routes (SPA mode).',
|
|
150
|
+
' deploy [opts] Ship the cwd project (detect → build → bundle → upload).',
|
|
151
|
+
' [--env <staging|prod>] Target env (default staging; prod needs operator approval).',
|
|
152
|
+
' [--dry-run] Stop after bundling; print the manifest summary (offline).',
|
|
153
|
+
' [--json] Progress to stderr; final JSON result on stdout.',
|
|
154
|
+
' deploy init [--slug <s>] [--type <t>] Create/link a hosting project; writes .yolo/deploy.json.',
|
|
155
|
+
' deploy status [--json] Project + release status (incl. pending approvals).',
|
|
156
|
+
' deploy logs [--tail] [--since <dur>] Recent logs; --tail streams NDJSON.',
|
|
157
|
+
' deploy rollback [releaseId] [--json] Repoint the project to a previous release.',
|
|
158
|
+
' --version Print substrate CLI version.',
|
|
159
|
+
' --help Print this help.',
|
|
160
|
+
'',
|
|
161
|
+
'Distinct from:',
|
|
162
|
+
' yolo-code — the YOLO Studio coding-agent CLI.',
|
|
163
|
+
' yolo-router — the LLM gateway client (LLM API forwarder).',
|
|
164
|
+
'',
|
|
165
|
+
].join('\n'));
|
|
166
|
+
}
|
|
167
|
+
function runPlanValidate(args) {
|
|
168
|
+
const file = args[0];
|
|
169
|
+
if (!file) {
|
|
170
|
+
process.stderr.write('yolo: plan validate requires a file path\n');
|
|
171
|
+
process.stderr.write('Usage: yolo plan validate <file>\n');
|
|
172
|
+
return 64; // EX_USAGE
|
|
173
|
+
}
|
|
174
|
+
let result;
|
|
175
|
+
try {
|
|
176
|
+
result = validatePlanFile(file);
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
180
|
+
process.stderr.write(`yolo: cannot read plan file '${file}': ${msg}\n`);
|
|
181
|
+
return 64;
|
|
182
|
+
}
|
|
183
|
+
if (result.ok) {
|
|
184
|
+
process.stdout.write(`OK: ${file} is canonical and schema-valid (planId=${result.planId}, ${result.bytes} bytes)\n`);
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
process.stderr.write(`FAIL: ${file}\n`);
|
|
188
|
+
process.stderr.write(`${formatErrors(result.errors)}\n`);
|
|
189
|
+
return 1;
|
|
190
|
+
}
|
|
191
|
+
function parseImportArgs(args) {
|
|
192
|
+
let filePath;
|
|
193
|
+
let workspaceFlag;
|
|
194
|
+
let lockfileFlag;
|
|
195
|
+
let force = false;
|
|
196
|
+
let jsonOutput = false;
|
|
197
|
+
for (let i = 0; i < args.length; i++) {
|
|
198
|
+
const a = args[i];
|
|
199
|
+
if (a === '--workspace') {
|
|
200
|
+
const v = args[++i];
|
|
201
|
+
if (!v || v.startsWith('--'))
|
|
202
|
+
return { ok: false, message: '--workspace requires a value' };
|
|
203
|
+
workspaceFlag = v;
|
|
204
|
+
}
|
|
205
|
+
else if (a.startsWith('--workspace=')) {
|
|
206
|
+
workspaceFlag = a.slice('--workspace='.length);
|
|
207
|
+
}
|
|
208
|
+
else if (a === '--lockfile') {
|
|
209
|
+
const v = args[++i];
|
|
210
|
+
if (!v || v.startsWith('--'))
|
|
211
|
+
return { ok: false, message: '--lockfile requires a value' };
|
|
212
|
+
lockfileFlag = v;
|
|
213
|
+
}
|
|
214
|
+
else if (a.startsWith('--lockfile=')) {
|
|
215
|
+
lockfileFlag = a.slice('--lockfile='.length);
|
|
216
|
+
}
|
|
217
|
+
else if (a === '--force') {
|
|
218
|
+
force = true;
|
|
219
|
+
}
|
|
220
|
+
else if (a === '--json') {
|
|
221
|
+
jsonOutput = true;
|
|
222
|
+
}
|
|
223
|
+
else if (a.startsWith('--')) {
|
|
224
|
+
return { ok: false, message: `unknown option: ${a}` };
|
|
225
|
+
}
|
|
226
|
+
else if (!filePath) {
|
|
227
|
+
filePath = a;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
return { ok: false, message: `unexpected positional argument: ${a}` };
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (!filePath)
|
|
234
|
+
return { ok: false, message: 'plan import requires a file path' };
|
|
235
|
+
return { ok: true, filePath, workspaceFlag, lockfileFlag, force, jsonOutput };
|
|
236
|
+
}
|
|
237
|
+
async function runPlanImportCmd(args) {
|
|
238
|
+
const parsed = parseImportArgs(args);
|
|
239
|
+
if (!parsed.ok) {
|
|
240
|
+
process.stderr.write(`yolo: ${parsed.message}\n`);
|
|
241
|
+
process.stderr.write('Usage: yolo plan import <file> [--workspace <wsId>] [--lockfile <name>] [--force] [--json]\n');
|
|
242
|
+
return 64;
|
|
243
|
+
}
|
|
244
|
+
const result = await runPlanImport({
|
|
245
|
+
filePath: parsed.filePath,
|
|
246
|
+
workspaceFlag: parsed.workspaceFlag,
|
|
247
|
+
lockfileFlag: parsed.lockfileFlag,
|
|
248
|
+
force: parsed.force,
|
|
249
|
+
});
|
|
250
|
+
if (result.ok) {
|
|
251
|
+
process.stdout.write(`${parsed.jsonOutput ? formatJsonResult(result) : formatImportSuccess(result)}\n`);
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
process.stderr.write(parsed.jsonOutput ? `${formatJsonResult(result)}\n` : `FAIL [${result.kind}]: ${result.message}\n`);
|
|
255
|
+
return importExitCode(result.kind);
|
|
256
|
+
}
|
|
257
|
+
function parseExportArgs(args) {
|
|
258
|
+
let planId;
|
|
259
|
+
let workspaceFlag;
|
|
260
|
+
let lockfileFlag;
|
|
261
|
+
let outputFlag;
|
|
262
|
+
let jsonOutput = false;
|
|
263
|
+
for (let i = 0; i < args.length; i++) {
|
|
264
|
+
const a = args[i];
|
|
265
|
+
if (a === '--workspace') {
|
|
266
|
+
const v = args[++i];
|
|
267
|
+
if (!v || v.startsWith('--'))
|
|
268
|
+
return { ok: false, message: '--workspace requires a value' };
|
|
269
|
+
workspaceFlag = v;
|
|
270
|
+
}
|
|
271
|
+
else if (a.startsWith('--workspace=')) {
|
|
272
|
+
workspaceFlag = a.slice('--workspace='.length);
|
|
273
|
+
}
|
|
274
|
+
else if (a === '--lockfile') {
|
|
275
|
+
const v = args[++i];
|
|
276
|
+
if (!v || v.startsWith('--'))
|
|
277
|
+
return { ok: false, message: '--lockfile requires a value' };
|
|
278
|
+
lockfileFlag = v;
|
|
279
|
+
}
|
|
280
|
+
else if (a.startsWith('--lockfile=')) {
|
|
281
|
+
lockfileFlag = a.slice('--lockfile='.length);
|
|
282
|
+
}
|
|
283
|
+
else if (a === '-o' || a === '--output') {
|
|
284
|
+
const v = args[++i];
|
|
285
|
+
if (!v || v.startsWith('--'))
|
|
286
|
+
return { ok: false, message: '-o requires a value' };
|
|
287
|
+
outputFlag = v;
|
|
288
|
+
}
|
|
289
|
+
else if (a.startsWith('--output=')) {
|
|
290
|
+
outputFlag = a.slice('--output='.length);
|
|
291
|
+
}
|
|
292
|
+
else if (a === '--json') {
|
|
293
|
+
jsonOutput = true;
|
|
294
|
+
}
|
|
295
|
+
else if (a.startsWith('--')) {
|
|
296
|
+
return { ok: false, message: `unknown option: ${a}` };
|
|
297
|
+
}
|
|
298
|
+
else if (!planId) {
|
|
299
|
+
planId = a;
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
return { ok: false, message: `unexpected positional argument: ${a}` };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (!planId)
|
|
306
|
+
return { ok: false, message: 'plan export requires a planId' };
|
|
307
|
+
return { ok: true, planId, workspaceFlag, lockfileFlag, outputFlag, jsonOutput };
|
|
308
|
+
}
|
|
309
|
+
async function runPlanExportCmd(args) {
|
|
310
|
+
const parsed = parseExportArgs(args);
|
|
311
|
+
if (!parsed.ok) {
|
|
312
|
+
process.stderr.write(`yolo: ${parsed.message}\n`);
|
|
313
|
+
process.stderr.write('Usage: yolo plan export <planId> [--workspace <wsId>] [--lockfile <name>] [-o <file>] [--json]\n');
|
|
314
|
+
return 64;
|
|
315
|
+
}
|
|
316
|
+
const result = await runPlanExport({
|
|
317
|
+
planId: parsed.planId,
|
|
318
|
+
workspaceFlag: parsed.workspaceFlag,
|
|
319
|
+
lockfileFlag: parsed.lockfileFlag,
|
|
320
|
+
outputFlag: parsed.outputFlag,
|
|
321
|
+
});
|
|
322
|
+
if (result.ok) {
|
|
323
|
+
process.stdout.write(`${parsed.jsonOutput ? formatJsonResult(result) : formatExportSuccess(result)}\n`);
|
|
324
|
+
return 0;
|
|
325
|
+
}
|
|
326
|
+
process.stderr.write(parsed.jsonOutput ? `${formatJsonResult(result)}\n` : `FAIL [${result.kind}]: ${result.message}\n`);
|
|
327
|
+
return exportExitCode(result.kind);
|
|
328
|
+
}
|
|
329
|
+
function parseStateArgs(args) {
|
|
330
|
+
let planId;
|
|
331
|
+
let workspaceFlag;
|
|
332
|
+
let lockfileFlag;
|
|
333
|
+
let jsonOutput = false;
|
|
334
|
+
for (let i = 0; i < args.length; i++) {
|
|
335
|
+
const a = args[i];
|
|
336
|
+
if (a === '--workspace') {
|
|
337
|
+
const v = args[++i];
|
|
338
|
+
if (!v || v.startsWith('--'))
|
|
339
|
+
return { ok: false, message: '--workspace requires a value' };
|
|
340
|
+
workspaceFlag = v;
|
|
341
|
+
}
|
|
342
|
+
else if (a.startsWith('--workspace=')) {
|
|
343
|
+
workspaceFlag = a.slice('--workspace='.length);
|
|
344
|
+
}
|
|
345
|
+
else if (a === '--lockfile') {
|
|
346
|
+
const v = args[++i];
|
|
347
|
+
if (!v || v.startsWith('--'))
|
|
348
|
+
return { ok: false, message: '--lockfile requires a value' };
|
|
349
|
+
lockfileFlag = v;
|
|
350
|
+
}
|
|
351
|
+
else if (a.startsWith('--lockfile=')) {
|
|
352
|
+
lockfileFlag = a.slice('--lockfile='.length);
|
|
353
|
+
}
|
|
354
|
+
else if (a === '--json') {
|
|
355
|
+
jsonOutput = true;
|
|
356
|
+
}
|
|
357
|
+
else if (a.startsWith('--')) {
|
|
358
|
+
return { ok: false, message: `unknown option: ${a}` };
|
|
359
|
+
}
|
|
360
|
+
else if (!planId) {
|
|
361
|
+
planId = a;
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
return { ok: false, message: `unexpected positional argument: ${a}` };
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (!planId)
|
|
368
|
+
return { ok: false, message: 'requires a planId' };
|
|
369
|
+
return { ok: true, planId, workspaceFlag, lockfileFlag, jsonOutput };
|
|
370
|
+
}
|
|
371
|
+
async function runPlanStateCmd(verb, args) {
|
|
372
|
+
const parsed = parseStateArgs(args);
|
|
373
|
+
if (!parsed.ok) {
|
|
374
|
+
process.stderr.write(`yolo: plan ${verb}: ${parsed.message}\n`);
|
|
375
|
+
process.stderr.write(`Usage: yolo plan ${verb} <planId> [--workspace <wsId>] [--lockfile <name>] [--json]\n`);
|
|
376
|
+
return 64;
|
|
377
|
+
}
|
|
378
|
+
const targetState = verb === 'activate' ? 'active' : 'archived';
|
|
379
|
+
const result = await runPlanStateTransition({
|
|
380
|
+
planId: parsed.planId,
|
|
381
|
+
targetState,
|
|
382
|
+
workspaceFlag: parsed.workspaceFlag,
|
|
383
|
+
lockfileFlag: parsed.lockfileFlag,
|
|
384
|
+
});
|
|
385
|
+
if (result.ok) {
|
|
386
|
+
process.stdout.write(`${parsed.jsonOutput ? formatJsonResult(result) : formatStateSuccess(result)}\n`);
|
|
387
|
+
return 0;
|
|
388
|
+
}
|
|
389
|
+
process.stderr.write(parsed.jsonOutput ? `${formatJsonResult(result)}\n` : `FAIL [${result.kind}]: ${result.message}\n`);
|
|
390
|
+
return stateExitCode(result.kind);
|
|
391
|
+
}
|
|
392
|
+
function parseGetArgs(args) {
|
|
393
|
+
let planId;
|
|
394
|
+
let workspaceFlag;
|
|
395
|
+
let jsonOutput = false;
|
|
396
|
+
let wavesOutput = false;
|
|
397
|
+
for (let i = 0; i < args.length; i++) {
|
|
398
|
+
const a = args[i];
|
|
399
|
+
if (a === '--workspace') {
|
|
400
|
+
const v = args[++i];
|
|
401
|
+
if (!v || v.startsWith('--'))
|
|
402
|
+
return { ok: false, message: '--workspace requires a value' };
|
|
403
|
+
workspaceFlag = v;
|
|
404
|
+
}
|
|
405
|
+
else if (a.startsWith('--workspace=')) {
|
|
406
|
+
workspaceFlag = a.slice('--workspace='.length);
|
|
407
|
+
}
|
|
408
|
+
else if (a === '--json') {
|
|
409
|
+
jsonOutput = true;
|
|
410
|
+
}
|
|
411
|
+
else if (a === '--waves') {
|
|
412
|
+
wavesOutput = true;
|
|
413
|
+
}
|
|
414
|
+
else if (a.startsWith('--')) {
|
|
415
|
+
return { ok: false, message: `unknown option: ${a}` };
|
|
416
|
+
}
|
|
417
|
+
else if (!planId) {
|
|
418
|
+
planId = a;
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
return { ok: false, message: `unexpected positional argument: ${a}` };
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (!planId)
|
|
425
|
+
return { ok: false, message: 'plan get requires a planId' };
|
|
426
|
+
if (jsonOutput && wavesOutput) {
|
|
427
|
+
return { ok: false, message: '--json and --waves are mutually exclusive' };
|
|
428
|
+
}
|
|
429
|
+
return { ok: true, planId, workspaceFlag, jsonOutput, wavesOutput };
|
|
430
|
+
}
|
|
431
|
+
async function runPlanGetCmd(args) {
|
|
432
|
+
const parsed = parseGetArgs(args);
|
|
433
|
+
if (!parsed.ok) {
|
|
434
|
+
process.stderr.write(`yolo: ${parsed.message}\n`);
|
|
435
|
+
process.stderr.write('Usage: yolo plan get <planId> [--workspace <wsId>] [--waves | --json]\n');
|
|
436
|
+
return 64;
|
|
437
|
+
}
|
|
438
|
+
const outputFormat = parsed.jsonOutput ? 'json' : parsed.wavesOutput ? 'waves' : 'summary';
|
|
439
|
+
const result = await runPlanGet({
|
|
440
|
+
planId: parsed.planId,
|
|
441
|
+
workspaceFlag: parsed.workspaceFlag,
|
|
442
|
+
outputFormat,
|
|
443
|
+
});
|
|
444
|
+
if (result.ok) {
|
|
445
|
+
process.stdout.write(`${result.output}\n`);
|
|
446
|
+
return 0;
|
|
447
|
+
}
|
|
448
|
+
process.stderr.write(`FAIL [${result.kind}]: ${result.message}\n`);
|
|
449
|
+
return getExitCode(result.kind);
|
|
450
|
+
}
|
|
451
|
+
function parseListArgs(args) {
|
|
452
|
+
let workspaceFlag;
|
|
453
|
+
let stateFilter;
|
|
454
|
+
let jsonOutput = false;
|
|
455
|
+
for (let i = 0; i < args.length; i++) {
|
|
456
|
+
const a = args[i];
|
|
457
|
+
if (a === '--workspace') {
|
|
458
|
+
const v = args[++i];
|
|
459
|
+
if (!v || v.startsWith('--'))
|
|
460
|
+
return { ok: false, message: '--workspace requires a value' };
|
|
461
|
+
workspaceFlag = v;
|
|
462
|
+
}
|
|
463
|
+
else if (a.startsWith('--workspace=')) {
|
|
464
|
+
workspaceFlag = a.slice('--workspace='.length);
|
|
465
|
+
}
|
|
466
|
+
else if (a === '--state') {
|
|
467
|
+
const v = args[++i];
|
|
468
|
+
if (!v || v.startsWith('--'))
|
|
469
|
+
return { ok: false, message: '--state requires a value' };
|
|
470
|
+
stateFilter = v;
|
|
471
|
+
}
|
|
472
|
+
else if (a.startsWith('--state=')) {
|
|
473
|
+
stateFilter = a.slice('--state='.length);
|
|
474
|
+
}
|
|
475
|
+
else if (a === '--json') {
|
|
476
|
+
jsonOutput = true;
|
|
477
|
+
}
|
|
478
|
+
else if (a.startsWith('--')) {
|
|
479
|
+
return { ok: false, message: `unknown option: ${a}` };
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
return { ok: false, message: `unexpected positional argument: ${a}` };
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return { ok: true, workspaceFlag, stateFilter, jsonOutput };
|
|
486
|
+
}
|
|
487
|
+
async function runPlanListCmd(args) {
|
|
488
|
+
const parsed = parseListArgs(args);
|
|
489
|
+
if (!parsed.ok) {
|
|
490
|
+
process.stderr.write(`yolo: plan list: ${parsed.message}\n`);
|
|
491
|
+
process.stderr.write('Usage: yolo plan list [--workspace <wsId>] [--state <draft|active|archived>] [--json]\n');
|
|
492
|
+
return 64;
|
|
493
|
+
}
|
|
494
|
+
const result = await runPlanList({
|
|
495
|
+
workspaceFlag: parsed.workspaceFlag,
|
|
496
|
+
stateFilter: parsed.stateFilter,
|
|
497
|
+
outputFormat: parsed.jsonOutput ? 'json' : 'summary',
|
|
498
|
+
});
|
|
499
|
+
if (result.ok) {
|
|
500
|
+
process.stdout.write(`${result.output}\n`);
|
|
501
|
+
return 0;
|
|
502
|
+
}
|
|
503
|
+
process.stderr.write(`FAIL [${result.kind}]: ${result.message}\n`);
|
|
504
|
+
return listExitCode(result.kind);
|
|
505
|
+
}
|
|
506
|
+
function parseOpenArgs(args) {
|
|
507
|
+
let planId;
|
|
508
|
+
let workspaceFlag;
|
|
509
|
+
let jsonOutput = false;
|
|
510
|
+
for (let i = 0; i < args.length; i++) {
|
|
511
|
+
const a = args[i];
|
|
512
|
+
if (a === '--workspace') {
|
|
513
|
+
const v = args[++i];
|
|
514
|
+
if (!v || v.startsWith('--'))
|
|
515
|
+
return { ok: false, message: '--workspace requires a value' };
|
|
516
|
+
workspaceFlag = v;
|
|
517
|
+
}
|
|
518
|
+
else if (a.startsWith('--workspace=')) {
|
|
519
|
+
workspaceFlag = a.slice('--workspace='.length);
|
|
520
|
+
}
|
|
521
|
+
else if (a === '--json') {
|
|
522
|
+
jsonOutput = true;
|
|
523
|
+
}
|
|
524
|
+
else if (a.startsWith('--')) {
|
|
525
|
+
return { ok: false, message: `unknown option: ${a}` };
|
|
526
|
+
}
|
|
527
|
+
else if (!planId) {
|
|
528
|
+
planId = a;
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
return { ok: false, message: `unexpected positional argument: ${a}` };
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (!planId)
|
|
535
|
+
return { ok: false, message: 'plan open requires a planId' };
|
|
536
|
+
return { ok: true, planId, workspaceFlag, jsonOutput };
|
|
537
|
+
}
|
|
538
|
+
async function runPlanOpenCmd(args) {
|
|
539
|
+
const parsed = parseOpenArgs(args);
|
|
540
|
+
if (!parsed.ok) {
|
|
541
|
+
process.stderr.write(`yolo: plan open: ${parsed.message}\n`);
|
|
542
|
+
process.stderr.write('Usage: yolo plan open <planId> [--workspace <wsId>] [--json]\n');
|
|
543
|
+
return 64;
|
|
544
|
+
}
|
|
545
|
+
const result = await runPlanOpen({
|
|
546
|
+
planId: parsed.planId,
|
|
547
|
+
workspaceFlag: parsed.workspaceFlag,
|
|
548
|
+
outputFormat: parsed.jsonOutput ? 'json' : 'text',
|
|
549
|
+
});
|
|
550
|
+
if (result.ok) {
|
|
551
|
+
if (parsed.jsonOutput) {
|
|
552
|
+
process.stdout.write(`${formatJsonResult(result)}\n`);
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
process.stdout.write(`${result.output}\n`);
|
|
556
|
+
}
|
|
557
|
+
return 0;
|
|
558
|
+
}
|
|
559
|
+
process.stderr.write(`FAIL [${result.kind}]: ${result.message}\n`);
|
|
560
|
+
return openExitCode(result.kind);
|
|
561
|
+
}
|
|
562
|
+
function parseRunStartArgs(args) {
|
|
563
|
+
let planId;
|
|
564
|
+
let workspaceFlag;
|
|
565
|
+
let inputs;
|
|
566
|
+
let jsonOutput = false;
|
|
567
|
+
for (let i = 0; i < args.length; i++) {
|
|
568
|
+
const a = args[i];
|
|
569
|
+
if (a === '--workspace') {
|
|
570
|
+
const v = args[++i];
|
|
571
|
+
if (!v || v.startsWith('--'))
|
|
572
|
+
return { ok: false, message: '--workspace requires a value' };
|
|
573
|
+
workspaceFlag = v;
|
|
574
|
+
}
|
|
575
|
+
else if (a.startsWith('--workspace=')) {
|
|
576
|
+
workspaceFlag = a.slice('--workspace='.length);
|
|
577
|
+
}
|
|
578
|
+
else if (a === '--inputs') {
|
|
579
|
+
const v = args[++i];
|
|
580
|
+
if (!v || v.startsWith('--'))
|
|
581
|
+
return { ok: false, message: '--inputs requires a JSON object value' };
|
|
582
|
+
const parsed = parseInputsJson(v);
|
|
583
|
+
if (!parsed.ok)
|
|
584
|
+
return parsed;
|
|
585
|
+
inputs = parsed.value;
|
|
586
|
+
}
|
|
587
|
+
else if (a.startsWith('--inputs=')) {
|
|
588
|
+
const parsed = parseInputsJson(a.slice('--inputs='.length));
|
|
589
|
+
if (!parsed.ok)
|
|
590
|
+
return parsed;
|
|
591
|
+
inputs = parsed.value;
|
|
592
|
+
}
|
|
593
|
+
else if (a === '--json') {
|
|
594
|
+
jsonOutput = true;
|
|
595
|
+
}
|
|
596
|
+
else if (a.startsWith('--')) {
|
|
597
|
+
return { ok: false, message: `unknown option: ${a}` };
|
|
598
|
+
}
|
|
599
|
+
else if (!planId) {
|
|
600
|
+
planId = a;
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
return { ok: false, message: `unexpected positional argument: ${a}` };
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (!planId)
|
|
607
|
+
return { ok: false, message: 'run start requires a planId' };
|
|
608
|
+
return { ok: true, planId, workspaceFlag, inputs, jsonOutput };
|
|
609
|
+
}
|
|
610
|
+
function parseInputsJson(raw) {
|
|
611
|
+
let parsed;
|
|
612
|
+
try {
|
|
613
|
+
parsed = JSON.parse(raw);
|
|
614
|
+
}
|
|
615
|
+
catch (err) {
|
|
616
|
+
return { ok: false, message: `--inputs is not valid JSON: ${err instanceof Error ? err.message : String(err)}` };
|
|
617
|
+
}
|
|
618
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
619
|
+
return { ok: false, message: '--inputs must be a JSON object (e.g. \'{"env":"prod"}\')' };
|
|
620
|
+
}
|
|
621
|
+
return { ok: true, value: parsed };
|
|
622
|
+
}
|
|
623
|
+
async function runRunStartCmd(args) {
|
|
624
|
+
const parsed = parseRunStartArgs(args);
|
|
625
|
+
if (!parsed.ok) {
|
|
626
|
+
process.stderr.write(`yolo: run start: ${parsed.message}\n`);
|
|
627
|
+
process.stderr.write('Usage: yolo run start <planId> [--workspace <wsId>] [--inputs <json>] [--json]\n');
|
|
628
|
+
return 64;
|
|
629
|
+
}
|
|
630
|
+
const result = await runRunStart({
|
|
631
|
+
planId: parsed.planId,
|
|
632
|
+
workspaceFlag: parsed.workspaceFlag,
|
|
633
|
+
inputs: parsed.inputs,
|
|
634
|
+
outputFormat: parsed.jsonOutput ? 'json' : 'summary',
|
|
635
|
+
});
|
|
636
|
+
if (result.ok) {
|
|
637
|
+
process.stdout.write(`${result.output}\n`);
|
|
638
|
+
return 0;
|
|
639
|
+
}
|
|
640
|
+
process.stderr.write(`FAIL [${result.kind}]: ${result.message}\n`);
|
|
641
|
+
return runStartExitCode(result.kind);
|
|
642
|
+
}
|
|
643
|
+
function parseRunLifecycleArgs(args) {
|
|
644
|
+
let planRunId;
|
|
645
|
+
let workspaceFlag;
|
|
646
|
+
let reason;
|
|
647
|
+
let jsonOutput = false;
|
|
648
|
+
for (let i = 0; i < args.length; i++) {
|
|
649
|
+
const a = args[i];
|
|
650
|
+
if (a === '--workspace') {
|
|
651
|
+
const v = args[++i];
|
|
652
|
+
if (!v || v.startsWith('--'))
|
|
653
|
+
return { ok: false, message: '--workspace requires a value' };
|
|
654
|
+
workspaceFlag = v;
|
|
655
|
+
}
|
|
656
|
+
else if (a.startsWith('--workspace=')) {
|
|
657
|
+
workspaceFlag = a.slice('--workspace='.length);
|
|
658
|
+
}
|
|
659
|
+
else if (a === '--reason') {
|
|
660
|
+
const v = args[++i];
|
|
661
|
+
if (v === undefined || v.startsWith('--')) {
|
|
662
|
+
return { ok: false, message: '--reason requires a value' };
|
|
663
|
+
}
|
|
664
|
+
reason = v;
|
|
665
|
+
}
|
|
666
|
+
else if (a.startsWith('--reason=')) {
|
|
667
|
+
reason = a.slice('--reason='.length);
|
|
668
|
+
}
|
|
669
|
+
else if (a === '--json') {
|
|
670
|
+
jsonOutput = true;
|
|
671
|
+
}
|
|
672
|
+
else if (a.startsWith('--')) {
|
|
673
|
+
return { ok: false, message: `unknown option: ${a}` };
|
|
674
|
+
}
|
|
675
|
+
else if (!planRunId) {
|
|
676
|
+
planRunId = a;
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
return { ok: false, message: `unexpected positional argument: ${a}` };
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (!planRunId)
|
|
683
|
+
return { ok: false, message: 'run <verb> requires a planRunId' };
|
|
684
|
+
return { ok: true, planRunId, workspaceFlag, reason, jsonOutput };
|
|
685
|
+
}
|
|
686
|
+
async function runRunLifecycleCmd(verb, args) {
|
|
687
|
+
const parsed = parseRunLifecycleArgs(args);
|
|
688
|
+
if (!parsed.ok) {
|
|
689
|
+
process.stderr.write(`yolo: run ${verb}: ${parsed.message}\n`);
|
|
690
|
+
const reasonHint = verb === 'resume' ? '' : ' [--reason <text>]';
|
|
691
|
+
process.stderr.write(`Usage: yolo run ${verb} <planRunId> [--workspace <wsId>]${reasonHint} [--json]\n`);
|
|
692
|
+
return 64;
|
|
693
|
+
}
|
|
694
|
+
const result = await runRunLifecycle({
|
|
695
|
+
verb,
|
|
696
|
+
planRunId: parsed.planRunId,
|
|
697
|
+
workspaceFlag: parsed.workspaceFlag,
|
|
698
|
+
reason: parsed.reason,
|
|
699
|
+
outputFormat: parsed.jsonOutput ? 'json' : 'summary',
|
|
700
|
+
});
|
|
701
|
+
if (result.ok) {
|
|
702
|
+
process.stdout.write(`${result.output}\n`);
|
|
703
|
+
return 0;
|
|
704
|
+
}
|
|
705
|
+
process.stderr.write(`FAIL [${result.kind}]: ${result.message}\n`);
|
|
706
|
+
return runLifecycleExitCode(result.kind);
|
|
707
|
+
}
|
|
708
|
+
function parseRunGetArgs(args) {
|
|
709
|
+
let planRunId;
|
|
710
|
+
let workspaceFlag;
|
|
711
|
+
let jsonOutput = false;
|
|
712
|
+
for (let i = 0; i < args.length; i++) {
|
|
713
|
+
const a = args[i];
|
|
714
|
+
if (a === '--workspace') {
|
|
715
|
+
const v = args[++i];
|
|
716
|
+
if (!v || v.startsWith('--'))
|
|
717
|
+
return { ok: false, message: '--workspace requires a value' };
|
|
718
|
+
workspaceFlag = v;
|
|
719
|
+
}
|
|
720
|
+
else if (a.startsWith('--workspace=')) {
|
|
721
|
+
workspaceFlag = a.slice('--workspace='.length);
|
|
722
|
+
}
|
|
723
|
+
else if (a === '--json') {
|
|
724
|
+
jsonOutput = true;
|
|
725
|
+
}
|
|
726
|
+
else if (a.startsWith('--')) {
|
|
727
|
+
return { ok: false, message: `unknown option: ${a}` };
|
|
728
|
+
}
|
|
729
|
+
else if (!planRunId) {
|
|
730
|
+
planRunId = a;
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
return { ok: false, message: `unexpected positional argument: ${a}` };
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (!planRunId)
|
|
737
|
+
return { ok: false, message: 'run get requires a planRunId' };
|
|
738
|
+
return { ok: true, planRunId, workspaceFlag, jsonOutput };
|
|
739
|
+
}
|
|
740
|
+
function tileAppUsage(sub, message) {
|
|
741
|
+
process.stderr.write(`yolo tileapp ${sub}: ${message}\n`);
|
|
742
|
+
const usage = {
|
|
743
|
+
init: 'Usage: yolo tileapp init <name> (scaffolds ./<name>/tileapp.json + index.html)\n',
|
|
744
|
+
sign: 'Usage: yolo tileapp sign <manifest.json> --publisher <id> [--key <keyId>] [--stdout]\n',
|
|
745
|
+
publish: 'Usage: yolo tileapp publish <manifest.json> --personal [--bundle-dir <dir>] [--context <dir>] [--dockerfile <f>] [--builder auto|podman|skopeo] (personal app)\n'
|
|
746
|
+
+ ' or: yolo tileapp publish <manifest.json> [--channel beta|stable] [--image-digest <d>] (marketplace)\n',
|
|
747
|
+
validate: 'Usage: yolo tileapp validate <manifest.json> [--bundle-dir <dir>]\n',
|
|
748
|
+
dev: 'Usage: yolo tileapp dev <manifest.json> [--port N] [--host H] [--bundle-dir <dir>] [--deny]\n',
|
|
749
|
+
};
|
|
750
|
+
process.stderr.write(usage[sub]);
|
|
751
|
+
return 64;
|
|
752
|
+
}
|
|
753
|
+
async function runTileAppSignCmd(args) {
|
|
754
|
+
let manifestPath;
|
|
755
|
+
let publisherId;
|
|
756
|
+
let keyId;
|
|
757
|
+
let toStdout = false;
|
|
758
|
+
for (let i = 0; i < args.length; i++) {
|
|
759
|
+
const a = args[i];
|
|
760
|
+
if (a === '--publisher' || a === '-p') {
|
|
761
|
+
publisherId = args[++i];
|
|
762
|
+
if (publisherId === undefined)
|
|
763
|
+
return tileAppUsage('sign', "--publisher requires a value");
|
|
764
|
+
}
|
|
765
|
+
else if (a === '--key') {
|
|
766
|
+
keyId = args[++i];
|
|
767
|
+
if (keyId === undefined)
|
|
768
|
+
return tileAppUsage('sign', '--key requires a value');
|
|
769
|
+
}
|
|
770
|
+
else if (a === '--stdout')
|
|
771
|
+
toStdout = true;
|
|
772
|
+
else if (!a.startsWith('-') && !manifestPath)
|
|
773
|
+
manifestPath = a;
|
|
774
|
+
else
|
|
775
|
+
return tileAppUsage('sign', `unexpected argument '${a}'`);
|
|
776
|
+
}
|
|
777
|
+
if (!manifestPath || !publisherId)
|
|
778
|
+
return tileAppUsage('sign', 'a manifest path and --publisher <id> are required');
|
|
779
|
+
const result = await runTileAppSign({ manifestPath, publisherId, keyId, toStdout });
|
|
780
|
+
if (result.ok) {
|
|
781
|
+
process.stdout.write(`${result.output}\n`);
|
|
782
|
+
return 0;
|
|
783
|
+
}
|
|
784
|
+
process.stderr.write(`FAIL [${result.kind}]: ${result.message}\n`);
|
|
785
|
+
return tileAppExitCode(result.kind);
|
|
786
|
+
}
|
|
787
|
+
async function runTileAppPublishCmd(args) {
|
|
788
|
+
let manifestPath;
|
|
789
|
+
let channel;
|
|
790
|
+
let imageDigest;
|
|
791
|
+
let bundleDir;
|
|
792
|
+
let context;
|
|
793
|
+
let dockerfile;
|
|
794
|
+
let builder;
|
|
795
|
+
let personal = false;
|
|
796
|
+
for (let i = 0; i < args.length; i++) {
|
|
797
|
+
const a = args[i];
|
|
798
|
+
if (a === '--personal' || a === '--private') {
|
|
799
|
+
personal = true;
|
|
800
|
+
}
|
|
801
|
+
else if (a === '--bundle-dir') {
|
|
802
|
+
bundleDir = args[++i];
|
|
803
|
+
if (bundleDir === undefined)
|
|
804
|
+
return tileAppUsage('publish', '--bundle-dir requires a value');
|
|
805
|
+
}
|
|
806
|
+
else if (a === '--context') {
|
|
807
|
+
context = args[++i];
|
|
808
|
+
if (context === undefined)
|
|
809
|
+
return tileAppUsage('publish', '--context requires a value');
|
|
810
|
+
}
|
|
811
|
+
else if (a === '--dockerfile') {
|
|
812
|
+
dockerfile = args[++i];
|
|
813
|
+
if (dockerfile === undefined)
|
|
814
|
+
return tileAppUsage('publish', '--dockerfile requires a value');
|
|
815
|
+
}
|
|
816
|
+
else if (a === '--builder') {
|
|
817
|
+
const v = args[++i];
|
|
818
|
+
if (v !== 'auto' && v !== 'podman' && v !== 'skopeo')
|
|
819
|
+
return tileAppUsage('publish', `invalid --builder '${v ?? ''}' (expected auto|podman|skopeo)`);
|
|
820
|
+
builder = v;
|
|
821
|
+
}
|
|
822
|
+
else if (a === '--channel') {
|
|
823
|
+
const v = args[++i];
|
|
824
|
+
if (v !== 'beta' && v !== 'stable')
|
|
825
|
+
return tileAppUsage('publish', `invalid --channel '${v ?? ''}' (expected beta|stable)`);
|
|
826
|
+
channel = v;
|
|
827
|
+
}
|
|
828
|
+
else if (a === '--image-digest') {
|
|
829
|
+
imageDigest = args[++i];
|
|
830
|
+
if (imageDigest === undefined)
|
|
831
|
+
return tileAppUsage('publish', '--image-digest requires a value');
|
|
832
|
+
}
|
|
833
|
+
else if (!a.startsWith('-') && !manifestPath)
|
|
834
|
+
manifestPath = a;
|
|
835
|
+
else
|
|
836
|
+
return tileAppUsage('publish', `unexpected argument '${a}'`);
|
|
837
|
+
}
|
|
838
|
+
if (!manifestPath)
|
|
839
|
+
return tileAppUsage('publish', 'a manifest path is required');
|
|
840
|
+
// Personal path: register under the caller's own namespace + upload the
|
|
841
|
+
// bundle (no signing/review). Distinct from the marketplace publish below.
|
|
842
|
+
if (personal) {
|
|
843
|
+
const { runTileAppPublishPersonal, exitCodeForFailure } = await import('./tileapp-personal.js');
|
|
844
|
+
const result = await runTileAppPublishPersonal({ manifestPath, bundleDir, context, dockerfile, builder });
|
|
845
|
+
if (result.ok) {
|
|
846
|
+
process.stdout.write(`${result.output}\n`);
|
|
847
|
+
return 0;
|
|
848
|
+
}
|
|
849
|
+
process.stderr.write(`FAIL [${result.kind}]: ${result.message}\n`);
|
|
850
|
+
return exitCodeForFailure(result.kind);
|
|
851
|
+
}
|
|
852
|
+
if (bundleDir || context || dockerfile || builder)
|
|
853
|
+
return tileAppUsage('publish', '--bundle-dir/--context/--dockerfile/--builder are only valid with --personal');
|
|
854
|
+
const result = await runTileAppPublish({ manifestPath, channel, imageDigest });
|
|
855
|
+
if (result.ok) {
|
|
856
|
+
process.stdout.write(`${result.output}\n`);
|
|
857
|
+
return 0;
|
|
858
|
+
}
|
|
859
|
+
process.stderr.write(`FAIL [${result.kind}]: ${result.message}\n`);
|
|
860
|
+
return tileAppExitCode(result.kind);
|
|
861
|
+
}
|
|
862
|
+
async function runTileAppInitCmd(args) {
|
|
863
|
+
let name;
|
|
864
|
+
for (let i = 0; i < args.length; i++) {
|
|
865
|
+
const a = args[i];
|
|
866
|
+
if (!a.startsWith('-') && !name)
|
|
867
|
+
name = a;
|
|
868
|
+
else
|
|
869
|
+
return tileAppUsage('init', `unexpected argument '${a}'`);
|
|
870
|
+
}
|
|
871
|
+
if (!name)
|
|
872
|
+
return tileAppUsage('init', 'an app name (slug) is required');
|
|
873
|
+
const { runTileAppInit, exitCodeForFailure } = await import('./tileapp-personal.js');
|
|
874
|
+
const result = runTileAppInit({ name });
|
|
875
|
+
if (result.ok) {
|
|
876
|
+
process.stdout.write(`${result.output}\n`);
|
|
877
|
+
return 0;
|
|
878
|
+
}
|
|
879
|
+
process.stderr.write(`FAIL [${result.kind}]: ${result.message}\n`);
|
|
880
|
+
return exitCodeForFailure(result.kind);
|
|
881
|
+
}
|
|
882
|
+
async function runTileAppValidateCmd(args) {
|
|
883
|
+
const { runTileAppValidate, devExitCode } = await import('./tileapp-developer.js');
|
|
884
|
+
let manifestPath;
|
|
885
|
+
let bundleDir;
|
|
886
|
+
for (let i = 0; i < args.length; i++) {
|
|
887
|
+
const a = args[i];
|
|
888
|
+
if (a === '--bundle-dir') {
|
|
889
|
+
bundleDir = args[++i];
|
|
890
|
+
if (bundleDir === undefined)
|
|
891
|
+
return tileAppUsage('validate', '--bundle-dir requires a value');
|
|
892
|
+
}
|
|
893
|
+
else if (!a.startsWith('-') && !manifestPath)
|
|
894
|
+
manifestPath = a;
|
|
895
|
+
else
|
|
896
|
+
return tileAppUsage('validate', `unexpected argument '${a}'`);
|
|
897
|
+
}
|
|
898
|
+
if (!manifestPath)
|
|
899
|
+
return tileAppUsage('validate', 'a manifest path is required');
|
|
900
|
+
const result = runTileAppValidate({ manifestPath, bundleDir });
|
|
901
|
+
if (result.ok) {
|
|
902
|
+
process.stdout.write(`${result.output}\n`);
|
|
903
|
+
return 0;
|
|
904
|
+
}
|
|
905
|
+
process.stderr.write(`FAIL [${result.kind}]: ${result.message}\n`);
|
|
906
|
+
return devExitCode(result.kind);
|
|
907
|
+
}
|
|
908
|
+
async function runTileAppDevCmd(args) {
|
|
909
|
+
const { runTileAppDev } = await import('./tileapp-developer.js');
|
|
910
|
+
let manifestPath;
|
|
911
|
+
let bundleDir;
|
|
912
|
+
let host = '127.0.0.1';
|
|
913
|
+
let port;
|
|
914
|
+
let allow = true;
|
|
915
|
+
for (let i = 0; i < args.length; i++) {
|
|
916
|
+
const a = args[i];
|
|
917
|
+
if (a === '--bundle-dir') {
|
|
918
|
+
bundleDir = args[++i];
|
|
919
|
+
if (bundleDir === undefined)
|
|
920
|
+
return tileAppUsage('dev', '--bundle-dir requires a value');
|
|
921
|
+
}
|
|
922
|
+
else if (a === '--port' || a === '-p') {
|
|
923
|
+
const v = args[++i];
|
|
924
|
+
const n = Number(v);
|
|
925
|
+
if (!Number.isInteger(n) || n < 1 || n > 65535)
|
|
926
|
+
return tileAppUsage('dev', `invalid --port '${v ?? ''}'`);
|
|
927
|
+
port = n;
|
|
928
|
+
}
|
|
929
|
+
else if (a === '--host') {
|
|
930
|
+
host = args[++i] ?? host;
|
|
931
|
+
}
|
|
932
|
+
else if (a === '--deny')
|
|
933
|
+
allow = false;
|
|
934
|
+
else if (!a.startsWith('-') && !manifestPath)
|
|
935
|
+
manifestPath = a;
|
|
936
|
+
else
|
|
937
|
+
return tileAppUsage('dev', `unexpected argument '${a}'`);
|
|
938
|
+
}
|
|
939
|
+
if (!manifestPath)
|
|
940
|
+
return tileAppUsage('dev', 'a manifest path is required');
|
|
941
|
+
if (port === undefined) {
|
|
942
|
+
const envPort = Number(process.env.PORT);
|
|
943
|
+
port = Number.isInteger(envPort) && envPort >= 1 && envPort <= 65535 ? envPort : 3000;
|
|
944
|
+
}
|
|
945
|
+
return runTileAppDev({ manifestPath, port, host, bundleDir, allow });
|
|
946
|
+
}
|
|
947
|
+
async function runRunGetCmd(args) {
|
|
948
|
+
const parsed = parseRunGetArgs(args);
|
|
949
|
+
if (!parsed.ok) {
|
|
950
|
+
process.stderr.write(`yolo: run get: ${parsed.message}\n`);
|
|
951
|
+
process.stderr.write('Usage: yolo run get <planRunId> [--workspace <wsId>] [--json]\n');
|
|
952
|
+
return 64;
|
|
953
|
+
}
|
|
954
|
+
const result = await runRunGet({
|
|
955
|
+
planRunId: parsed.planRunId,
|
|
956
|
+
workspaceFlag: parsed.workspaceFlag,
|
|
957
|
+
outputFormat: parsed.jsonOutput ? 'json' : 'summary',
|
|
958
|
+
});
|
|
959
|
+
if (result.ok) {
|
|
960
|
+
process.stdout.write(`${result.output}\n`);
|
|
961
|
+
return 0;
|
|
962
|
+
}
|
|
963
|
+
process.stderr.write(`FAIL [${result.kind}]: ${result.message}\n`);
|
|
964
|
+
return runGetExitCode(result.kind);
|
|
965
|
+
}
|
|
966
|
+
function parseRunListArgs(args) {
|
|
967
|
+
let workspaceFlag;
|
|
968
|
+
let stateFilter;
|
|
969
|
+
let jsonOutput = false;
|
|
970
|
+
for (let i = 0; i < args.length; i++) {
|
|
971
|
+
const a = args[i];
|
|
972
|
+
if (a === '--workspace') {
|
|
973
|
+
const v = args[++i];
|
|
974
|
+
if (!v || v.startsWith('--'))
|
|
975
|
+
return { ok: false, message: '--workspace requires a value' };
|
|
976
|
+
workspaceFlag = v;
|
|
977
|
+
}
|
|
978
|
+
else if (a.startsWith('--workspace=')) {
|
|
979
|
+
workspaceFlag = a.slice('--workspace='.length);
|
|
980
|
+
}
|
|
981
|
+
else if (a === '--state') {
|
|
982
|
+
const v = args[++i];
|
|
983
|
+
if (!v || v.startsWith('--'))
|
|
984
|
+
return { ok: false, message: '--state requires a value' };
|
|
985
|
+
stateFilter = v;
|
|
986
|
+
}
|
|
987
|
+
else if (a.startsWith('--state=')) {
|
|
988
|
+
stateFilter = a.slice('--state='.length);
|
|
989
|
+
}
|
|
990
|
+
else if (a === '--json') {
|
|
991
|
+
jsonOutput = true;
|
|
992
|
+
}
|
|
993
|
+
else if (a.startsWith('--')) {
|
|
994
|
+
return { ok: false, message: `unknown option: ${a}` };
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
return { ok: false, message: `unexpected positional argument: ${a}` };
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return { ok: true, workspaceFlag, stateFilter, jsonOutput };
|
|
1001
|
+
}
|
|
1002
|
+
async function runRunListCmd(args) {
|
|
1003
|
+
const parsed = parseRunListArgs(args);
|
|
1004
|
+
if (!parsed.ok) {
|
|
1005
|
+
process.stderr.write(`yolo: run list: ${parsed.message}\n`);
|
|
1006
|
+
process.stderr.write('Usage: yolo run list [--workspace <wsId>] [--state <pending|running|paused|succeeded|failed|cancelled|superseded>] [--json]\n');
|
|
1007
|
+
return 64;
|
|
1008
|
+
}
|
|
1009
|
+
const result = await runRunList({
|
|
1010
|
+
workspaceFlag: parsed.workspaceFlag,
|
|
1011
|
+
stateFilter: parsed.stateFilter,
|
|
1012
|
+
outputFormat: parsed.jsonOutput ? 'json' : 'summary',
|
|
1013
|
+
});
|
|
1014
|
+
if (result.ok) {
|
|
1015
|
+
process.stdout.write(`${result.output}\n`);
|
|
1016
|
+
return 0;
|
|
1017
|
+
}
|
|
1018
|
+
process.stderr.write(`FAIL [${result.kind}]: ${result.message}\n`);
|
|
1019
|
+
return runListExitCode(result.kind);
|
|
1020
|
+
}
|
|
1021
|
+
function parseRunTransferArgs(args) {
|
|
1022
|
+
let planRunId;
|
|
1023
|
+
let workspaceFlag;
|
|
1024
|
+
let toFlag;
|
|
1025
|
+
let userDriven = false;
|
|
1026
|
+
let jsonOutput = false;
|
|
1027
|
+
for (let i = 0; i < args.length; i++) {
|
|
1028
|
+
const a = args[i];
|
|
1029
|
+
if (a === '--workspace') {
|
|
1030
|
+
const v = args[++i];
|
|
1031
|
+
if (!v || v.startsWith('--'))
|
|
1032
|
+
return { ok: false, message: '--workspace requires a value' };
|
|
1033
|
+
workspaceFlag = v;
|
|
1034
|
+
}
|
|
1035
|
+
else if (a.startsWith('--workspace=')) {
|
|
1036
|
+
workspaceFlag = a.slice('--workspace='.length);
|
|
1037
|
+
}
|
|
1038
|
+
else if (a === '--to') {
|
|
1039
|
+
const v = args[++i];
|
|
1040
|
+
if (!v || v.startsWith('--')) {
|
|
1041
|
+
// Without the `--` guard, `--to --user-driven` and `--to --json`
|
|
1042
|
+
// silently consume the next flag as the agentId, bypassing the
|
|
1043
|
+
// mutex / boolean check and triggering a network round-trip with
|
|
1044
|
+
// a bogus target. Use the `--to=<value>` form for values that
|
|
1045
|
+
// legitimately start with `--`.
|
|
1046
|
+
return { ok: false, message: '--to requires an agentId value' };
|
|
1047
|
+
}
|
|
1048
|
+
toFlag = v;
|
|
1049
|
+
}
|
|
1050
|
+
else if (a.startsWith('--to=')) {
|
|
1051
|
+
toFlag = a.slice('--to='.length);
|
|
1052
|
+
}
|
|
1053
|
+
else if (a === '--user-driven') {
|
|
1054
|
+
userDriven = true;
|
|
1055
|
+
}
|
|
1056
|
+
else if (a === '--json') {
|
|
1057
|
+
jsonOutput = true;
|
|
1058
|
+
}
|
|
1059
|
+
else if (a.startsWith('--')) {
|
|
1060
|
+
return { ok: false, message: `unknown option: ${a}` };
|
|
1061
|
+
}
|
|
1062
|
+
else if (!planRunId) {
|
|
1063
|
+
planRunId = a;
|
|
1064
|
+
}
|
|
1065
|
+
else {
|
|
1066
|
+
return { ok: false, message: `unexpected positional argument: ${a}` };
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
if (!planRunId)
|
|
1070
|
+
return { ok: false, message: 'run transfer requires a planRunId' };
|
|
1071
|
+
if (toFlag !== undefined && userDriven) {
|
|
1072
|
+
return { ok: false, message: '--to and --user-driven are mutually exclusive' };
|
|
1073
|
+
}
|
|
1074
|
+
if (toFlag === undefined && !userDriven) {
|
|
1075
|
+
return { ok: false, message: 'one of --to <agentId> or --user-driven is required' };
|
|
1076
|
+
}
|
|
1077
|
+
return {
|
|
1078
|
+
ok: true,
|
|
1079
|
+
planRunId,
|
|
1080
|
+
newOperatorAgentId: userDriven ? null : toFlag,
|
|
1081
|
+
workspaceFlag,
|
|
1082
|
+
jsonOutput,
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
async function runRunTransferCmd(args) {
|
|
1086
|
+
const parsed = parseRunTransferArgs(args);
|
|
1087
|
+
if (!parsed.ok) {
|
|
1088
|
+
process.stderr.write(`yolo: run transfer: ${parsed.message}\n`);
|
|
1089
|
+
process.stderr.write('Usage: yolo run transfer <planRunId> (--to <agentId> | --user-driven) [--workspace <wsId>] [--json]\n');
|
|
1090
|
+
return 64;
|
|
1091
|
+
}
|
|
1092
|
+
const result = await runRunTransfer({
|
|
1093
|
+
planRunId: parsed.planRunId,
|
|
1094
|
+
newOperatorAgentId: parsed.newOperatorAgentId,
|
|
1095
|
+
workspaceFlag: parsed.workspaceFlag,
|
|
1096
|
+
outputFormat: parsed.jsonOutput ? 'json' : 'summary',
|
|
1097
|
+
});
|
|
1098
|
+
if (result.ok) {
|
|
1099
|
+
process.stdout.write(`${result.output}\n`);
|
|
1100
|
+
return 0;
|
|
1101
|
+
}
|
|
1102
|
+
process.stderr.write(`FAIL [${result.kind}]: ${result.message}\n`);
|
|
1103
|
+
return runTransferExitCode(result.kind);
|
|
1104
|
+
}
|
|
1105
|
+
function parseArtifactGetArgs(args) {
|
|
1106
|
+
let key;
|
|
1107
|
+
let version;
|
|
1108
|
+
let workspaceFlag;
|
|
1109
|
+
let contentOutput = false;
|
|
1110
|
+
let jsonOutput = false;
|
|
1111
|
+
for (let i = 0; i < args.length; i++) {
|
|
1112
|
+
const a = args[i];
|
|
1113
|
+
if (a === '--workspace') {
|
|
1114
|
+
const v = args[++i];
|
|
1115
|
+
if (!v || v.startsWith('--'))
|
|
1116
|
+
return { ok: false, message: '--workspace requires a value' };
|
|
1117
|
+
workspaceFlag = v;
|
|
1118
|
+
}
|
|
1119
|
+
else if (a.startsWith('--workspace=')) {
|
|
1120
|
+
workspaceFlag = a.slice('--workspace='.length);
|
|
1121
|
+
}
|
|
1122
|
+
else if (a === '--version') {
|
|
1123
|
+
const v = args[++i];
|
|
1124
|
+
if (!v || v.startsWith('--'))
|
|
1125
|
+
return { ok: false, message: '--version requires an integer value' };
|
|
1126
|
+
const n = Number(v);
|
|
1127
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
1128
|
+
return { ok: false, message: `--version must be a positive integer (got '${v}')` };
|
|
1129
|
+
}
|
|
1130
|
+
version = n;
|
|
1131
|
+
}
|
|
1132
|
+
else if (a.startsWith('--version=')) {
|
|
1133
|
+
const raw = a.slice('--version='.length);
|
|
1134
|
+
const n = Number(raw);
|
|
1135
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
1136
|
+
return { ok: false, message: `--version must be a positive integer (got '${raw}')` };
|
|
1137
|
+
}
|
|
1138
|
+
version = n;
|
|
1139
|
+
}
|
|
1140
|
+
else if (a === '--content') {
|
|
1141
|
+
contentOutput = true;
|
|
1142
|
+
}
|
|
1143
|
+
else if (a === '--json') {
|
|
1144
|
+
jsonOutput = true;
|
|
1145
|
+
}
|
|
1146
|
+
else if (a.startsWith('--')) {
|
|
1147
|
+
return { ok: false, message: `unknown option: ${a}` };
|
|
1148
|
+
}
|
|
1149
|
+
else if (!key) {
|
|
1150
|
+
key = a;
|
|
1151
|
+
}
|
|
1152
|
+
else {
|
|
1153
|
+
return { ok: false, message: `unexpected positional argument: ${a}` };
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
if (!key)
|
|
1157
|
+
return { ok: false, message: 'artifact get requires a key' };
|
|
1158
|
+
if (contentOutput && jsonOutput) {
|
|
1159
|
+
return { ok: false, message: '--content and --json are mutually exclusive' };
|
|
1160
|
+
}
|
|
1161
|
+
return { ok: true, key, version, workspaceFlag, contentOutput, jsonOutput };
|
|
1162
|
+
}
|
|
1163
|
+
async function runArtifactGetCmd(args) {
|
|
1164
|
+
const parsed = parseArtifactGetArgs(args);
|
|
1165
|
+
if (!parsed.ok) {
|
|
1166
|
+
process.stderr.write(`yolo: artifact get: ${parsed.message}\n`);
|
|
1167
|
+
process.stderr.write('Usage: yolo artifact get <key> [--version <n>] [--workspace <wsId>] [--content | --json]\n');
|
|
1168
|
+
return 64;
|
|
1169
|
+
}
|
|
1170
|
+
const outputFormat = parsed.jsonOutput ? 'json' : parsed.contentOutput ? 'content' : 'summary';
|
|
1171
|
+
const result = await runArtifactGet({
|
|
1172
|
+
key: parsed.key,
|
|
1173
|
+
version: parsed.version,
|
|
1174
|
+
workspaceFlag: parsed.workspaceFlag,
|
|
1175
|
+
outputFormat,
|
|
1176
|
+
});
|
|
1177
|
+
if (result.ok) {
|
|
1178
|
+
// For --content, the body might be binary-ish text we don't want a trailing newline on.
|
|
1179
|
+
// Stdout the content verbatim; the user can pipe it to a file.
|
|
1180
|
+
if (outputFormat === 'content') {
|
|
1181
|
+
process.stdout.write(result.output);
|
|
1182
|
+
}
|
|
1183
|
+
else {
|
|
1184
|
+
process.stdout.write(`${result.output}\n`);
|
|
1185
|
+
}
|
|
1186
|
+
return 0;
|
|
1187
|
+
}
|
|
1188
|
+
process.stderr.write(`FAIL [${result.kind}]: ${result.message}\n`);
|
|
1189
|
+
return artifactGetExitCode(result.kind);
|
|
1190
|
+
}
|
|
1191
|
+
function parseArtifactListArgs(args) {
|
|
1192
|
+
let workspaceFlag;
|
|
1193
|
+
let prefix;
|
|
1194
|
+
let limit;
|
|
1195
|
+
let jsonOutput = false;
|
|
1196
|
+
for (let i = 0; i < args.length; i++) {
|
|
1197
|
+
const a = args[i];
|
|
1198
|
+
if (a === '--workspace') {
|
|
1199
|
+
const v = args[++i];
|
|
1200
|
+
if (!v || v.startsWith('--'))
|
|
1201
|
+
return { ok: false, message: '--workspace requires a value' };
|
|
1202
|
+
workspaceFlag = v;
|
|
1203
|
+
}
|
|
1204
|
+
else if (a.startsWith('--workspace=')) {
|
|
1205
|
+
workspaceFlag = a.slice('--workspace='.length);
|
|
1206
|
+
}
|
|
1207
|
+
else if (a === '--prefix') {
|
|
1208
|
+
const v = args[++i];
|
|
1209
|
+
if (!v || v.startsWith('--'))
|
|
1210
|
+
return { ok: false, message: '--prefix requires a value' };
|
|
1211
|
+
prefix = v;
|
|
1212
|
+
}
|
|
1213
|
+
else if (a.startsWith('--prefix=')) {
|
|
1214
|
+
prefix = a.slice('--prefix='.length);
|
|
1215
|
+
}
|
|
1216
|
+
else if (a === '--limit') {
|
|
1217
|
+
const v = args[++i];
|
|
1218
|
+
if (!v || v.startsWith('--'))
|
|
1219
|
+
return { ok: false, message: '--limit requires an integer value' };
|
|
1220
|
+
const n = Number(v);
|
|
1221
|
+
if (!Number.isInteger(n) || n < 1 || n > 500) {
|
|
1222
|
+
return { ok: false, message: `--limit must be an integer between 1 and 500 (got '${v}')` };
|
|
1223
|
+
}
|
|
1224
|
+
limit = n;
|
|
1225
|
+
}
|
|
1226
|
+
else if (a.startsWith('--limit=')) {
|
|
1227
|
+
const raw = a.slice('--limit='.length);
|
|
1228
|
+
const n = Number(raw);
|
|
1229
|
+
if (!Number.isInteger(n) || n < 1 || n > 500) {
|
|
1230
|
+
return { ok: false, message: `--limit must be an integer between 1 and 500 (got '${raw}')` };
|
|
1231
|
+
}
|
|
1232
|
+
limit = n;
|
|
1233
|
+
}
|
|
1234
|
+
else if (a === '--json') {
|
|
1235
|
+
jsonOutput = true;
|
|
1236
|
+
}
|
|
1237
|
+
else if (a.startsWith('--')) {
|
|
1238
|
+
return { ok: false, message: `unknown option: ${a}` };
|
|
1239
|
+
}
|
|
1240
|
+
else {
|
|
1241
|
+
return { ok: false, message: `unexpected positional argument: ${a}` };
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
return { ok: true, workspaceFlag, prefix, limit, jsonOutput };
|
|
1245
|
+
}
|
|
1246
|
+
async function runArtifactListCmd(args) {
|
|
1247
|
+
const parsed = parseArtifactListArgs(args);
|
|
1248
|
+
if (!parsed.ok) {
|
|
1249
|
+
process.stderr.write(`yolo: artifact list: ${parsed.message}\n`);
|
|
1250
|
+
process.stderr.write('Usage: yolo artifact list [--prefix <prefix>] [--limit <n>] [--workspace <wsId>] [--json]\n');
|
|
1251
|
+
return 64;
|
|
1252
|
+
}
|
|
1253
|
+
const result = await runArtifactList({
|
|
1254
|
+
workspaceFlag: parsed.workspaceFlag,
|
|
1255
|
+
prefix: parsed.prefix,
|
|
1256
|
+
limit: parsed.limit,
|
|
1257
|
+
outputFormat: parsed.jsonOutput ? 'json' : 'summary',
|
|
1258
|
+
});
|
|
1259
|
+
if (result.ok) {
|
|
1260
|
+
process.stdout.write(`${result.output}\n`);
|
|
1261
|
+
return 0;
|
|
1262
|
+
}
|
|
1263
|
+
process.stderr.write(`FAIL [${result.kind}]: ${result.message}\n`);
|
|
1264
|
+
return artifactListExitCode(result.kind);
|
|
1265
|
+
}
|
|
1266
|
+
async function main(argv) {
|
|
1267
|
+
const [, , ...args] = argv;
|
|
1268
|
+
const cmd = args[0];
|
|
1269
|
+
if (!cmd || cmd === '--help' || cmd === '-h') {
|
|
1270
|
+
printHelp();
|
|
1271
|
+
return 0;
|
|
1272
|
+
}
|
|
1273
|
+
if (cmd === '--version' || cmd === '-v') {
|
|
1274
|
+
process.stdout.write(`${PKG_VERSION}\n`);
|
|
1275
|
+
return 0;
|
|
1276
|
+
}
|
|
1277
|
+
if (cmd === 'context' || cmd === 'env' || cmd === 'env-doctor') {
|
|
1278
|
+
try {
|
|
1279
|
+
const ctx = readSessionContext();
|
|
1280
|
+
process.stdout.write(formatContext(ctx));
|
|
1281
|
+
return 0;
|
|
1282
|
+
}
|
|
1283
|
+
catch (err) {
|
|
1284
|
+
if (err instanceof ContextResolutionError) {
|
|
1285
|
+
process.stderr.write(`yolo: ${err.message}\n`);
|
|
1286
|
+
return err.exitCode;
|
|
1287
|
+
}
|
|
1288
|
+
throw err;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
if (cmd === 'plan') {
|
|
1292
|
+
const sub = args[1];
|
|
1293
|
+
if (sub === 'validate') {
|
|
1294
|
+
return runPlanValidate(args.slice(2));
|
|
1295
|
+
}
|
|
1296
|
+
if (sub === 'import') {
|
|
1297
|
+
return runPlanImportCmd(args.slice(2));
|
|
1298
|
+
}
|
|
1299
|
+
if (sub === 'export') {
|
|
1300
|
+
return runPlanExportCmd(args.slice(2));
|
|
1301
|
+
}
|
|
1302
|
+
if (sub === 'activate') {
|
|
1303
|
+
return runPlanStateCmd('activate', args.slice(2));
|
|
1304
|
+
}
|
|
1305
|
+
if (sub === 'archive') {
|
|
1306
|
+
return runPlanStateCmd('archive', args.slice(2));
|
|
1307
|
+
}
|
|
1308
|
+
if (sub === 'get') {
|
|
1309
|
+
return runPlanGetCmd(args.slice(2));
|
|
1310
|
+
}
|
|
1311
|
+
if (sub === 'list') {
|
|
1312
|
+
return runPlanListCmd(args.slice(2));
|
|
1313
|
+
}
|
|
1314
|
+
if (sub === 'open') {
|
|
1315
|
+
return runPlanOpenCmd(args.slice(2));
|
|
1316
|
+
}
|
|
1317
|
+
if (!sub) {
|
|
1318
|
+
process.stderr.write('yolo: plan requires a subcommand (validate, import, export, activate, archive, get, list, open)\n');
|
|
1319
|
+
return 64;
|
|
1320
|
+
}
|
|
1321
|
+
process.stderr.write(`yolo: unknown plan subcommand '${sub}'\n`);
|
|
1322
|
+
process.stderr.write('Subcommands: validate, import, export, activate, archive, get, list, open\n');
|
|
1323
|
+
return 64;
|
|
1324
|
+
}
|
|
1325
|
+
if (cmd === 'run') {
|
|
1326
|
+
const sub = args[1];
|
|
1327
|
+
if (sub === 'start') {
|
|
1328
|
+
return runRunStartCmd(args.slice(2));
|
|
1329
|
+
}
|
|
1330
|
+
if (sub === 'get') {
|
|
1331
|
+
return runRunGetCmd(args.slice(2));
|
|
1332
|
+
}
|
|
1333
|
+
if (sub === 'list') {
|
|
1334
|
+
return runRunListCmd(args.slice(2));
|
|
1335
|
+
}
|
|
1336
|
+
if (sub === 'pause' || sub === 'resume' || sub === 'cancel') {
|
|
1337
|
+
return runRunLifecycleCmd(sub, args.slice(2));
|
|
1338
|
+
}
|
|
1339
|
+
if (sub === 'transfer') {
|
|
1340
|
+
return runRunTransferCmd(args.slice(2));
|
|
1341
|
+
}
|
|
1342
|
+
if (!sub) {
|
|
1343
|
+
process.stderr.write('yolo: run requires a subcommand (start, get, list, pause, resume, cancel, transfer)\n');
|
|
1344
|
+
return 64;
|
|
1345
|
+
}
|
|
1346
|
+
process.stderr.write(`yolo: unknown run subcommand '${sub}'\n`);
|
|
1347
|
+
process.stderr.write('Subcommands: start, get, list, pause, resume, cancel, transfer\n');
|
|
1348
|
+
return 64;
|
|
1349
|
+
}
|
|
1350
|
+
if (cmd === 'artifact') {
|
|
1351
|
+
const sub = args[1];
|
|
1352
|
+
if (sub === 'get') {
|
|
1353
|
+
return runArtifactGetCmd(args.slice(2));
|
|
1354
|
+
}
|
|
1355
|
+
if (sub === 'list') {
|
|
1356
|
+
return runArtifactListCmd(args.slice(2));
|
|
1357
|
+
}
|
|
1358
|
+
if (!sub) {
|
|
1359
|
+
process.stderr.write('yolo: artifact requires a subcommand (get, list)\n');
|
|
1360
|
+
return 64;
|
|
1361
|
+
}
|
|
1362
|
+
process.stderr.write(`yolo: unknown artifact subcommand '${sub}'\n`);
|
|
1363
|
+
process.stderr.write('Subcommands: get, list\n');
|
|
1364
|
+
return 64;
|
|
1365
|
+
}
|
|
1366
|
+
if (cmd === 'tileapp') {
|
|
1367
|
+
const sub = args[1];
|
|
1368
|
+
if (sub === 'init')
|
|
1369
|
+
return runTileAppInitCmd(args.slice(2));
|
|
1370
|
+
if (sub === 'sign')
|
|
1371
|
+
return runTileAppSignCmd(args.slice(2));
|
|
1372
|
+
if (sub === 'publish')
|
|
1373
|
+
return runTileAppPublishCmd(args.slice(2));
|
|
1374
|
+
if (sub === 'validate')
|
|
1375
|
+
return runTileAppValidateCmd(args.slice(2));
|
|
1376
|
+
if (sub === 'dev')
|
|
1377
|
+
return runTileAppDevCmd(args.slice(2));
|
|
1378
|
+
if (!sub) {
|
|
1379
|
+
process.stderr.write('yolo: tileapp requires a subcommand (init, validate, dev, sign, publish)\n');
|
|
1380
|
+
return 64;
|
|
1381
|
+
}
|
|
1382
|
+
process.stderr.write(`yolo: unknown tileapp subcommand '${sub}'\n`);
|
|
1383
|
+
process.stderr.write('Subcommands: init, validate, dev, sign, publish\n');
|
|
1384
|
+
return 64;
|
|
1385
|
+
}
|
|
1386
|
+
if (cmd === 'serve') {
|
|
1387
|
+
const { runServeCmd } = await import('./serve.js');
|
|
1388
|
+
return runServeCmd(args.slice(1));
|
|
1389
|
+
}
|
|
1390
|
+
if (cmd === 'deploy') {
|
|
1391
|
+
// Lazy-load (serve precedent) so the deploy stack — incl. esbuild via
|
|
1392
|
+
// deploy-bundle — never taxes `yolo plan` startup.
|
|
1393
|
+
const { runDeployCmd } = await import('./deploy-cli.js');
|
|
1394
|
+
return runDeployCmd(args.slice(1));
|
|
1395
|
+
}
|
|
1396
|
+
process.stderr.write(`yolo: unknown command '${cmd}'\n`);
|
|
1397
|
+
printHelp();
|
|
1398
|
+
return 64; // EX_USAGE
|
|
1399
|
+
}
|
|
1400
|
+
main(process.argv).then((code) => process.exit(code), (err) => {
|
|
1401
|
+
process.stderr.write(`yolo: unexpected error: ${err?.message ?? err}\n`);
|
|
1402
|
+
process.exit(1);
|
|
1403
|
+
});
|