@assethub/cli 0.1.32 → 0.1.33

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 CHANGED
@@ -378,12 +378,19 @@ assembly was made. Follow a run started without `--wait` with
378
378
  While a V4 run works, `--wait`, `runs watch` and `runs get` show where it is.
379
379
  The receipt's `progress` field has the phase, a one-line summary, each part's
380
380
  state and the Blender assembly rounds, in the words of the canvas Part Composer
381
- card. `--wait` and `runs watch` print a stderr line only when it changes:
381
+ card. `--wait` and `runs watch` print a stderr line, with the local time, only
382
+ when the run really moves on (a new step, another mesh ready, a part kept with
383
+ an issue, the end); part states that come and go do not print:
382
384
 
383
385
  ```text
384
- [run] 557d2a58-… running · Step 3 of 4 · Making the parts · 3 of 9 meshes ready · 4 redoing, 2 checking
386
+ 19:52 run 557d2a58 3/4 Making the parts · 3 of 9 meshes ready
387
+ 19:58 run 557d2a58 ⚠ kept with an issue: Cropped top · open hem at the lower chest
388
+ 20:05 run 557d2a58 ✗ FAILED · at 3/4 Making the parts · Blender assembly failed
385
389
  ```
386
390
 
391
+ `production batch --wait` prints the same lines per image, named by letter and
392
+ file name, and a status table every 5 minutes; see `docs/guides/production-batch.md`.
393
+
387
394
  ```sh
388
395
  assethub runs watch <run-id> --timeout-ms 7200000 --download --out-dir ./character
389
396
  assethub runs get <run-id> # JSON receipt, progress included
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import { downloadCanvas } from './canvasDownload.js';
13
13
  import { downloadCanvasGraphZip } from './canvasGraphDownload.js';
14
14
  import { executeNodeMeshBatch, resumeNodeMeshBatch, } from './nodeMeshBatch.js';
15
15
  import { writeCanvasComparison } from './canvasComparison.js';
16
- import { productionBatchLabels, readProductionBatch, runProductionBatch, } from './productionBatch.js';
16
+ import { productionBatchImageName, productionBatchLabels, readProductionBatch, runProductionBatch, } from './productionBatch.js';
17
17
  import { importCanvasAssetWithState } from './canvasAssetImport.js';
18
18
  import { execFile, spawn } from 'node:child_process';
19
19
  import { createHash, randomUUID } from 'node:crypto';
@@ -27,7 +27,7 @@ import { promisify } from 'node:util';
27
27
  import { AssetHubApiError, WorkspaceClientError, createWorkspaceClient, createAssetHubClient, } from '@assethub/api-client';
28
28
  import { resolveCanvasSelection, saveCanvasSelection } from './canvas.js';
29
29
  import { activeExecution, CliExecutionError, executeRecorded, hasRecordedOperation, openExecutionSession, resumeRecorded, waitForExecution, } from './execution.js';
30
- import { runProgressLine } from './runProgress.js';
30
+ import { batchReporter, runReporter } from './progressReport.js';
31
31
  import { buildRunUploadPlan, formatBytes } from './runsUpload/buildRunUpload.js';
32
32
  import { readGraphFolder } from './runsUpload/graphFolder.js';
33
33
  import { uploadRun } from './runsUpload/uploadRun.js';
@@ -2081,19 +2081,8 @@ const defaultWaitMs = (flags) => {
2081
2081
  const watchOptions = (flags) => ({
2082
2082
  intervalMs: parsePositiveIntegerFlag(flags, 'interval-ms', 5000),
2083
2083
  timeoutMs: parsePositiveIntegerFlag(flags, 'timeout-ms', defaultWaitMs(flags)),
2084
- onProgress: progressPrinter(run => `[run] ${run.runId} ${run.status}${run.progress ? ` · ${runProgressLine(run)}` : ''}`),
2084
+ onProgress: runReporter({ write: text => stderr.write(text) }),
2085
2085
  });
2086
- /** Prints a run's line only when it changed since the last poll. */
2087
- const progressPrinter = (line) => {
2088
- let last = '';
2089
- return (run) => {
2090
- const next = line(run);
2091
- if (next === last)
2092
- return;
2093
- last = next;
2094
- stderr.write(`${next}\n`);
2095
- };
2096
- };
2097
2086
  const readJsonArgument = async (value, flag) => {
2098
2087
  const text = value === '@-'
2099
2088
  ? await readStdin()
@@ -3673,10 +3662,29 @@ const commandProductionBatch = async (ctx) => {
3673
3662
  if (saved && requestedCanvasId != null && requestedCanvasId !== saved.canvasId)
3674
3663
  throw new Error(`Batch ${operationId} runs on canvas ${saved.canvasId}; drop --canvas or pass that canvas`);
3675
3664
  const session = await productionSession(ctx, 'production.analyze', saved?.canvasId ?? requestedCanvasId);
3676
- stderr.write(`[batch] ${operationId} · ${entries.length} run(s) on canvas ${session.canvas.id}. Rerun with --operation-id ${operationId} to resume.\n`);
3677
3665
  const wait = hasFlag(ctx.flags, 'wait');
3678
3666
  const concurrency = parsePositiveIntegerFlag(ctx.flags, 'concurrency', 2);
3679
- const printItem = (item) => stderr.write(`[batch] ${item.key} ${item.label} ${item.status}${item.runId ? ` ${item.runId}` : ''}${item.execution?.progress ? ` · ${runProgressLine(item.execution)}` : ''}${item.message ? ` · ${item.message}` : ''}\n`);
3667
+ const report = batchReporter({
3668
+ write: text => stderr.write(text),
3669
+ operationId,
3670
+ agent: partExtractorOptions.find(option => option.apiValue === options.agentVersion)
3671
+ ?.publicName ?? String(options.agentVersion),
3672
+ canvasId: session.canvas.id,
3673
+ ...(wait ? { concurrency } : {}),
3674
+ images: entries.map(entry => ({
3675
+ key: entry.key,
3676
+ imageIndex: entry.index,
3677
+ repeat: entry.repeat,
3678
+ name: productionBatchImageName(sources[entry.index]),
3679
+ })),
3680
+ });
3681
+ report.header();
3682
+ const printItem = (item) => report.item(item);
3683
+ // A status table every 5 minutes while waiting; runs take about an hour.
3684
+ const tableTimer = wait
3685
+ ? setInterval(() => report.table('still running'), 5 * 60_000)
3686
+ : undefined;
3687
+ tableTimer?.unref();
3680
3688
  const result = await runProductionBatch(session, {
3681
3689
  operationId,
3682
3690
  request: { sources, repeat, options, agent, parentRunId },
@@ -3714,8 +3722,10 @@ const commandProductionBatch = async (ctx) => {
3714
3722
  }
3715
3723
  : {}),
3716
3724
  });
3725
+ clearInterval(tableTimer);
3717
3726
  if (!wait)
3718
3727
  result.items.forEach(printItem);
3728
+ report.table(!wait ? 'started' : result.timedOut ? 'stopped waiting' : 'done');
3719
3729
  const downloads = [];
3720
3730
  if (hasFlag(ctx.flags, 'download')) {
3721
3731
  const outDir = getFlag(ctx.flags, 'out-dir') ?? getFlag(ctx.flags, 'out');
@@ -52,6 +52,8 @@ export type ProductionBatchSource = {
52
52
  * two images must never share one. Files that share a name get their folder.
53
53
  */
54
54
  export declare const productionBatchLabels: (sources: readonly ProductionBatchSource[]) => string[];
55
+ /** The image as the user named it, for progress lines: `image (28).png`. */
56
+ export declare const productionBatchImageName: ({ flag, value }: ProductionBatchSource) => string;
55
57
  export declare const readProductionBatch: (stateDir: string, operationId: string) => Promise<ProductionBatchState | undefined>;
56
58
  export declare const runProductionBatch: (session: Session, options: {
57
59
  operationId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"productionBatch.d.ts","sourceRoot":"","sources":["../src/productionBatch.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,gBAAgB,EAGtB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAkB,eAAe,EAAC,MAAM,gBAAgB,CAAA;AAG/D,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC,CAAA;AAEpD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;GAIG;AACH,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE,CAAC,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,8DAA8D;IAC9D,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,mBAAmB,EAAE,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,yBAAyB,GAAG;IACtC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,8BAA8B;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,8DAA8D;IAC9D,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,UAAU,GAAG,QAAQ,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAA;IACpF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,YAAY,CAAA;IACzC,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAKD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,GAChC,SAAS,SAAS,qBAAqB,EAAE,KACxC,MAAM,EAuBR,CAAA;AASD,eAAO,MAAM,mBAAmB,GAC9B,UAAU,MAAM,EAChB,aAAa,MAAM,KAClB,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAwB1C,CAAA;AAOD,eAAO,MAAM,kBAAkB,GAC7B,SAAS,OAAO,EAChB,SAAS;IACP,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAA;IAChD,KAAK,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAA;IACjC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,sFAAsF;IACtF,IAAI,CAAC,EAAE;QACL,WAAW,EAAE,MAAM,CAAA;QACnB,UAAU,EAAE,MAAM,CAAA;QAClB,SAAS,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,yBAAyB,KAAK,IAAI,CAAA;KACvD,CAAA;CACF,KACA,OAAO,CAAC;IACT,gBAAgB,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,yBAAyB,EAAE,CAAA;IAClC,QAAQ,EAAE,OAAO,CAAA;CAClB,CA2JA,CAAA"}
1
+ {"version":3,"file":"productionBatch.d.ts","sourceRoot":"","sources":["../src/productionBatch.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,16 @@ import { activeExecution, executeRecorded } from './execution.js';
7
7
  import { runProgressLine } from './runProgress.js';
8
8
  const stem = (path) => basename(path, extname(path));
9
9
  const safeLabel = (label) => label.replace(/[^\w.-]+/g, '_');
10
+ /**
11
+ * A source URL's file name, or its host when the path has none. Never the whole
12
+ * URL: a signed URL carries its credentials in the query string, and this name
13
+ * is printed in progress lines and used as a download folder.
14
+ */
15
+ const urlFileName = (value, strip = name => name) => {
16
+ const url = new URL(value);
17
+ const file = basename(url.pathname);
18
+ return file ? strip(file) : url.hostname;
19
+ };
10
20
  /**
11
21
  * One distinct label per image: it names the order and the download folder, so
12
22
  * two images must never share one. Files that share a name get their folder.
@@ -15,7 +25,7 @@ export const productionBatchLabels = (sources) => {
15
25
  const base = sources.map(({ flag, value }) => flag === 'file'
16
26
  ? stem(value)
17
27
  : flag === 'source-url'
18
- ? stem(new URL(value).pathname) || value
28
+ ? urlFileName(value, stem)
19
29
  : value);
20
30
  const count = (labels, label) => labels.filter(entry => entry === label).length;
21
31
  const withFolder = base.map((label, index) => {
@@ -30,6 +40,12 @@ export const productionBatchLabels = (sources) => {
30
40
  : label)
31
41
  .map(safeLabel);
32
42
  };
43
+ /** The image as the user named it, for progress lines: `image (28).png`. */
44
+ export const productionBatchImageName = ({ flag, value }) => flag === 'file'
45
+ ? basename(value)
46
+ : flag === 'source-url'
47
+ ? urlFileName(value)
48
+ : value;
33
49
  const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
34
50
  const statePath = (stateDir, operationId) => {
35
51
  if (!uuid.test(operationId))
@@ -0,0 +1,42 @@
1
+ import type { CanvasExecution } from '@assethub/api-client';
2
+ import type { ProductionBatchItemResult } from './productionBatch.js';
3
+ type Clock = () => Date;
4
+ type Write = (text: string) => void;
5
+ /** Where the run is: the summary with `Step 3 of 4` shortened to `3/4`. */
6
+ export declare const stepText: (execution: CanvasExecution) => string;
7
+ /**
8
+ * Why a run stopped, in plain words. The backend sends V4 findings as a
9
+ * Python-style list (`[{'code': ..., 'message': ...}]`); each becomes
10
+ * `message (code)`.
11
+ */
12
+ export declare const failureText: (execution: CanvasExecution) => string;
13
+ /** `runs watch`, `production analyze --wait` and friends: one run. */
14
+ export declare const runReporter: ({ write, now, }: {
15
+ write: Write;
16
+ now?: Clock;
17
+ }) => (execution: CanvasExecution) => void;
18
+ export type BatchImage = {
19
+ key: string;
20
+ imageIndex: number;
21
+ repeat: number;
22
+ name: string;
23
+ };
24
+ /**
25
+ * `production batch`: a header naming each image by letter (A, B, …; `A#2`
26
+ * for the second repeat), a line per real change, and a status table.
27
+ */
28
+ export declare const batchReporter: (options: {
29
+ write: Write;
30
+ now?: Clock;
31
+ operationId: string;
32
+ agent: string;
33
+ canvasId: number;
34
+ concurrency?: number;
35
+ images: BatchImage[];
36
+ }) => {
37
+ header: () => void;
38
+ item: (result: ProductionBatchItemResult) => void;
39
+ table: (title: string) => void;
40
+ };
41
+ export {};
42
+ //# sourceMappingURL=progressReport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progressReport.d.ts","sourceRoot":"","sources":["../src/progressReport.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAA;AAEzD,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,sBAAsB,CAAA;AAEnE,KAAK,KAAK,GAAG,MAAM,IAAI,CAAA;AACvB,KAAK,KAAK,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;AAqBnC,2EAA2E;AAC3E,eAAO,MAAM,QAAQ,GAAI,WAAW,eAAe,KAAG,MAGhC,CAAA;AAEtB;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,WAAW,eAAe,KAAG,MAMtD,CAAA;AAuFH,sEAAsE;AACtE,eAAO,MAAM,WAAW,GAAI,iBAGzB;IACD,KAAK,EAAE,KAAK,CAAA;IACZ,GAAG,CAAC,EAAE,KAAK,CAAA;CACZ,MAGS,WAAW,eAAe,SAanC,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS;IACrC,KAAK,EAAE,KAAK,CAAA;IACZ,GAAG,CAAC,EAAE,KAAK,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,UAAU,EAAE,CAAA;CACrB;;mBA2CuB,yBAAyB;mBAqDzB,MAAM;CAsC7B,CAAA"}
@@ -0,0 +1,201 @@
1
+ // What `--wait` prints while runs work: one line per real change (a new step,
2
+ // another mesh ready, a part kept with an issue, the end of the run), with the
3
+ // local time, and for a batch the image's letter and file name. Part states
4
+ // that flicker (drawing, checking, redoing) never print a line on their own.
5
+ // A stopped run reports phase `done` and step 4 whatever stopped it, so where
6
+ // it stopped is the last step seen while it worked.
7
+ const stepNames = {
8
+ 1: 'Planning the parts',
9
+ 2: 'Making the base body',
10
+ 3: 'Making the parts',
11
+ 4: 'Assembling in Blender',
12
+ };
13
+ const pad2 = (value) => String(value).padStart(2, '0');
14
+ const clockTime = (date) => `${pad2(date.getHours())}:${pad2(date.getMinutes())}`;
15
+ const shortId = (id) => id.slice(0, 8);
16
+ const duration = (ms) => {
17
+ const minutes = Math.max(0, Math.round(ms / 60_000));
18
+ return minutes < 60
19
+ ? `${minutes} min`
20
+ : `${Math.floor(minutes / 60)} h ${pad2(minutes % 60)} min`;
21
+ };
22
+ /** Where the run is: the summary with `Step 3 of 4` shortened to `3/4`. */
23
+ export const stepText = (execution) => execution.progress
24
+ ? execution.progress.summary.replace(/^Step (\d+) of (\d+) · /, '$1/$2 ')
25
+ : execution.status;
26
+ /**
27
+ * Why a run stopped, in plain words. The backend sends V4 findings as a
28
+ * Python-style list (`[{'code': ..., 'message': ...}]`); each becomes
29
+ * `message (code)`.
30
+ */
31
+ export const failureText = (execution) => plainError(execution.error?.message ??
32
+ execution.progress?.outcome?.detail ??
33
+ execution.history.error ??
34
+ execution.status);
35
+ const plainError = (message, code) => {
36
+ const findings = [...message.matchAll(/\{([^{}]*)\}/g)].flatMap(([, body]) => {
37
+ const field = (name) => new RegExp(`['"]${name}['"]:\\s*['"]([^'"]*)['"]`).exec(body)?.[1];
38
+ const text = field('message');
39
+ return text
40
+ ? [`${text}${field('code') ? ` (${field('code')})` : ''}`]
41
+ : [];
42
+ });
43
+ if (message.trim().startsWith('[') && findings.length)
44
+ return findings.join('; ');
45
+ return code ? `${message} (${code})` : message;
46
+ };
47
+ const isFailure = (status) => status === 'failed' || status === 'cancelled';
48
+ const stoppedAt = (lastStep) => lastStep && stepNames[lastStep]
49
+ ? `at ${lastStep}/4 ${stepNames[lastStep]} · `
50
+ : '';
51
+ const terminalText = (execution, status, lastStep) => {
52
+ if (isFailure(status)) {
53
+ return `✗ FAILED · ${stoppedAt(lastStep)}${failureText(execution)}`;
54
+ }
55
+ if (status === 'completed')
56
+ return `✓ done · ${stepText(execution)}`;
57
+ if (status === 'needs_review')
58
+ return `⚠ needs review · ${stepText(execution)}`;
59
+ return `⚠ ${status} · ${stepText(execution)}`;
60
+ };
61
+ const isTerminal = (status) => !['queued', 'running', 'dispatched'].includes(status);
62
+ /** The step a still-working run is on, to say later where it stopped. */
63
+ const workingStep = (execution, status) => !isTerminal(status) ? execution.progress?.step : undefined;
64
+ /** Parts that became kept-with-an-issue or failed since the last report. */
65
+ const newIssues = (previous, next) => {
66
+ const before = new Map(previous?.progress?.parts.map(part => [part.label, part.state]));
67
+ return (next.progress?.parts ?? [])
68
+ .filter(part => (part.state === 'kept' || part.state === 'failed') &&
69
+ before.get(part.label) !== part.state)
70
+ .map(part => `${part.state === 'kept' ? '⚠ kept with an issue' : '✗ part needs a person'}: ${part.label}${part.note ? ` · ${part.note}` : ''}`);
71
+ };
72
+ /**
73
+ * The lines one run's new receipt adds: its step or its end when either
74
+ * changed, then any part that newly needs attention. `status` is the batch
75
+ * item's status when it differs from the receipt's; `lastStep` the last step
76
+ * seen while the run worked.
77
+ */
78
+ const changeLines = (previous, next, status = next.status, previousStatus = previous?.status, lastStep) => {
79
+ const text = isTerminal(status)
80
+ ? terminalText(next, status, lastStep)
81
+ : stepText(next);
82
+ const before = previous && previousStatus
83
+ ? isTerminal(previousStatus)
84
+ ? terminalText(previous, previousStatus)
85
+ : stepText(previous)
86
+ : undefined;
87
+ return [...(text !== before ? [text] : []), ...newIssues(previous, next)];
88
+ };
89
+ /** `runs watch`, `production analyze --wait` and friends: one run. */
90
+ export const runReporter = ({ write, now = () => new Date(), }) => {
91
+ let last;
92
+ let lastStep;
93
+ return (execution) => {
94
+ const lines = changeLines(last, execution, execution.status, last?.status, lastStep);
95
+ last = execution;
96
+ lastStep = workingStep(execution, execution.status) ?? lastStep;
97
+ for (const line of lines)
98
+ write(`${clockTime(now())} run ${shortId(execution.runId)} ${line}\n`);
99
+ };
100
+ };
101
+ /**
102
+ * `production batch`: a header naming each image by letter (A, B, …; `A#2`
103
+ * for the second repeat), a line per real change, and a status table.
104
+ */
105
+ export const batchReporter = (options) => {
106
+ const { write, images } = options;
107
+ const now = options.now ?? (() => new Date());
108
+ const repeats = images.some(image => image.repeat > 1);
109
+ const letter = (image) => `${image.imageIndex < 26 ? String.fromCharCode(65 + image.imageIndex) : image.imageIndex + 1}${repeats ? `#${image.repeat}` : ''}`;
110
+ const letterWidth = Math.max(...images.map(image => letter(image).length));
111
+ const nameWidth = Math.max(...images.map(image => image.name.length));
112
+ const who = (key) => {
113
+ const image = images.find(entry => entry.key === key);
114
+ return `${letter(image).padEnd(letterWidth)} ${image.name.padEnd(nameWidth)}`;
115
+ };
116
+ const state = new Map();
117
+ const say = (key, line) => write(`${clockTime(now())} ${who(key)} ${line}\n`);
118
+ const header = () => {
119
+ const runs = `${images.length} run${images.length === 1 ? '' : 's'}${options.concurrency ? `, ${options.concurrency} at a time` : ''}`;
120
+ write([
121
+ `Batch ${shortId(options.operationId)} · ${options.agent} · ${runs} · canvas ${options.canvasId}`,
122
+ ...images
123
+ .filter(image => !repeats || image.repeat === 1)
124
+ .map(image => ` ${String.fromCharCode(65 + image.imageIndex).padEnd(1)} ${image.name}`),
125
+ ` Resume with: --operation-id ${options.operationId}`,
126
+ '',
127
+ '',
128
+ ].join('\n'));
129
+ };
130
+ const item = (result) => {
131
+ const entry = state.get(result.key);
132
+ const previous = entry?.result;
133
+ const lines = [];
134
+ if (result.execution) {
135
+ if (!previous?.execution)
136
+ lines.push(isTerminal(result.status)
137
+ ? terminalText(result.execution, result.status)
138
+ : `started run ${shortId(result.execution.runId)} · ${stepText(result.execution)}`, ...newIssues(undefined, result.execution));
139
+ else
140
+ lines.push(...changeLines(previous.execution, result.execution, result.status, previous.status, entry?.lastStep));
141
+ }
142
+ else if (result.status === 'deferred') {
143
+ lines.push(`waiting for a free run slot · ${result.message ?? result.reason ?? ''}`);
144
+ }
145
+ else if (result.status === 'failed') {
146
+ lines.push(`✗ could not start · ${plainError(result.message ?? 'failed', result.code)}`);
147
+ }
148
+ const next = {
149
+ result,
150
+ startedAt: entry?.startedAt ??
151
+ (result.execution ? new Date(result.execution.createdAt) : undefined),
152
+ endedAt: entry?.endedAt ??
153
+ (result.execution && isTerminal(result.status) ? now() : undefined),
154
+ lastLine: entry?.lastLine,
155
+ lastStep: (result.execution && workingStep(result.execution, result.status)) ??
156
+ entry?.lastStep,
157
+ };
158
+ for (const line of lines) {
159
+ if (line === next.lastLine)
160
+ continue;
161
+ next.lastLine = line;
162
+ say(result.key, line);
163
+ }
164
+ state.set(result.key, next);
165
+ };
166
+ /** Every image's state now: printed every few minutes and at the end. */
167
+ const table = (title) => {
168
+ const rows = images.map(image => {
169
+ const entry = state.get(image.key);
170
+ const result = entry?.result;
171
+ const execution = result?.execution;
172
+ const [mark, detail] = !result
173
+ ? ['○ not started', '']
174
+ : execution
175
+ ? isFailure(result.status)
176
+ ? [
177
+ '✗ failed',
178
+ `${entry?.lastStep ? `at ${entry.lastStep}/4 · ` : ''}${failureText(execution)}`,
179
+ ]
180
+ : result.status === 'completed'
181
+ ? ['✓ done', stepText(execution)]
182
+ : isTerminal(result.status)
183
+ ? [`⚠ ${result.status.replace('_', ' ')}`, stepText(execution)]
184
+ : ['● running', stepText(execution)]
185
+ : result.status === 'deferred'
186
+ ? ['… waiting', result.message ?? result.reason ?? '']
187
+ : result.status === 'failed'
188
+ ? [
189
+ '✗ failed',
190
+ plainError(result.message ?? 'failed', result.code),
191
+ ]
192
+ : ['○ not started', ''];
193
+ const took = entry?.startedAt && !Number.isNaN(entry.startedAt.getTime())
194
+ ? ` ${duration((entry.endedAt ?? now()).getTime() - entry.startedAt.getTime())}`
195
+ : '';
196
+ return ` ${who(image.key)} ${mark.padEnd(12)}${detail}${took}`;
197
+ });
198
+ write(['', `── ${clockTime(now())} · ${title} ──`, ...rows, '', ''].join('\n'));
199
+ };
200
+ return { header, item, table };
201
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assethub/cli",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "description": "Command line interface for AssetHub.",
5
5
  "type": "module",
6
6
  "bin": {