@assethub/cli 0.1.6 → 0.1.7

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.
Files changed (50) hide show
  1. package/README.md +349 -5
  2. package/dist/autopilot.d.ts +13 -0
  3. package/dist/autopilot.d.ts.map +1 -0
  4. package/dist/autopilot.js +158 -0
  5. package/dist/canvas.d.ts +27 -0
  6. package/dist/canvas.d.ts.map +1 -0
  7. package/dist/canvas.js +82 -0
  8. package/dist/canvasAssetImport.d.ts +45 -0
  9. package/dist/canvasAssetImport.d.ts.map +1 -0
  10. package/dist/canvasAssetImport.js +154 -0
  11. package/dist/canvasComparison.d.ts +21 -0
  12. package/dist/canvasComparison.d.ts.map +1 -0
  13. package/dist/canvasComparison.js +58 -0
  14. package/dist/execution.d.ts +175 -0
  15. package/dist/execution.d.ts.map +1 -0
  16. package/dist/execution.js +234 -0
  17. package/dist/index.d.ts +94 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1579 -189
  20. package/dist/projectSources.d.ts +65 -0
  21. package/dist/projectSources.d.ts.map +1 -0
  22. package/dist/projectSources.js +369 -0
  23. package/dist/projectSourcesWorkbook.d.ts +3 -0
  24. package/dist/projectSourcesWorkbook.d.ts.map +1 -0
  25. package/dist/projectSourcesWorkbook.js +143 -0
  26. package/dist/runsUpload/artifactGraphModel.d.ts +48 -0
  27. package/dist/runsUpload/artifactGraphModel.d.ts.map +1 -0
  28. package/dist/runsUpload/artifactGraphModel.js +127 -0
  29. package/dist/runsUpload/blobRefs.d.ts +13 -0
  30. package/dist/runsUpload/blobRefs.d.ts.map +1 -0
  31. package/dist/runsUpload/blobRefs.js +73 -0
  32. package/dist/runsUpload/buildRunUpload.d.ts +63 -0
  33. package/dist/runsUpload/buildRunUpload.d.ts.map +1 -0
  34. package/dist/runsUpload/buildRunUpload.js +428 -0
  35. package/dist/runsUpload/canonicalJson.d.ts +5 -0
  36. package/dist/runsUpload/canonicalJson.d.ts.map +1 -0
  37. package/dist/runsUpload/canonicalJson.js +108 -0
  38. package/dist/runsUpload/controlEndpoints.d.ts +63 -0
  39. package/dist/runsUpload/controlEndpoints.d.ts.map +1 -0
  40. package/dist/runsUpload/controlEndpoints.js +64 -0
  41. package/dist/runsUpload/graphFolder.d.ts +25 -0
  42. package/dist/runsUpload/graphFolder.d.ts.map +1 -0
  43. package/dist/runsUpload/graphFolder.js +147 -0
  44. package/dist/runsUpload/runUploadError.d.ts +23 -0
  45. package/dist/runsUpload/runUploadError.d.ts.map +1 -0
  46. package/dist/runsUpload/runUploadError.js +24 -0
  47. package/dist/runsUpload/uploadRun.d.ts +43 -0
  48. package/dist/runsUpload/uploadRun.d.ts.map +1 -0
  49. package/dist/runsUpload/uploadRun.js +217 -0
  50. package/package.json +11 -8
package/README.md CHANGED
@@ -2,6 +2,318 @@
2
2
 
3
3
  Command line interface for AssetHub.
4
4
 
