@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.
- package/README.md +349 -5
- package/dist/autopilot.d.ts +13 -0
- package/dist/autopilot.d.ts.map +1 -0
- package/dist/autopilot.js +158 -0
- package/dist/canvas.d.ts +27 -0
- package/dist/canvas.d.ts.map +1 -0
- package/dist/canvas.js +82 -0
- package/dist/canvasAssetImport.d.ts +45 -0
- package/dist/canvasAssetImport.d.ts.map +1 -0
- package/dist/canvasAssetImport.js +154 -0
- package/dist/canvasComparison.d.ts +21 -0
- package/dist/canvasComparison.d.ts.map +1 -0
- package/dist/canvasComparison.js +58 -0
- package/dist/execution.d.ts +175 -0
- package/dist/execution.d.ts.map +1 -0
- package/dist/execution.js +234 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1579 -189
- package/dist/projectSources.d.ts +65 -0
- package/dist/projectSources.d.ts.map +1 -0
- package/dist/projectSources.js +369 -0
- package/dist/projectSourcesWorkbook.d.ts +3 -0
- package/dist/projectSourcesWorkbook.d.ts.map +1 -0
- package/dist/projectSourcesWorkbook.js +143 -0
- package/dist/runsUpload/artifactGraphModel.d.ts +48 -0
- package/dist/runsUpload/artifactGraphModel.d.ts.map +1 -0
- package/dist/runsUpload/artifactGraphModel.js +127 -0
- package/dist/runsUpload/blobRefs.d.ts +13 -0
- package/dist/runsUpload/blobRefs.d.ts.map +1 -0
- package/dist/runsUpload/blobRefs.js +73 -0
- package/dist/runsUpload/buildRunUpload.d.ts +63 -0
- package/dist/runsUpload/buildRunUpload.d.ts.map +1 -0
- package/dist/runsUpload/buildRunUpload.js +428 -0
- package/dist/runsUpload/canonicalJson.d.ts +5 -0
- package/dist/runsUpload/canonicalJson.d.ts.map +1 -0
- package/dist/runsUpload/canonicalJson.js +108 -0
- package/dist/runsUpload/controlEndpoints.d.ts +63 -0
- package/dist/runsUpload/controlEndpoints.d.ts.map +1 -0
- package/dist/runsUpload/controlEndpoints.js +64 -0
- package/dist/runsUpload/graphFolder.d.ts +25 -0
- package/dist/runsUpload/graphFolder.d.ts.map +1 -0
- package/dist/runsUpload/graphFolder.js +147 -0
- package/dist/runsUpload/runUploadError.d.ts +23 -0
- package/dist/runsUpload/runUploadError.d.ts.map +1 -0
- package/dist/runsUpload/runUploadError.js +24 -0
- package/dist/runsUpload/uploadRun.d.ts +43 -0
- package/dist/runsUpload/uploadRun.d.ts.map +1 -0
- package/dist/runsUpload/uploadRun.js +217 -0
- 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
|
|
83
|
-
|
|
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
|
|
86
|
-
`
|
|
87
|
-
|
|
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}[0m` : 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
|
+
};
|
package/dist/canvas.d.ts
ADDED
|
@@ -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
|
+
};
|