@assethub/cli 0.1.31 → 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 +9 -2
- package/dist/hooks/command.d.ts.map +1 -1
- package/dist/hooks/command.js +21 -18
- package/dist/hooks/graph.d.ts.map +1 -1
- package/dist/hooks/graph.js +7 -5
- package/dist/hooks/install.d.ts +24 -9
- package/dist/hooks/install.d.ts.map +1 -1
- package/dist/hooks/install.js +147 -35
- package/dist/hooks/save.d.ts +3 -1
- package/dist/hooks/save.d.ts.map +1 -1
- package/dist/hooks/save.js +3 -2
- package/dist/hooks/transcript.d.ts.map +1 -1
- package/dist/hooks/transcript.js +5 -1
- package/dist/hooks/trim.d.ts +12 -2
- package/dist/hooks/trim.d.ts.map +1 -1
- package/dist/hooks/trim.js +117 -5
- package/dist/hooks/types.d.ts +1 -1
- package/dist/hooks/types.d.ts.map +1 -1
- package/dist/hooks/upload.js +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -16
- package/dist/productionBatch.d.ts +2 -0
- package/dist/productionBatch.d.ts.map +1 -1
- package/dist/productionBatch.js +17 -1
- package/dist/progressReport.d.ts +42 -0
- package/dist/progressReport.d.ts.map +1 -0
- package/dist/progressReport.js +201 -0
- package/dist/setupCommand.d.ts +3 -0
- package/dist/setupCommand.d.ts.map +1 -1
- package/dist/setupCommand.js +57 -22
- package/dist/setupHooksBridge.d.ts +4 -0
- package/dist/setupHooksBridge.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/hooks/trim.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// Keep only the conversation from a Claude Code transcript
|
|
1
|
+
// Keep only the conversation from a Claude Code transcript (or a Codex rollout,
|
|
2
|
+
// converted to the same shape) before it is saved
|
|
2
3
|
// or uploaded: the artist's prompts, the agent's replies, its tool calls and
|
|
3
4
|
// their results, and compaction summaries. Everything else Claude Code writes
|
|
4
5
|
// to the file (skill, tool and agent listings, the system prompt, hook
|
|
@@ -85,9 +86,118 @@ export const trimTranscriptEntry = (entry) => {
|
|
|
85
86
|
},
|
|
86
87
|
};
|
|
87
88
|
};
|
|
89
|
+
/** Top-level record types of a Codex rollout (`~/.codex/sessions/.../rollout-*.jsonl`). */
|
|
90
|
+
const CODEX_LINE_TYPES = new Set(['session_meta', 'response_item', 'event_msg', 'turn_context', 'compacted']);
|
|
91
|
+
// Codex sends its context as user messages: AGENTS.md, and tagged blocks such
|
|
92
|
+
// as <environment_context> or <user_instructions>. They are not the artist's words.
|
|
93
|
+
const CODEX_CONTEXT_BLOCK = /<([A-Za-z][\w-]*(?:context|instructions|plugins))(?:\s[^>]*)?>[\s\S]*?<\/\1>\s*/g;
|
|
94
|
+
const codexUserText = (text) => /^\s*# AGENTS\.md instructions/.test(text) ? '' : cleanText(text.replace(CODEX_CONTEXT_BLOCK, ''));
|
|
95
|
+
const codexText = (content, blockType, clean) => (Array.isArray(content) ? content : [])
|
|
96
|
+
.map(block => (isObject(block) && block.type === blockType && typeof block.text === 'string' ? clean(block.text) : ''))
|
|
97
|
+
.filter(text => text.trim())
|
|
98
|
+
.join('\n');
|
|
99
|
+
// An exec result is JSON text with timing and token bookkeeping around the
|
|
100
|
+
// command's output; only the output is conversation.
|
|
101
|
+
const codexOutputText = (text) => {
|
|
102
|
+
if (text.trimStart().startsWith('{')) {
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(text);
|
|
105
|
+
if (isObject(parsed) && typeof parsed.output === 'string')
|
|
106
|
+
return parsed.output;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// plain text that happens to start with a brace
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return text;
|
|
113
|
+
};
|
|
114
|
+
/** Tool output: a string, `{output}`, or a list of `{type, text}` items (images are dropped). */
|
|
115
|
+
const codexToolOutput = (output) => {
|
|
116
|
+
if (typeof output === 'string')
|
|
117
|
+
return cleanText(codexOutputText(output));
|
|
118
|
+
if (Array.isArray(output))
|
|
119
|
+
return cleanText(output
|
|
120
|
+
.map(item => (isObject(item) && typeof item.text === 'string' ? codexOutputText(item.text) : ''))
|
|
121
|
+
.filter(Boolean)
|
|
122
|
+
.join('\n'));
|
|
123
|
+
if (isObject(output)) {
|
|
124
|
+
for (const key of ['output', 'content'])
|
|
125
|
+
if (typeof output[key] === 'string')
|
|
126
|
+
return cleanText(output[key]);
|
|
127
|
+
}
|
|
128
|
+
return output === undefined ? '' : JSON.stringify(output);
|
|
129
|
+
};
|
|
130
|
+
const codexArguments = (value) => {
|
|
131
|
+
if (typeof value !== 'string')
|
|
132
|
+
return value ?? {};
|
|
133
|
+
try {
|
|
134
|
+
return JSON.parse(value);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return { arguments: value };
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const codexMessage = (type, timestamp, content) => ({
|
|
141
|
+
type,
|
|
142
|
+
...(typeof timestamp === 'string' ? { timestamp } : {}),
|
|
143
|
+
message: { role: type, content },
|
|
144
|
+
});
|
|
145
|
+
/**
|
|
146
|
+
* One Codex rollout record as a conversation entry in the same shape as a
|
|
147
|
+
* trimmed Claude Code transcript, or undefined to drop it. Kept: the artist's
|
|
148
|
+
* prompts, the agent's replies, its tool calls and their output, and compaction
|
|
149
|
+
* summaries. Dropped: session metadata (base instructions, git, cwd), developer
|
|
150
|
+
* and context messages, reasoning, turn settings and the event stream, which
|
|
151
|
+
* repeats the messages.
|
|
152
|
+
*/
|
|
153
|
+
export const codexEntry = (entry) => {
|
|
154
|
+
if (!isObject(entry) || !isObject(entry.payload))
|
|
155
|
+
return undefined;
|
|
156
|
+
const { payload, timestamp } = entry;
|
|
157
|
+
if (entry.type === 'compacted')
|
|
158
|
+
return typeof payload.message === 'string' && payload.message.trim() ? { type: 'summary', summary: payload.message } : undefined;
|
|
159
|
+
if (entry.type !== 'response_item')
|
|
160
|
+
return undefined;
|
|
161
|
+
switch (payload.type) {
|
|
162
|
+
case 'message': {
|
|
163
|
+
if (payload.role === 'user') {
|
|
164
|
+
const text = codexText(payload.content, 'input_text', codexUserText).trim();
|
|
165
|
+
return text ? codexMessage('user', timestamp, text) : undefined;
|
|
166
|
+
}
|
|
167
|
+
if (payload.role !== 'assistant')
|
|
168
|
+
return undefined;
|
|
169
|
+
const text = codexText(payload.content, 'output_text', cleanText);
|
|
170
|
+
return text.trim() ? codexMessage('assistant', timestamp, [{ type: 'text', text }]) : undefined;
|
|
171
|
+
}
|
|
172
|
+
case 'function_call':
|
|
173
|
+
case 'custom_tool_call':
|
|
174
|
+
case 'local_shell_call': {
|
|
175
|
+
const id = typeof payload.call_id === 'string' ? payload.call_id : payload.id;
|
|
176
|
+
if (typeof id !== 'string')
|
|
177
|
+
return undefined;
|
|
178
|
+
const name = typeof payload.name === 'string' ? payload.name : payload.type === 'local_shell_call' ? 'shell' : 'unknown';
|
|
179
|
+
const input = payload.type === 'function_call'
|
|
180
|
+
? codexArguments(payload.arguments)
|
|
181
|
+
: payload.type === 'custom_tool_call'
|
|
182
|
+
? { input: payload.input }
|
|
183
|
+
: (payload.action ?? {});
|
|
184
|
+
return codexMessage('assistant', timestamp, [{ type: 'tool_use', id, name, input }]);
|
|
185
|
+
}
|
|
186
|
+
case 'function_call_output':
|
|
187
|
+
case 'custom_tool_call_output': {
|
|
188
|
+
if (typeof payload.call_id !== 'string')
|
|
189
|
+
return undefined;
|
|
190
|
+
return codexMessage('user', timestamp, [{ type: 'tool_result', tool_use_id: payload.call_id, content: codexToolOutput(payload.output) }]);
|
|
191
|
+
}
|
|
192
|
+
default:
|
|
193
|
+
// reasoning, web search and anything else that is not conversation
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
88
197
|
/**
|
|
89
|
-
* Trim a JSONL transcript
|
|
90
|
-
*
|
|
198
|
+
* Trim a JSONL transcript: a Claude Code transcript, or a Codex rollout turned
|
|
199
|
+
* into the same conversation shape. A file in any other format is returned
|
|
200
|
+
* unchanged.
|
|
91
201
|
*/
|
|
92
202
|
export const trimTranscript = (raw) => {
|
|
93
203
|
const entries = raw
|
|
@@ -101,8 +211,10 @@ export const trimTranscript = (raw) => {
|
|
|
101
211
|
return undefined;
|
|
102
212
|
}
|
|
103
213
|
});
|
|
104
|
-
|
|
214
|
+
const isClaude = entries.some(entry => isObject(entry) && CLAUDE_LINE_TYPES.has(String(entry.type)));
|
|
215
|
+
const isCodex = !isClaude && entries.some(entry => isObject(entry) && CODEX_LINE_TYPES.has(String(entry.type)) && isObject(entry.payload));
|
|
216
|
+
if (!isClaude && !isCodex)
|
|
105
217
|
return raw;
|
|
106
|
-
const kept = entries.map(trimTranscriptEntry).filter((entry) => entry !== undefined);
|
|
218
|
+
const kept = entries.map(isCodex ? codexEntry : trimTranscriptEntry).filter((entry) => entry !== undefined);
|
|
107
219
|
return kept.map(entry => `${JSON.stringify(entry)}\n`).join('');
|
|
108
220
|
};
|
package/dist/hooks/types.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/hooks/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAA;AAE3C,MAAM,MAAM,SAAS,GAAG;IACtB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,gBAAgB,GAAG,UAAU,CAAA;AAEnE,MAAM,MAAM,YAAY,GAAG;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAA;AAEvF,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/hooks/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAA;AAE3C,MAAM,MAAM,SAAS,GAAG;IACtB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,gBAAgB,GAAG,UAAU,CAAA;AAEnE,MAAM,MAAM,YAAY,GAAG;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAA;AAEvF,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,UAAU,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,aAAa,CAAA;IACrB,MAAM,EAAE,YAAY,EAAE,CAAA;IACtB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,gGAAgG;IAChG,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,kEAAkE;IAClE,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,mEAAmE;IACnE,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,uFAAuF;IACvF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,8FAA8F;IAC9F,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,OAAO,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sFAAsF;IACtF,SAAS,CAAC,EAAE,IAAI,CAAA;CACjB,CAAA"}
|
package/dist/hooks/upload.js
CHANGED
|
@@ -177,7 +177,7 @@ const uploadSessionUnlocked = async (sessionDir, options) => {
|
|
|
177
177
|
graphId: sessionGraphId(basename(sessionDir)),
|
|
178
178
|
rev,
|
|
179
179
|
description: `Agent session ${meta.sessionId}`,
|
|
180
|
-
tags: ['session', '
|
|
180
|
+
tags: ['session', `agent:${meta.client ?? 'claude'}`],
|
|
181
181
|
now: now(),
|
|
182
182
|
});
|
|
183
183
|
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA2BA,OAAO,EAAsB,KAAK,YAAY,EAAC,MAAM,uBAAuB,CAAA;
|
|
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 {
|
|
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:
|
|
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
|
|
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;
|
|
1
|
+
{"version":3,"file":"productionBatch.d.ts","sourceRoot":"","sources":["../src/productionBatch.ts"],"names":[],"mappings":"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"}
|
package/dist/productionBatch.js
CHANGED
|
@@ -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
|
-
?
|
|
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/dist/setupCommand.d.ts
CHANGED
|
@@ -53,6 +53,8 @@ type DoctorReport = {
|
|
|
53
53
|
export type SetupDeps = {
|
|
54
54
|
interactive: boolean;
|
|
55
55
|
installHooks: InstallHooks;
|
|
56
|
+
/** Session saving is installed for this folder only. */
|
|
57
|
+
cwd: string;
|
|
56
58
|
codexHome: string;
|
|
57
59
|
now: () => Date;
|
|
58
60
|
hasWorkingKey: () => Promise<boolean>;
|
|
@@ -108,6 +110,7 @@ export declare const runProcess: (file: string, args: string[]) => Promise<{
|
|
|
108
110
|
export declare const defaultFileDeps: () => {
|
|
109
111
|
platform: NodeJS.Platform;
|
|
110
112
|
home: string;
|
|
113
|
+
cwd: string;
|
|
111
114
|
codexHome: string;
|
|
112
115
|
now: () => Date;
|
|
113
116
|
findBinary: (name: string) => Promise<string | undefined>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setupCommand.d.ts","sourceRoot":"","sources":["../src/setupCommand.ts"],"names":[],"mappings":"AAKA,OAAO,EAAC,WAAW,EAAwE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"setupCommand.d.ts","sourceRoot":"","sources":["../src/setupCommand.ts"],"names":[],"mappings":"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"}
|