5
+ ## Agent generation with canvas history
6
+
7
+ This release uses the Internal `api_canvas_execution` gate. Run `assethub capabilities`
8
+ first. Image, mesh, parts split, and production analyze/execute/automation refuse to dispatch if canvas history is unavailable.
9
+ The current public API remains compatible with clients that omit `executionContext`.
10
+
11
+ ```sh
12
+ assethub image generate --prompt "small stylized robot" --agent my-agent --wait > image.json
13
+ CANVAS_ID=$(jq -r '.execution.canvas.id' image.json)
14
+ IMAGE_ASSET_ID=$(jq -r '.execution.outputs[0].assetId' image.json)
15
+ IMAGE_RUN_ID=$(jq -r '.execution.runId' image.json)
16
+ assethub mesh generate --source-id "$IMAGE_ASSET_ID" --canvas "$CANVAS_ID" \
17
+ --derived-from-run "$IMAGE_RUN_ID" --agent my-agent --wait > mesh.json
18
+ assethub graph show --canvas "$CANVAS_ID"
19
+ assethub runs list --canvas "$CANVAS_ID"
20
+ assethub canvas open "$CANVAS_ID"
21
+ ```
22
+
23
+ Without `--canvas`, the first generation creates one canvas per real working
24
+ directory, API origin and organization. `canvas use <id>` changes the selection.
25
+ An inaccessible selected canvas is an error; the CLI does not silently make another.
26
+ `canvas open` prints JSON and opens a browser only in a terminal.
27
+
28
+ Each generation returns `execution` with the canvas URL, run/job IDs, stable output
29
+ asset IDs, history status, and credit usage when available. History persists while
30
+ all browsers are closed. Uploaded images are promoted to owned durable assets.
31
+ Use `--input-json @request.json` for a complete request, or the existing file/stdin/
32
+ source flags. A `source create` or `files import` JSON response can be passed to
33
+ `--source-json @source.json`; generated asset IDs can be passed to `--source-id`.
34
+
35
+ The CLI saves each request before dispatch under `~/.assethub/state/operations`
36
+ (or `ASSETHUB_CLI_STATE_DIR`). Those files contain inputs, never API keys. Keep the
37
+ state directory private. `--operation-id <uuid>` supplies an operation identity;
38
+ retries and `runs resume <operation-id>` reuse its identical body and key. A new
39
+ candidate needs a new operation ID. `runs watch <run-id>` only reads status.
40
+
41
+ stdout contains final JSON; stderr contains progress. Exit codes are 0 for accepted/
42
+ completed, 1 for terminal failure/partial, 2 for input/auth/capability/budget errors,
43
+ 3 for timeout/history pending/needs review, and 130 for Ctrl-C. Timeout and Ctrl-C
44
+ leave server execution running and return known identities.
45
+
46
+ External agents can evaluate downloaded artifacts with their own tools and store
47
+ an assessment without starting another model call:
48
+
49
+ ```sh
50
+ assethub evaluations submit --canvas "$CANVAS_ID" --artifact "$IMAGE_ASSET_ID" \
51
+ --report ./evaluation.json --agent my-agent --require-pass
52
+ assethub evaluations list --canvas "$CANVAS_ID"
53
+ assethub graph lineage --canvas "$CANVAS_ID" --artifact "$IMAGE_ASSET_ID"
54
+ assethub graph export --canvas "$CANVAS_ID" --out ./graph.json
55
+ ```
56
+
57
+ `evaluation.json` requires the strict report fields `schemaVersion`,
58
+ `rubric`, `verdict: pass|fail|needs_review`, `criteria`, `referenceAssetIds`, and
59
+ `evidenceAssetIds` for comparable reports. The server records `agent_submission`
60
+ and authenticated actor provenance. This never counts as human approval.
61
+ `--require-pass` returns 1 for fail and 3 for missing/needs-review verdicts.
62
+
63
+ For example, an agent that has not inspected the output yet can submit:
64
+
65
+ ```json
66
+ {
67
+ "schemaVersion": "assethub.evaluation-submission.v1",
68
+ "rubric": {"id": "asset-quality", "version": "1"},
69
+ "verdict": "needs_review",
70
+ "criteria": [
71
+ {
72
+ "id": "visual-quality",
73
+ "verdict": "needs_review",
74
+ "reason": "Artifact inspection is pending."
75
+ }
76
+ ],
77
+ "referenceAssetIds": [],
78
+ "evidenceAssetIds": []
79
+ }
80
+ ```
81
+
82
+ Replace the verdict and reason with the actual assessment after inspecting the
83
+ artifact; reference and evidence IDs must belong to the authenticated organization.
84
+
85
+ Existing parts commands use the same canvas and durable operation storage:
86
+
87
+ ```sh
88
+ assethub parts split --source-id "$IMAGE_ASSET_ID" --canvas "$CANVAS_ID" --part-extractor V1.5 --wait
89
+ # Select only the parts to execute; --all-ready selects all ready tasks.
90
+ assethub parts split --source-id "$IMAGE_ASSET_ID" --canvas "$CANVAS_ID" --part-extractor V1.5 --all-ready --wait
91
+ # Internal V3.6.1 runs the complete graph split through one analyze receipt.
92
+ assethub production agents
93
+ assethub parts split --source-id "$IMAGE_ASSET_ID" --canvas "$CANVAS_ID" --part-extractor V3.6.1 --all-ready --wait
94
+ assethub production automation --canvas "$CANVAS_ID" --input-json @batch.json --wait
95
+ ```
96
+
97
+ `parts split` keeps separate analysis and selected-part execution phases. Each phase
98
+ has its own saved operation ID, with the execution linked to the analysis run.
99
+ For Internal `V3.6.1` (`V3.6.1 Primary Images First`), analysis runs the complete
100
+ Artifact Graph split. `--all-ready` waits for that one receipt and does not start a
101
+ second production execution. Resume an interrupted graph split with
102
+ `runs resume <operation-id>`; `--task-id`, `--mission-id`,
103
+ `--part-extraction-mode`, and classic `--order-id` continuation are unsupported.
104
+ Automation runs the existing complete batch pipeline, including mesh generation;
105
+ its `images[]` share the selected canvas. `batch.json` uses the existing automation
106
+ request shape, including `agentVersion`, `images` (owned `imageAssetId`, `uploadId`,
107
+ or an `imageUrl` to import), `config`, and optional `maxCostCredits`.
108
+ An uncertain Trigger acknowledgement remains recoverable as `needs_review` with
109
+ pending history; retrying the same operation never silently launches another run.
110
+
111
+ `evaluate list` discovers server evaluators. Server evaluation dispatch is currently
112
+ unavailable. Graph exports are bounded to 100 runs/evaluations and 1,000 nodes;
113
+ truncated exports require `--allow-truncated`. Other commands, including production run/intervene, rig and retopology,
114
+ retain their existing API behavior and do not yet create these receipts.
115
+ `autopilot --demo` is an experimental demo requiring the AssetHub monorepo and its unpublished development dependency `@assethub/autopilot-core`. Standalone project workflow commands do not require or install it. Autonomous budgeted improvement is a later stage.
116
+
117
+ ## Storyboard project workflow
118
+
119
+ These API-key commands work from the standalone CLI. Canvas/context commands use
120
+ `api_canvas_execution`; native moodboards additionally require an internal actor
121
+ with `moodboard_style_reference_enabled`. Select a key belonging to the intended
122
+ workspace before uploading source material. API keys are never part of context.
123
+
124
+ ```sh
125
+ # No API key is needed for local extraction. Optional dependencies are reported.
126
+ assethub project ingest ./client-sources --out-dir ./extracted --exclude-dir generated
127
+ assethub canvas create --name "Storyboard scene study" > canvas.json
128
+ CANVAS_ID=$(jq -r .id canvas.json)
129
+ assethub canvas import --canvas "$CANVAS_ID" --file ./extracted/storyboard.png > storyboard.json
130
+ assethub canvas import --canvas "$CANVAS_ID" --file ./character-face.png > character.json
131
+ assethub canvas import --canvas "$CANVAS_ID" --file ./primary-style.png > style.json
132
+
133
+ # Supply prompt + source text + imageAssetIds in analysis-request.json.
134
+ # Keep facts, uncertain identities and proposed backgrounds separate.
135
+ assethub language vision --input-json @analysis-request.json \
136
+ --operation-id 61e3a99c-30b7-4f41-8d57-2789ccdc7c92 --out analysis.json
137
+
138
+ # Prepare context.json from the inspected source facts and analysis.
139
+ assethub canvas context put --canvas "$CANVAS_ID" --file ./context.json --if-version 0
140
+ assethub canvas context get --canvas "$CANVAS_ID" --out ./recovered-context.json
141
+ assethub moodboard create --input-json @moodboard.json > board.json
142
+ BOARD_ID=$(jq -r .id board.json)
143
+ assethub moodboard analyze "$BOARD_ID" --wait > revision.json
144
+ REVISION_ID=$(jq -r .id revision.json)
145
+ assethub image generate --canvas "$CANVAS_ID" --context "$CANVAS_ID" \
146
+ --moodboard-revision "$REVISION_ID" --prompt "Reconstruct this storyboard as a 3D scene" \
147
+ --wait --download --out-dir ./generated > generated.json
148
+ SOURCE_ID=$(jq -r .assetId storyboard.json)
149
+ OUTPUT_ID=$(jq -r '.execution.outputs[0].assetId' generated.json)
150
+ assethub canvas compare --canvas "$CANVAS_ID" --source-id "$SOURCE_ID" \
151
+ --asset-id "$OUTPUT_ID" --out ./comparison.html
152
+ assethub evaluations submit --canvas "$CANVAS_ID" --artifact "$OUTPUT_ID" \
153
+ --report ./review.json --agent my-agent
154
+ ```
155
+
156
+ `project ingest` extracts PDF text/pages, workbook cells and embedded images,
157
+ representative video frames, and image/text sources. It writes `summary.json`,
158
+ `text.md` and `images.json` with source paths, hashes and extraction metadata.
159
+ Dependencies (Poppler, ffmpeg and Python 3) are detected, never installed. Partial
160
+ extraction exits 3 and reports missing dependencies; filenames do not establish
161
+ whether a picture is a storyboard or a 3D reference. Original files stay unchanged.
162
+
163
+ A context document is an extensible JSON object:
164
+
165
+ ```json
166
+ {
167
+ "schemaVersion": 1,
168
+ "title": "EP013 s150",
169
+ "instructions": {
170
+ "must": ["Preserve the original character face and pose"],
171
+ "avoid": ["Generic replacement characters"]
172
+ },
173
+ "sources": [
174
+ {
175
+ "key": "storyboard",
176
+ "remoteAssetId": "<owned-image-uuid>",
177
+ "generationUse": "required"
178
+ }
179
+ ],
180
+ "generationPlan": {"requiredSourceKeys": ["storyboard"]},
181
+ "sourceFacts": [
182
+ "Facts established by the original storyboard and story text"
183
+ ],
184
+ "inferences": [
185
+ {"proposal": "Background inferred from story", "status": "proposed"}
186
+ ],
187
+ "reviewState": {"status": "awaiting_creator_review"}
188
+ }
189
+ ```
190
+
191
+ `context get --out` writes the document for editing and reuse; stdout contains its
192
+ version/hash envelope. Updating changed content requires `--if-version N` from the
193
+ last read. Image generation pins the current version by default; `--context-version`
194
+ and repeated `--context-source` explicitly select a version and source keys. Required
195
+ references, labelled instructions and source facts are added by the server, and their
196
+ IDs/version/hash are recorded on the run. Retry an uncertain generation with
197
+ `runs resume <operation-id>` to preserve the original pinned inputs.
198
+
199
+ `moodboard.json` contains `name`, `assetIds` (maximum 12),
200
+ `representativeAssetIds` (maximum 3, all included in assetIds), and optional
201
+ `userNote`. `update <board-id> --input-json @file` creates a new immutable revision;
202
+ `get`, `list`, and `archive` manage existing boards. Analysis uses the existing
203
+ 8-credit vision plan for a new attempt, resumes an active job, and reuses a ready
204
+ revision. The combined generation limit is **6 content and style images**: Marza's
205
+ 5 required identity/storyboard references leave room for 1 style representative.
206
+
207
+ Image import persists upload identity before registration and recovers the same
208
+ asset after an uncertain response; `--upload-id` resumes a known completed upload.
209
+ Identical moodboard creation input uses the same operation identity; supply a fresh
210
+ `--operation-id` when intentionally creating another identical board. Language
211
+ commands require a UUID operation ID: reuse it with the same input after a timeout.
212
+
213
+ Comparisons are portable HTML with embedded image bytes and a SHA-256 provenance
214
+ sidecar. They remain `awaiting_creator_review`; an agent evaluation is not creator
215
+ approval. Native project assets and generation history are saved in AssetHub.
216
+ `canvas layout` appends native image nodes and source/output edges through the
217
+ authoritative canvas room (Internal rollout). It preserves matching existing nodes
218
+ and rejects identity collisions. `canvas import` alone preserves an asset without
219
+ changing layout. Use the owned asset IDs returned by import or generation, including `image_...` IDs.
220
+ For example, create `layout.json` with those IDs:
221
+
222
+ ```json
223
+ {
224
+ "images": [
225
+ {
226
+ "assetId": "<storyboard-asset-id>",
227
+ "role": "source",
228
+ "x": 0,
229
+ "y": 0,
230
+ "name": "Original storyboard"
231
+ },
232
+ {
233
+ "assetId": "<generated-asset-id>",
234
+ "role": "output",
235
+ "x": 600,
236
+ "y": 0,
237
+ "name": "Generated candidate — review pending"
238
+ }
239
+ ],
240
+ "connections": [
241
+ {
242
+ "sourceAssetId": "<storyboard-asset-id>",
243
+ "targetAssetId": "<generated-asset-id>"
244
+ }
245
+ ]
246
+ }
247
+ ```
248
+
249
+ ```sh
250
+ assethub canvas layout --canvas "$CANVAS_ID" --input-json @layout.json
251
+ ```
252
+
253
+ Internal workspace preparation (monorepo checkout only):
254
+
255
+ ```sh
256
+ infisical run --env=prod -- node --import tsx packages/assethub-cli/src/index.ts \
257
+ workspace prepare --internal --manifest ./workspace.json --state ./workspace-state.json
258
+ ```
259
+
260
+ `workspace.json` provides `orgId`, `actorId`, `actorEmail`, `projectName`,
261
+ `boardName`, `userNote`, and `references` / optional `inputs` and `outputs` arrays containing
262
+ `path`, `name`, optional `section`, and optional `representative` (up to three
263
+ references). An output can set `sourcePath` to an imported input path to retain a
264
+ native canvas connection from storyboard to result. The internal actor must
265
+ already belong to the selected workspace.
266
+ This creates a native workflow canvas, owned image assets, and a native moodboard
267
+ revision. `workspace sync` appends new images from the manifest; the original
268
+ moodboard reference revision stays frozen. Preserve the state file for resuming.
269
+ `workspace analyze` uses the native analysis model, prompt and parser with the
270
+ existing OpenRouter environment, saving provider usage beside the state file.
271
+ It uses only the saved revision's frozen references, checks the original owned
272
+ assets again, and claims one analysis attempt before calling the provider.
273
+ Resumed order and image identities, storage paths and source hashes are checked
274
+ against owned records before reading storage or changing the canvas.
275
+ `workspace verify` checks persisted native schema, ownership and uploaded hashes.
276
+ Optional `contextNote` (up to 24,000 characters) adds a readable native text node
277
+ beside the image groups. The full project context is saved separately below.
278
+ `workspace self-check --internal` checks native schema, context roundtrips and
279
+ conflict rejection without credentials.
280
+
281
+ Save and recover a project's instructions without the local manifest/state:
282
+
283
+ ```sh
284
+ assethub workspace context put --internal --org <workspace-uuid> --actor <internal-user-uuid> \
285
+ --canvas <canvas-id> --file ./project-context.json
286
+ assethub workspace context get --internal --org <workspace-uuid> --actor <internal-user-uuid> \
287
+ --canvas <canvas-id> --out ./recovered-context.json
288
+ # To replace changed content, use the version returned by the latest get:
289
+ assethub workspace context put --internal --org <workspace-uuid> --actor <internal-user-uuid> \
290
+ --canvas <canvas-id> --file ./edited-context.json --if-version <current-version>
291
+ ```
292
+
293
+ The document is a JSON object of at most 256 KiB, with `schemaVersion: 1`, a
294
+ nonempty `title`, `instructions: {must: string[], avoid: string[]}` (at least one
295
+ required instruction), and a nonempty `sources` object array. Other JSON fields
296
+ are retained. Source entries can use `remoteAssetId: <uuid> | null`; every
297
+ explicit `assetId` / `*AssetId` / `*AssetIds` reference must belong to the workspace.
298
+ An unuploaded source may retain a null ID and its provenance.
299
+
300
+ The native order's `metadata.projectContext` holds the document, version, content
301
+ SHA-256, timestamp and actor. Other metadata is preserved. A changed existing
302
+ document requires `--if-version`; identical-content retries do not create a new
303
+ version. The write also compares the order's `updated_at`, which the native
304
+ `model_workflow_update_updated_at` trigger advances on every update, refusing
305
+ concurrent changes to any order metadata. Documents stay in the request body,
306
+ so large contexts do not exceed URI limits. If multiple active orders belong to
307
+ a canvas, supply `--order`.
308
+ The actor must be an internal user with a writable workspace membership; both
309
+ project and order ownership are checked. `get --out` writes the original document
310
+ and stdout includes its stored envelope. It does not require local CLI state.
311
+
312
+ These commands require the existing internal Supabase service environment and
313
+ reuse native workspace, moodboard and context contracts separately from the
314
+ public API commands. This internal command is unavailable in a standalone
315
+ published CLI installation.
316
+
5
317
  ```sh
6
318
  export ASSETHUB_API_KEY=ah_live_xxx
7
319
 
@@ -13,8 +325,39 @@ assethub mesh generate --file ./input.png --wait
13
325
  assethub jobs watch job_123 --download --out-dir ./out/job
14
326
  assethub parts split --file ./input.png --part-extractor "V1.5" --wait --all-ready --download --out-dir ./out/parts
15
327
  assethub parts compare --file ./input.png --preprocess-prompt "clean white background, centered product photo" --part-extractor "V1.5" --wait --all-ready
328
+ assethub runs upload ./out/pluffy-run-2026-07-30 --dry-run
16
329
  ```
17
330
 
331
+ Uploading a local artifact-graph run:
332
+
333
+ ```sh
334
+ # Validate the run folder without sending anything.
335
+ assethub runs upload ./out/pluffy-run-2026-07-30 --dry-run
336
+
337
+ # Upload it. Blobs go first, one at a time, then the snapshot.
338
+ assethub runs upload ./out/pluffy-run-2026-07-30 --tag pluffy --description "V3 humanoid"
339
+ ```
340
+
341
+ `<path>` is an `ag.graph-folder.v1` folder — the directory holding
342
+ `manifest.json`, `nodes.jsonl`, `edges.jsonl` and `blobs/`. The wire format is
343
+ `ag.registry.v1`, the same document the artifact-graph registry accepts, so one
344
+ folder can be published to either destination without conversion.
345
+
346
+ - `--dry-run` runs every local check and prints what _would_ be sent. A folder
347
+ whose manifest no longer matches its own `nodes.jsonl`, whose blob table
348
+ disagrees with the graph, or which exceeds the per-request size ceilings is
349
+ rejected here rather than halfway through an upload.
350
+ - Uploads are resumable. Each blob is checked against the server before it is
351
+ sent, and blob storage is content-addressed, so re-running the identical
352
+ command after a failure sends only what is still missing.
353
+ - `--graph-id` / `--stream-id` / `--rev` override the identity derived from the
354
+ folder name and `manifest.source`. Re-uploading the same revision with
355
+ different content is refused; bump `--rev` to publish a new revision.
356
+ - `--skip-register` skips the graph-registration call for a graph that already
357
+ exists.
358
+ - Requires an internal account whose org has the Production Control
359
+ entitlement; other keys get "cannot upload runs to Production Control".
360
+
18
361
  Configuration:
19
362
 
20
363
  - `ASSETHUB_API_KEY`: optional when a profile was saved with `auth login`.
@@ -79,12 +422,13 @@ is preferred. Use `--all-ready` rather than explicit `--task-id` values when
79
422
  preprocessing is enabled, because the generated-image split creates a separate
80
423
  production order.
81
424
 
82
- Image generation defaults to the deployed `v1` endpoint. Pass `--api-version v2`
83
- only when the `v2` image endpoint is available in the target environment.
425
+ Image generation uses the `v2` endpoint with mandatory canvas history.
426
+ `--api-version v1` is rejected; run `assethub capabilities` to check availability.
84
427
 
85
- Part extraction uses the same public names shown in the AssetHub product:
86
- `V1`, `V1.5 alpha`, `V1.5`, `V2.0 alpha`, and `V2.1 alpha`. Internal agent
87
- IDs are intentionally not part of the public CLI interface.
428
+ Part extraction uses the same names shown in the AssetHub product: `V1.5`,
429
+ `V2.0 alpha`, `V2.1 alpha`, and the Internal-only `V3.6.1` alias for
430
+ `V3.6.1 Primary Images First`. Retired names are no longer accepted by the CLI.
431
+ Internal agent IDs are intentionally not part of the CLI interface.
88
432
 
89
433
  The CLI only uses AssetHub's public API surface and is designed so this package
90
434
  can be published independently from the private application monorepo.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `assethub autopilot` — drives the auto-split autopilot from the terminal,
3
+ * rendering the shared event stream coding-agent style.
4
+ *
5
+ * The CLI is the primary frontend for the autopilot brain; the in-app chat
6
+ * window is the same engine + event stream rendered inside the app. This
7
+ * command currently runs against the deterministic MockBackend (`--demo`); the
8
+ * real backend (AssetHub API / trigger.dev) is a drop-in AutopilotBackend.
9
+ */
10
+ type Flags = Record<string, string | boolean | string[]>;
11
+ export declare const commandAutopilot: (positionals: string[], flags: Flags) => Promise<void>;
12
+ export {};
13
+ //# sourceMappingURL=autopilot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autopilot.d.ts","sourceRoot":"","sources":["../src/autopilot.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAiBH,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC,CAAA;AAmHxD,eAAO,MAAM,gBAAgB,GAC3B,aAAa,MAAM,EAAE,EACrB,OAAO,KAAK,KACX,OAAO,CAAC,IAAI,CAgDd,CAAA"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * `assethub autopilot` — drives the auto-split autopilot from the terminal,
3
+ * rendering the shared event stream coding-agent style.
4
+ *
5
+ * The CLI is the primary frontend for the autopilot brain; the in-app chat
6
+ * window is the same engine + event stream rendered inside the app. This
7
+ * command currently runs against the deterministic MockBackend (`--demo`); the
8
+ * real backend (AssetHub API / trigger.dev) is a drop-in AutopilotBackend.
9
+ */
10
+ import { createInterface } from 'node:readline/promises';
11
+ import { stdin, stdout } from 'node:process';
12
+ import { AutopilotEngine, MockBackend, demoConfig, formatEventLine, } from '@assethub/autopilot-core';
13
+ // --- tiny ANSI helpers (no dependency) ---
14
+ const useColor = stdout.isTTY === true;
15
+ const paint = (code, s) => (useColor ? `[${code}m${s}` : s);
16
+ const dim = (s) => paint('2', s);
17
+ const bold = (s) => paint('1', s);
18
+ const green = (s) => paint('32', s);
19
+ const yellow = (s) => paint('33', s);
20
+ const red = (s) => paint('31', s);
21
+ const cyan = (s) => paint('36', s);
22
+ const flagStr = (flags, name) => {
23
+ const v = flags[name];
24
+ return typeof v === 'string' ? v : Array.isArray(v) ? v[0] : undefined;
25
+ };
26
+ const flagBool = (flags, name) => flags[name] != null;
27
+ const flagNum = (flags, name) => {
28
+ const s = flagStr(flags, name);
29
+ if (s == null)
30
+ return undefined;
31
+ const n = Number(s);
32
+ return Number.isFinite(n) ? n : undefined;
33
+ };
34
+ /** Colorize the shared narration line for the terminal. */
35
+ const colorize = (event, line) => {
36
+ switch (event.type) {
37
+ case 'run_started':
38
+ return bold(cyan(line));
39
+ case 'phase_changed':
40
+ return bold(line);
41
+ case 'log':
42
+ if (event.level === 'success')
43
+ return green(line);
44
+ if (event.level === 'warn')
45
+ return yellow(line);
46
+ if (event.level === 'error')
47
+ return red(line);
48
+ return dim(line);
49
+ case 'human_review_requested':
50
+ return yellow(line);
51
+ case 'human_review_resolved':
52
+ return cyan(line);
53
+ case 'credit_estimate':
54
+ return dim(line);
55
+ case 'run_finished':
56
+ return event.status === 'complete' ? bold(green(line)) : bold(red(line));
57
+ default:
58
+ return line;
59
+ }
60
+ };
61
+ const PLAN_GLYPH = {
62
+ pending: '○',
63
+ active: '◐',
64
+ done: '●',
65
+ skipped: '⊘',
66
+ failed: '✗',
67
+ };
68
+ const renderPlan = (plan, indent = 0) => {
69
+ const pad = ' '.repeat(indent);
70
+ return plan
71
+ .map((step) => {
72
+ const head = `${pad}${PLAN_GLYPH[step.status]} ${step.label}${step.detail ? dim(` — ${step.detail}`) : ''}`;
73
+ const kids = step.children?.length ? '\n' + renderPlan(step.children, indent + 1) : '';
74
+ return head + kids;
75
+ })
76
+ .join('\n');
77
+ };
78
+ /** Interactive resolver reading a choice from the TTY; falls back to a policy when not a TTY. */
79
+ const makeResolver = (opts) => {
80
+ return async (request) => {
81
+ const autoChoice = autoPolicy(request);
82
+ if (opts.yolo || !opts.interactive) {
83
+ if (!opts.quiet)
84
+ stdout.write(dim(` → auto: ${autoChoice}\n`));
85
+ return { requestId: request.requestId, choice: autoChoice };
86
+ }
87
+ const rl = createInterface({ input: stdin, output: stdout });
88
+ try {
89
+ const answer = (await rl.question(cyan(' choose [1..] (enter = auto): '))).trim();
90
+ const idx = Number(answer);
91
+ if (Number.isInteger(idx) && idx >= 1 && idx <= request.options.length) {
92
+ return { requestId: request.requestId, choice: request.options[idx - 1].id };
93
+ }
94
+ return { requestId: request.requestId, choice: autoChoice };
95
+ }
96
+ finally {
97
+ rl.close();
98
+ }
99
+ };
100
+ };
101
+ /** Default non-interactive policy: accept fail parts, pick the first (top-scored) tie candidate. */
102
+ const autoPolicy = (request) => {
103
+ if (request.kind === 'fail_part')
104
+ return 'accept';
105
+ return request.options[0].id;
106
+ };
107
+ const buildConfig = (flags) => {
108
+ const base = demoConfig();
109
+ return {
110
+ ...base,
111
+ maxRegenAttempts: flagNum(flags, 'max-regen') ?? base.maxRegenAttempts,
112
+ regenStallRounds: flagNum(flags, 'stall-rounds') ?? base.regenStallRounds,
113
+ tieEpsilon: flagNum(flags, 'tie-epsilon') ?? base.tieEpsilon,
114
+ multiviewGoalId: flagStr(flags, 'multiview') ?? base.multiviewGoalId,
115
+ abMode: flagStr(flags, 'ab-mode') === 'cross' ? 'cross' : base.abMode,
116
+ creditBudget: flagNum(flags, 'credit-budget') ?? base.creditBudget,
117
+ };
118
+ };
119
+ export const commandAutopilot = async (positionals, flags) => {
120
+ const sourceImage = positionals[1] ?? flagStr(flags, 'file') ?? flagStr(flags, 'source-url') ?? 'mock://demo-source.png';
121
+ const isDemo = flagBool(flags, 'demo');
122
+ const asJson = flagBool(flags, 'json');
123
+ if (!isDemo) {
124
+ stdout.write(yellow('autopilot: only --demo (deterministic MockBackend) is wired up in this build.\n' +
125
+ 'The real backend (AssetHub API / trigger.dev) is a drop-in AutopilotBackend — pass --demo to run the mock end-to-end.\n'));
126
+ return;
127
+ }
128
+ const config = buildConfig(flags);
129
+ const events = [];
130
+ let lastPlan = [];
131
+ const emit = (event) => {
132
+ events.push(event);
133
+ if (asJson)
134
+ return; // collect only; print JSON at the end
135
+ if (event.type === 'plan_updated') {
136
+ lastPlan = event.plan;
137
+ return;
138
+ }
139
+ const line = formatEventLine(event);
140
+ if (line != null)
141
+ stdout.write(colorize(event, line) + '\n');
142
+ };
143
+ const engine = new AutopilotEngine(new MockBackend(), config, {
144
+ emit,
145
+ resolveHumanReview: makeResolver({
146
+ yolo: flagBool(flags, 'yolo'),
147
+ interactive: stdin.isTTY === true && !asJson,
148
+ quiet: asJson,
149
+ }),
150
+ runId: 'demo-run',
151
+ });
152
+ const summary = await engine.run(sourceImage);
153
+ if (asJson) {
154
+ stdout.write(`${JSON.stringify({ summary, events }, null, 2)}\n`);
155
+ return;
156
+ }
157
+ stdout.write('\n' + bold('Plan') + '\n' + renderPlan(lastPlan) + '\n');
158
+ };
@@ -0,0 +1,27 @@
1
+ import type { Canvas } from '@assethub/api-client';
2
+ type CanvasScope = {
3
+ stateDir: string;
4
+ cwd: string;
5
+ baseUrl: string;
6
+ ownerId: string;
7
+ };
8
+ type CanvasClient = {
9
+ v2: {
10
+ createCanvas: (body: {
11
+ name: string;
12
+ }, options: {
13
+ idempotencyKey: string;
14
+ }) => Promise<Canvas>;
15
+ getCanvas: (id: number) => Promise<Canvas>;
16
+ };
17
+ };
18
+ export declare const writeState: (path: string, value: unknown) => Promise<void>;
19
+ export declare const readState: (path: string) => Promise<unknown | undefined>;
20
+ export declare const saveCanvasSelection: (scope: CanvasScope, canvasId: number) => Promise<void>;
21
+ export declare const resolveCanvasSelection: (options: CanvasScope & {
22
+ client: CanvasClient;
23
+ canvasId?: number;
24
+ create?: boolean;
25
+ }) => Promise<Canvas>;
26
+ export {};
27
+ //# sourceMappingURL=canvas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../src/canvas.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,sBAAsB,CAAA;AAEhD,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AACD,KAAK,YAAY,GAAG;IAClB,EAAE,EAAE;QACF,YAAY,EAAE,CACZ,IAAI,EAAE;YAAC,IAAI,EAAE,MAAM,CAAA;SAAC,EACpB,OAAO,EAAE;YAAC,cAAc,EAAE,MAAM,CAAA;SAAC,KAC9B,OAAO,CAAC,MAAM,CAAC,CAAA;QACpB,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;KAC3C,CAAA;CACF,CAAA;AAED,eAAO,MAAM,UAAU,GACrB,MAAM,MAAM,EACZ,OAAO,OAAO,KACb,OAAO,CAAC,IAAI,CAWd,CAAA;AAED,eAAO,MAAM,SAAS,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAazE,CAAA;AAiBD,eAAO,MAAM,mBAAmB,GAC9B,OAAO,WAAW,EAClB,UAAU,MAAM,KACf,OAAO,CAAC,IAAI,CAOd,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,SAAS,WAAW,GAAG;IACrB,MAAM,EAAE,YAAY,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,KACA,OAAO,CAAC,MAAM,CA8ChB,CAAA"}
package/dist/canvas.js ADDED
@@ -0,0 +1,82 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile, realpath, rename, unlink, writeFile, } from 'node:fs/promises';
3
+ import { basename, dirname, join } from 'node:path';
4
+ export const writeState = async (path, value) => {
5
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
6
+ const temporary = `${path}.${randomUUID()}.tmp`;
7
+ try {
8
+ await writeFile(temporary, JSON.stringify(value), { mode: 0o600, flag: 'wx' });
9
+ await rename(temporary, path);
10
+ }
11
+ finally {
12
+ await unlink(temporary).catch(error => {
13
+ if (error.code !== 'ENOENT')
14
+ throw error;
15
+ });
16
+ }
17
+ };
18
+ export const readState = async (path) => {
19
+ try {
20
+ return JSON.parse(await readFile(path, 'utf8'));
21
+ }
22
+ catch (error) {
23
+ if (error &&
24
+ typeof error === 'object' &&
25
+ 'code' in error &&
26
+ error.code === 'ENOENT')
27
+ return undefined;
28
+ throw error;
29
+ }
30
+ };
31
+ const scopeHash = async (scope) => createHash('sha256')
32
+ .update(JSON.stringify([
33
+ scope.baseUrl.replace(/\/+$/, ''),
34
+ scope.ownerId,
35
+ await realpath(scope.cwd),
36
+ ]))
37
+ .digest('hex');
38
+ /** A stable operation identity lets the server deduplicate even a crashed first invocation. */
39
+ const creationId = (hash) => `${hash.slice(0, 8)}-${hash.slice(8, 12)}-4${hash.slice(13, 16)}-8${hash.slice(17, 20)}-${hash.slice(20, 32)}`;
40
+ export const saveCanvasSelection = async (scope, canvasId) => {
41
+ if (!Number.isSafeInteger(canvasId) || canvasId <= 0)
42
+ throw new Error('Canvas ID must be a positive integer');
43
+ await writeState(join(scope.stateDir, 'canvases', `${await scopeHash(scope)}.json`), { canvasId });
44
+ };
45
+ export const resolveCanvasSelection = async (options) => {
46
+ const { client } = options;
47
+ let canvasId = options.canvasId;
48
+ const hash = await scopeHash(options);
49
+ if (canvasId == null) {
50
+ const stored = await readState(join(options.stateDir, 'canvases', `${hash}.json`));
51
+ if (stored != null) {
52
+ if (typeof stored !== 'object' ||
53
+ !('canvasId' in stored) ||
54
+ !Number.isSafeInteger(stored.canvasId)) {
55
+ throw new Error('Invalid saved canvas selection; select one with canvas use <id>');
56
+ }
57
+ canvasId = Number(stored.canvasId);
58
+ }
59
+ }
60
+ if (canvasId != null) {
61
+ if (!Number.isSafeInteger(canvasId) || canvasId <= 0)
62
+ throw new Error('Canvas ID must be a positive integer');
63
+ const canvas = await client.v2.getCanvas(canvasId);
64
+ if (canvas.ownerId !== options.ownerId)
65
+ throw new Error('Canvas belongs to a different organization');
66
+ return canvas;
67
+ }
68
+ if (options.create === false)
69
+ throw new Error('No canvas selected; use --canvas <id> or canvas use <id>');
70
+ // Check local persistence before starting a remote mutation. The same scope
71
+ // always posts the same name and key, including after a lost response.
72
+ const directory = join(options.stateDir, 'canvases');
73
+ await mkdir(directory, { recursive: true, mode: 0o700 });
74
+ const probe = join(directory, `${hash}.${randomUUID()}.probe`);
75
+ await writeFile(probe, '', { mode: 0o600, flag: 'wx' });
76
+ await unlink(probe);
77
+ const canvas = await client.v2.createCanvas({ name: `CLI ${basename(await realpath(options.cwd))} ${hash.slice(0, 8)}` }, { idempotencyKey: creationId(hash) });
78
+ if (canvas.ownerId !== options.ownerId)
79
+ throw new Error('Created canvas belongs to a different organization');
80
+ await saveCanvasSelection(options, canvas.id);
81
+ return canvas;
82
+ };