@remixmate/cli 0.9.71 → 0.9.73
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/LICENSE +45 -21
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/argv.js +1 -51
- package/dist/auth/auth-lock.js +1 -100
- package/dist/auth/auto-login.js +7 -75
- package/dist/auth/commands.js +40 -290
- package/dist/auth/credential-store.js +1 -143
- package/dist/auth/device-flow-runner.js +13 -110
- package/dist/auth/device-flow.js +1 -132
- package/dist/auth/ensure.js +1 -62
- package/dist/auth/environment.js +1 -69
- package/dist/auth/pending-store.js +1 -45
- package/dist/auth/resolve.js +1 -44
- package/dist/billing.js +3 -76
- package/dist/capabilities.js +1 -75
- package/dist/cli.js +39 -211
- package/dist/diagnostics.js +2 -70
- package/dist/doctor.js +9 -188
- package/dist/errors.js +1 -33
- package/dist/exec.js +4 -47
- package/dist/handlers/gen-digital-human.js +33 -264
- package/dist/handlers/gen-image.js +9 -113
- package/dist/handlers/gen-post.js +15 -188
- package/dist/handlers/gen-video.js +7 -185
- package/dist/handlers/gen-voice.js +19 -133
- package/dist/handlers/index.js +1 -20
- package/dist/handlers/shared.js +1 -66
- package/dist/handlers/template-init.js +1 -21
- package/dist/http.js +3 -155
- package/dist/index.js +1 -8
- package/dist/install.js +40 -448
- package/dist/manifest.json +53 -13
- package/dist/progress.js +2 -15
- package/dist/project/commands.js +34 -156
- package/dist/project/host.js +1 -151
- package/dist/project/resolve.js +1 -65
- package/dist/project/store.js +2 -97
- package/dist/project/take.js +1 -106
- package/dist/project/trace.js +5 -278
- package/dist/registry.js +3 -70
- package/dist/runner.js +7 -225
- package/dist/skill-schema.js +1 -194
- package/dist/template/args.js +1 -69
- package/dist/template/check.js +2 -30
- package/dist/template/commands.js +34 -163
- package/dist/template/constants.js +1 -46
- package/dist/template/init.js +5 -133
- package/dist/template/list.js +1 -46
- package/dist/template/preview.js +7 -318
- package/dist/template/proc.js +1 -43
- package/dist/template/project.js +6 -161
- package/dist/template/publish.js +5 -185
- package/dist/template/render-client.js +2 -79
- package/dist/template/upgrade.js +4 -123
- package/dist/text.js +1 -15
- package/package.json +2 -35
- package/skills/doc-parse/scripts/extract_via_render.py +32 -32
- package/skills/export-jianying/scripts/gen_jianying_draft.py +188 -188
- package/skills/gen-post/SKILL.md +23 -13
- package/skills/gen-post/skill.json +9 -10
- package/skills/gen-script/SKILL.md +23 -1
- package/skills/gen-script/scripts/gen_script.py +644 -551
- package/skills/gen-script/skill.json +12 -0
- package/skills/gen-script/version.json +1 -1
- package/skills/prepare-video-assets/scripts/prepare_video_assets.py +21 -21
- package/skills/render-video/scripts/_chrome_vendor.py +17 -17
- package/skills/render-video/scripts/_digital_human_segments.py +34 -34
- package/skills/render-video/scripts/_video_probe.py +33 -33
- package/skills/render-video/scripts/_vod_polling.py +13 -13
- package/skills/render-video/scripts/remote_renderer_client.py +42 -42
- package/skills/render-video/scripts/render_video.py +639 -639
- package/skills/render-video/scripts/required_props.py +37 -37
- package/skills/render-video/scripts/upload_video.py +35 -35
- package/skills/render-video/version.json +1 -1
- package/skills/template-registry/SKILL.md +16 -0
- package/skills/template-registry/scripts/countdown_spec.py +92 -92
- package/skills/template-registry/scripts/doc_extract.py +40 -40
- package/skills/template-registry/scripts/list_templates.py +303 -191
- package/skills/template-registry/scripts/log_diagnostics.py +1 -1
- package/skills/template-registry/scripts/match_template.py +87 -87
- package/skills/template-registry/scripts/payload_hydrator.py +89 -89
- package/skills/template-registry/scripts/registry_loader.py +159 -159
- package/skills/template-registry/scripts/render_job_client.py +75 -75
- package/skills/template-registry/scripts/showcase_loader.py +44 -44
- package/skills/template-registry/skill.json +4 -1
- package/skills/template-registry/version.json +1 -1
- package/skills/template-registry/video_dsl/runtime/__init__.py +8 -8
- package/skills/template-registry/video_dsl/runtime/dsl_validator.py +329 -329
- package/skills/template-registry/video_dsl/runtime/narration_budget.py +36 -36
- package/skills/template-registry/video_dsl/runtime/payload_contract.py +7 -7
- package/skills/template-registry/video_dsl/runtime/prompt_enhancer.py +98 -98
- package/skills/template-registry/video_dsl/runtime/timeline_compiler.py +127 -127
- package/skills/template-registry/video_dsl/schema/video-dsl-v1alpha1.json +14 -1
- package/skills/video-parser/scripts/analyze_video.py +173 -173
- package/skills/video-parser/scripts/deconstruct_video.py +91 -91
- package/skills/video-parser/scripts/parse_via_render.py +21 -21
- package/skills/web-screenshot/scripts/_media_screenshot/__init__.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/bootstrap.py +7 -7
- package/skills/web-screenshot/scripts/_media_screenshot/browser.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/cli_args.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/js_loader.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/overlay.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/reader.py +40 -40
- package/skills/web-screenshot/scripts/_media_screenshot/recording.py +11 -11
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/__init__.py +8 -8
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/arrow.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/caption.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/click.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/code_lines.py +5 -5
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/focus.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/highlight.py +3 -3
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/hover.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/redact.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/scroll.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/sequence.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/title_card.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/type.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/wait.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/zoom.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/screenshot.py +15 -15
- package/skills/web-screenshot/scripts/_media_screenshot/storyboard.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/template.py +11 -11
- package/skills/web-screenshot/scripts/_media_screenshot/trim.py +10 -10
- package/skills/web-screenshot/scripts/_media_screenshot/urlguard.py +22 -22
- package/skills/web-screenshot/scripts/_media_upload.py +33 -33
- package/skills/web-screenshot/scripts/read_page.py +15 -15
- package/skills/web-screenshot/scripts/record.py +26 -26
- package/skills/web-screenshot/scripts/screenshot.py +5 -5
- package/dist/argv.d.ts +0 -15
- package/dist/auth/auth-lock.d.ts +0 -26
- package/dist/auth/auto-login.d.ts +0 -20
- package/dist/auth/commands.d.ts +0 -8
- package/dist/auth/credential-store.d.ts +0 -60
- package/dist/auth/device-flow-runner.d.ts +0 -58
- package/dist/auth/device-flow.d.ts +0 -52
- package/dist/auth/ensure.d.ts +0 -55
- package/dist/auth/environment.d.ts +0 -38
- package/dist/auth/pending-store.d.ts +0 -33
- package/dist/auth/resolve.d.ts +0 -30
- package/dist/billing.d.ts +0 -44
- package/dist/capabilities.d.ts +0 -63
- package/dist/cli.d.ts +0 -13
- package/dist/diagnostics.d.ts +0 -24
- package/dist/doctor.d.ts +0 -22
- package/dist/errors.d.ts +0 -29
- package/dist/exec.d.ts +0 -18
- package/dist/handlers/gen-digital-human.d.ts +0 -17
- package/dist/handlers/gen-image.d.ts +0 -20
- package/dist/handlers/gen-post.d.ts +0 -21
- package/dist/handlers/gen-video.d.ts +0 -19
- package/dist/handlers/gen-voice.d.ts +0 -26
- package/dist/handlers/index.d.ts +0 -20
- package/dist/handlers/shared.d.ts +0 -26
- package/dist/handlers/template-init.d.ts +0 -10
- package/dist/http.d.ts +0 -72
- package/dist/index.d.ts +0 -9
- package/dist/install.d.ts +0 -71
- package/dist/progress.d.ts +0 -23
- package/dist/project/commands.d.ts +0 -19
- package/dist/project/host.d.ts +0 -91
- package/dist/project/resolve.d.ts +0 -43
- package/dist/project/store.d.ts +0 -31
- package/dist/project/take.d.ts +0 -50
- package/dist/project/trace.d.ts +0 -98
- package/dist/registry.d.ts +0 -30
- package/dist/runner.d.ts +0 -39
- package/dist/skill-schema.d.ts +0 -138
- package/dist/template/args.d.ts +0 -25
- package/dist/template/check.d.ts +0 -23
- package/dist/template/commands.d.ts +0 -25
- package/dist/template/constants.d.ts +0 -39
- package/dist/template/init.d.ts +0 -28
- package/dist/template/list.d.ts +0 -23
- package/dist/template/preview.d.ts +0 -60
- package/dist/template/proc.d.ts +0 -26
- package/dist/template/project.d.ts +0 -47
- package/dist/template/publish.d.ts +0 -43
- package/dist/template/render-client.d.ts +0 -39
- package/dist/template/upgrade.d.ts +0 -44
- package/dist/text.d.ts +0 -8
package/dist/project/trace.js
CHANGED
|
@@ -1,278 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* its result into `media_chat_turn_trace` (ab-agent/src/trace-persist.ts). The
|
|
7
|
-
* local path records nothing — a take created from codex / claude code has an
|
|
8
|
-
* empty conversation, zero tool calls and zero credits in the analytics tables,
|
|
9
|
-
* so every session-level metric silently excludes it.
|
|
10
|
-
*
|
|
11
|
-
* What we can honestly report and what we cannot: the CLI is a child process
|
|
12
|
-
* spawned once per skill call. It never sees the user's prompt or the host's
|
|
13
|
-
* reply — those live in the host's own transcript. So this module reports the
|
|
14
|
-
* one thing it genuinely observes: the invocation itself.
|
|
15
|
-
*
|
|
16
|
-
* See docs/product/cli/cli-session-record-design.md §5.
|
|
17
|
-
*/
|
|
18
|
-
import os from 'node:os';
|
|
19
|
-
import { cleanDiagnosticText, outcomeOf } from '../diagnostics.js';
|
|
20
|
-
import { billingSummary } from '../billing.js';
|
|
21
|
-
import { packageVersion } from '../install.js';
|
|
22
|
-
import { mmPost } from '../http.js';
|
|
23
|
-
/** Same ceiling ab-agent uses for a tool result — one table, one limit. */
|
|
24
|
-
const TOOL_RESULT_MAX = 16 * 1024;
|
|
25
|
-
/** Raw bytes held before trimming. Generous: trimming is the tail, cleaning is cheap. */
|
|
26
|
-
const RAW_CAPTURE_MAX = 64 * 1024;
|
|
27
|
-
/** Per-argument ceiling. Prompts and narration bodies are content, not evidence. */
|
|
28
|
-
const ARG_VALUE_MAX = 512;
|
|
29
|
-
/** `error_summary` is varchar(1024). */
|
|
30
|
-
const ERROR_SUMMARY_MAX = 1024;
|
|
31
|
-
const FLUSH_TIMEOUT_MS = 10_000;
|
|
32
|
-
const SENSITIVE_KEY = /token|secret|password|api[_-]?key|authorization/i;
|
|
33
|
-
/**
|
|
34
|
-
* Whether this invocation should report at all.
|
|
35
|
-
*
|
|
36
|
-
* The rule is deliberately narrow: report only when THIS process resolved the
|
|
37
|
-
* take. Everything else is already covered by someone else —
|
|
38
|
-
* - under the cloud agent, CONVERSATION_ID arrives pre-set and ab-agent's own
|
|
39
|
-
* TurnTracer already recorded the very same call;
|
|
40
|
-
* - a nested `gen-image` spawned by prepare-video-assets inherits its
|
|
41
|
-
* parent's CONVERSATION_ID, and the parent's row already covers it.
|
|
42
|
-
* Since runner.ts only builds a tracer after resolving a take itself, this
|
|
43
|
-
* function just honours the kill switch.
|
|
44
|
-
*/
|
|
45
|
-
export function tracingEnabled(env = process.env) {
|
|
46
|
-
return (env.REMIXMATE_TRACE ?? '').trim() !== '0';
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Collects one invocation's evidence and posts it as a single turn.
|
|
50
|
-
*
|
|
51
|
-
* Lifecycle (runner.ts): `begin()` → output flows through the tap → `finish()`
|
|
52
|
-
* in a finally block. Every failure mode ends in a warning on stderr: a lost
|
|
53
|
-
* trace row must never cost the user a render they already paid for.
|
|
54
|
-
*/
|
|
55
|
-
export class InvocationTrace {
|
|
56
|
-
opts;
|
|
57
|
-
chunks = '';
|
|
58
|
-
restore = null;
|
|
59
|
-
flushed = false;
|
|
60
|
-
constructor(opts) {
|
|
61
|
-
this.opts = opts;
|
|
62
|
-
}
|
|
63
|
-
/** Returns null when tracing is off — callers can then skip output capture entirely. */
|
|
64
|
-
static begin(opts) {
|
|
65
|
-
if (!tracingEnabled())
|
|
66
|
-
return null;
|
|
67
|
-
if (!opts.take.conversationId)
|
|
68
|
-
return null;
|
|
69
|
-
return new InvocationTrace(opts);
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Tap both output streams.
|
|
73
|
-
*
|
|
74
|
-
* One tap covers both dispatch paths: in-process handlers write here
|
|
75
|
-
* directly, and the Python child's piped output is re-emitted through the
|
|
76
|
-
* same `write` (see runner.ts). Restored in finish().
|
|
77
|
-
*/
|
|
78
|
-
installCapture() {
|
|
79
|
-
if (this.restore)
|
|
80
|
-
return;
|
|
81
|
-
const out = process.stdout.write.bind(process.stdout);
|
|
82
|
-
const err = process.stderr.write.bind(process.stderr);
|
|
83
|
-
const tap = (original) => ((chunk, ...rest) => {
|
|
84
|
-
this.absorb(chunk);
|
|
85
|
-
return original(chunk, ...rest);
|
|
86
|
-
});
|
|
87
|
-
process.stdout.write = tap(out);
|
|
88
|
-
process.stderr.write = tap(err);
|
|
89
|
-
this.restore = () => {
|
|
90
|
-
process.stdout.write = out;
|
|
91
|
-
process.stderr.write = err;
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
/** Feed captured output. Keeps a bounded tail — the decisive line is the last one. */
|
|
95
|
-
absorb(chunk) {
|
|
96
|
-
try {
|
|
97
|
-
const text = typeof chunk === 'string'
|
|
98
|
-
? chunk
|
|
99
|
-
: chunk instanceof Uint8Array
|
|
100
|
-
? Buffer.from(chunk).toString('utf8')
|
|
101
|
-
: '';
|
|
102
|
-
if (!text)
|
|
103
|
-
return;
|
|
104
|
-
this.chunks += text;
|
|
105
|
-
if (this.chunks.length > RAW_CAPTURE_MAX * 2) {
|
|
106
|
-
this.chunks = this.chunks.slice(-RAW_CAPTURE_MAX);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
// Capturing must never break the thing being captured.
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Emit the turn. Idempotent; never throws.
|
|
115
|
-
*
|
|
116
|
-
* `error` is the dispatcher's caught exception when there is one — a Python
|
|
117
|
-
* child that exits non-zero gives us no exception, only its output, which is
|
|
118
|
-
* exactly why the tail buffer exists.
|
|
119
|
-
*/
|
|
120
|
-
async finish(exitCode, durationMs, error) {
|
|
121
|
-
this.restore?.();
|
|
122
|
-
this.restore = null;
|
|
123
|
-
if (this.flushed)
|
|
124
|
-
return;
|
|
125
|
-
this.flushed = true;
|
|
126
|
-
const { skillName, args, take, ctx } = this.opts;
|
|
127
|
-
const output = condense(this.chunks);
|
|
128
|
-
const outcome = outcomeOf(exitCode);
|
|
129
|
-
const isError = exitCode !== 0;
|
|
130
|
-
const rows = [];
|
|
131
|
-
let stepSeq = 0;
|
|
132
|
-
// One env row per take, not per call: it describes the toolchain, and the
|
|
133
|
-
// toolchain does not change between two calls of the same pipeline.
|
|
134
|
-
if (take.created) {
|
|
135
|
-
rows.push({ sessionId: take.sessionKey, stepSeq: stepSeq++, kind: 'env', env: envSnapshot(take) });
|
|
136
|
-
}
|
|
137
|
-
rows.push({
|
|
138
|
-
sessionId: take.sessionKey,
|
|
139
|
-
stepSeq: stepSeq++,
|
|
140
|
-
kind: 'tool_call',
|
|
141
|
-
toolName: `cli__${skillName.replace(/-/g, '_')}`,
|
|
142
|
-
toolUseId: (process.env.AB_OPERATION_ID ?? '').slice(0, 64),
|
|
143
|
-
toolInput: sanitiseArgs(args),
|
|
144
|
-
toolResult: output || '[no output captured]',
|
|
145
|
-
isError,
|
|
146
|
-
durationMs: Math.round(durationMs),
|
|
147
|
-
});
|
|
148
|
-
rows.push({
|
|
149
|
-
sessionId: take.sessionKey,
|
|
150
|
-
stepSeq: stepSeq++,
|
|
151
|
-
kind: 'turn_result',
|
|
152
|
-
outcome,
|
|
153
|
-
errorSummary: isError ? summariseError(error, output) : '',
|
|
154
|
-
credits: billingSummary()?.credits ?? 0,
|
|
155
|
-
});
|
|
156
|
-
try {
|
|
157
|
-
await mmPost(ctx, `/chat/conversation/${encodeURIComponent(take.conversationId)}/trace`,
|
|
158
|
-
// appendTurn: the server allocates turn_seq. N unrelated processes
|
|
159
|
-
// cannot number themselves, and cli-project-binding §5.1 already ruled
|
|
160
|
-
// out keeping local state. Older backends ignore the field and every
|
|
161
|
-
// row lands on turn 0 — still ordered, just ungrouped.
|
|
162
|
-
{ appendTurn: true, traces: rows }, { timeoutMs: FLUSH_TIMEOUT_MS });
|
|
163
|
-
}
|
|
164
|
-
catch (err) {
|
|
165
|
-
process.stderr.write(`⚠️ 执行轨迹未能上报(不影响本次产物): ${cleanDiagnosticText(err.message, 256)}\n`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
/** Which rung of the grouping ladder produced this key (cli-project-binding §5.1). */
|
|
170
|
-
function keySource(key) {
|
|
171
|
-
if (key.startsWith('custom:'))
|
|
172
|
-
return 'env-override';
|
|
173
|
-
if (key.startsWith('job:'))
|
|
174
|
-
return 'render-job';
|
|
175
|
-
return 'host';
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* What this take was produced by. Mirrors ab-agent's envSnapshot() in purpose:
|
|
179
|
-
* answer "which version started failing" without storing anything large.
|
|
180
|
-
*/
|
|
181
|
-
function envSnapshot(take) {
|
|
182
|
-
return {
|
|
183
|
-
// The discriminator every cross-channel metric needs — a CLI "turn" is one
|
|
184
|
-
// skill call, a cloud turn is a whole user request. Never compare blindly.
|
|
185
|
-
channel: 'cli',
|
|
186
|
-
cli_version: packageVersion(),
|
|
187
|
-
host: take.host || 'unknown',
|
|
188
|
-
take_key_source: keySource(take.sessionKey),
|
|
189
|
-
node: process.version,
|
|
190
|
-
platform: `${os.platform()}-${os.arch()}`,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Flags → a trace-safe object.
|
|
195
|
-
*
|
|
196
|
-
* Credentials are dropped outright; long strings are cut to their opening
|
|
197
|
-
* because an argument like `--prompt` is *content*, and this table is evidence,
|
|
198
|
-
* not an archive of what the user wrote.
|
|
199
|
-
*/
|
|
200
|
-
export function sanitiseArgs(args) {
|
|
201
|
-
const out = {};
|
|
202
|
-
for (const [key, value] of Object.entries(args).slice(0, 40)) {
|
|
203
|
-
if (SENSITIVE_KEY.test(key)) {
|
|
204
|
-
out[key] = '[redacted]';
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
if (typeof value === 'boolean') {
|
|
208
|
-
out[key] = value;
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
if (Array.isArray(value)) {
|
|
212
|
-
out[key] = value.slice(0, 20).map((v) => cleanDiagnosticText(String(v), ARG_VALUE_MAX));
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
out[key] = cleanDiagnosticText(String(value), ARG_VALUE_MAX);
|
|
216
|
-
}
|
|
217
|
-
return out;
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Captured output → the tail we store.
|
|
221
|
-
*
|
|
222
|
-
* Two transformations before truncating, both about spending the 16KB on
|
|
223
|
-
* things a human would read:
|
|
224
|
-
* - progress lines are the machine protocol — one fat JSON object per phase,
|
|
225
|
-
* marked by a `__progress__` field (progress.ts, and the same shape from the
|
|
226
|
-
* Python skills). Their payload is noise here, but WHICH PHASE a failed run
|
|
227
|
-
* reached is the single most useful fact about it, so the line collapses to
|
|
228
|
-
* its phase name instead of being dropped.
|
|
229
|
-
* - `__diagnostic_v1__` lines are our own structured log of this very run;
|
|
230
|
-
* keeping them would store the same verdict twice.
|
|
231
|
-
*
|
|
232
|
-
* Then keeps the TAIL, unlike ab-agent's head-first cleanText: a command's
|
|
233
|
-
* verdict — the error, or the URL it produced — is its last line.
|
|
234
|
-
*/
|
|
235
|
-
export function condense(raw) {
|
|
236
|
-
if (!raw)
|
|
237
|
-
return '';
|
|
238
|
-
const compacted = raw
|
|
239
|
-
.split('\n')
|
|
240
|
-
.map((line) => {
|
|
241
|
-
const trimmed = line.trim();
|
|
242
|
-
if (trimmed.startsWith('__diagnostic_v1__'))
|
|
243
|
-
return null;
|
|
244
|
-
if (!trimmed.startsWith('{') || !trimmed.includes('"__progress__"'))
|
|
245
|
-
return line;
|
|
246
|
-
try {
|
|
247
|
-
const phase = JSON.parse(trimmed).phase;
|
|
248
|
-
return typeof phase === 'string' ? `· phase=${phase}` : '· progress';
|
|
249
|
-
}
|
|
250
|
-
catch {
|
|
251
|
-
return '· progress';
|
|
252
|
-
}
|
|
253
|
-
})
|
|
254
|
-
.filter((line) => line !== null)
|
|
255
|
-
.join('\n')
|
|
256
|
-
.trim();
|
|
257
|
-
// Slice the tail before cleaning: cleanDiagnosticText truncates head-first.
|
|
258
|
-
const tail = compacted.length > TOOL_RESULT_MAX ? compacted.slice(-TOOL_RESULT_MAX) : compacted;
|
|
259
|
-
return cleanDiagnosticText(tail, TOOL_RESULT_MAX);
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* The one-line verdict.
|
|
263
|
-
*
|
|
264
|
-
* A thrown SkillError carries it directly. A Python child does not throw — it
|
|
265
|
-
* just exits non-zero, so fall back to the last line it printed, which is where
|
|
266
|
-
* argparse errors, tracebacks and our own ❌ messages end up.
|
|
267
|
-
*/
|
|
268
|
-
function summariseError(error, output) {
|
|
269
|
-
if (error instanceof Error && error.message) {
|
|
270
|
-
return cleanDiagnosticText(error.message, ERROR_SUMMARY_MAX);
|
|
271
|
-
}
|
|
272
|
-
const lastLine = output
|
|
273
|
-
.split('\n')
|
|
274
|
-
.map((l) => l.trim())
|
|
275
|
-
.filter((l) => l && !l.startsWith('· phase='))
|
|
276
|
-
.pop();
|
|
277
|
-
return lastLine ? cleanDiagnosticText(lastLine, ERROR_SUMMARY_MAX) : '';
|
|
278
|
-
}
|
|
1
|
+
var k=Object.defineProperty;var n=(e,t)=>k(e,"name",{value:t,configurable:!0});import d from"node:os";import{cleanDiagnosticText as u,outcomeOf as v}from"../diagnostics.js";import{billingSummary as I}from"../billing.js";import{packageVersion as b}from"../install.js";import{mmPost as M}from"../http.js";const l=16*1024,m=64*1024,_=512,g=1024,R=1e4,w=/token|secret|password|api[_-]?key|authorization/i;function T(e=process.env){return(e.REMIXMATE_TRACE??"").trim()!=="0"}n(T,"tracingEnabled");class y{static{n(this,"InvocationTrace")}opts;chunks="";restore=null;flushed=!1;constructor(t){this.opts=t}static begin(t){return!T()||!t.take.conversationId?null:new y(t)}installCapture(){if(this.restore)return;const t=process.stdout.write.bind(process.stdout),r=process.stderr.write.bind(process.stderr),s=n(o=>((c,...i)=>(this.absorb(c),o(c,...i))),"tap");process.stdout.write=s(t),process.stderr.write=s(r),this.restore=()=>{process.stdout.write=t,process.stderr.write=r}}absorb(t){try{const r=typeof t=="string"?t:t instanceof Uint8Array?Buffer.from(t).toString("utf8"):"";if(!r)return;this.chunks+=r,this.chunks.length>m*2&&(this.chunks=this.chunks.slice(-m))}catch{}}async finish(t,r,s){if(this.restore?.(),this.restore=null,this.flushed)return;this.flushed=!0;const{skillName:o,args:c,take:i,ctx:S}=this.opts,f=W(this.chunks),A=v(t),h=t!==0,a=[];let p=0;i.created&&a.push({sessionId:i.sessionKey,stepSeq:p++,kind:"env",env:U(i)}),a.push({sessionId:i.sessionKey,stepSeq:p++,kind:"tool_call",toolName:`cli__${o.replace(/-/g,"_")}`,toolUseId:(process.env.AB_OPERATION_ID??"").slice(0,64),toolInput:x(c),toolResult:f||"[no output captured]",isError:h,durationMs:Math.round(r)}),a.push({sessionId:i.sessionKey,stepSeq:p++,kind:"turn_result",outcome:A,errorSummary:h?$(s,f):"",credits:I()?.credits??0});try{await M(S,`/chat/conversation/${encodeURIComponent(i.conversationId)}/trace`,{appendTurn:!0,traces:a},{timeoutMs:R})}catch(E){process.stderr.write(`⚠️ 执行轨迹未能上报(不影响本次产物): ${u(E.message,256)}
|
|
2
|
+
`)}}}function O(e){return e.startsWith("custom:")?"env-override":e.startsWith("job:")?"render-job":"host"}n(O,"keySource");function U(e){return{channel:"cli",cli_version:b(),host:e.host||"unknown",take_key_source:O(e.sessionKey),node:process.version,platform:`${d.platform()}-${d.arch()}`}}n(U,"envSnapshot");function x(e){const t={};for(const[r,s]of Object.entries(e).slice(0,40)){if(w.test(r)){t[r]="[redacted]";continue}if(typeof s=="boolean"){t[r]=s;continue}if(Array.isArray(s)){t[r]=s.slice(0,20).map(o=>u(String(o),_));continue}t[r]=u(String(s),_)}return t}n(x,"sanitiseArgs");function W(e){if(!e)return"";const t=e.split(`
|
|
3
|
+
`).map(s=>{const o=s.trim();if(o.startsWith("__diagnostic_v1__"))return null;if(!o.startsWith("{")||!o.includes('"__progress__"'))return s;try{const c=JSON.parse(o).phase;return typeof c=="string"?`· phase=${c}`:"· progress"}catch{return"· progress"}}).filter(s=>s!==null).join(`
|
|
4
|
+
`).trim(),r=t.length>l?t.slice(-l):t;return u(r,l)}n(W,"condense");function $(e,t){if(e instanceof Error&&e.message)return u(e.message,g);const r=t.split(`
|
|
5
|
+
`).map(s=>s.trim()).filter(s=>s&&!s.startsWith("· phase=")).pop();return r?u(r,g):""}n($,"summariseError");export{y as InvocationTrace,W as condense,x as sanitiseArgs,T as tracingEnabled};
|
package/dist/registry.js
CHANGED
|
@@ -1,70 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Validation and entry-normalization live in `skill-schema.ts` (shared with
|
|
5
|
-
* the build-time manifest generator and smoke test). A record that fails
|
|
6
|
-
* validation is skipped with a warning rather than crashing the whole CLI.
|
|
7
|
-
*/
|
|
8
|
-
import { readdirSync, readFileSync, existsSync, statSync } from 'node:fs';
|
|
9
|
-
import path from 'node:path';
|
|
10
|
-
import { fileURLToPath } from 'node:url';
|
|
11
|
-
import { normalizeEntry, resolveAuthMode, validateSkillJson, } from './skill-schema.js';
|
|
12
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
export const SKILLS_DIR = path.resolve(__dirname, '..', 'skills');
|
|
14
|
-
function loadOne(skillDir) {
|
|
15
|
-
const skillJsonPath = path.join(skillDir, 'skill.json');
|
|
16
|
-
if (!existsSync(skillJsonPath))
|
|
17
|
-
return null;
|
|
18
|
-
const skillId = path.basename(skillDir);
|
|
19
|
-
let raw;
|
|
20
|
-
try {
|
|
21
|
-
raw = JSON.parse(readFileSync(skillJsonPath, 'utf-8'));
|
|
22
|
-
}
|
|
23
|
-
catch (err) {
|
|
24
|
-
console.error(`[remixmate] failed to parse ${skillJsonPath}:`, err);
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
const errors = validateSkillJson(raw, skillId);
|
|
28
|
-
if (errors.length > 0) {
|
|
29
|
-
console.warn(`[remixmate] skipping ${skillId}:\n${errors.map((e) => ` - ${e}`).join('\n')}`);
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
// validateSkillJson guarantees a resolvable entry.
|
|
33
|
-
const entry = normalizeEntry(raw);
|
|
34
|
-
const def = {
|
|
35
|
-
name: raw.name,
|
|
36
|
-
toolName: raw.toolName,
|
|
37
|
-
description: raw.description,
|
|
38
|
-
parameters: raw.parameters ?? {},
|
|
39
|
-
entry,
|
|
40
|
-
auth: resolveAuthMode(raw),
|
|
41
|
-
createsTake: raw.createsTake === true,
|
|
42
|
-
joinsTake: raw.joinsTake === true,
|
|
43
|
-
createsTakeUnless: Array.isArray(raw.createsTakeUnless) ? raw.createsTakeUnless : [],
|
|
44
|
-
skillDir,
|
|
45
|
-
};
|
|
46
|
-
if (entry.type === 'python') {
|
|
47
|
-
def.scriptAbsolutePath = path.join(skillDir, entry.scriptPath);
|
|
48
|
-
}
|
|
49
|
-
return def;
|
|
50
|
-
}
|
|
51
|
-
export function loadSkills(baseDir = SKILLS_DIR) {
|
|
52
|
-
if (!existsSync(baseDir)) {
|
|
53
|
-
console.warn(`[remixmate] skills directory not found: ${baseDir}`);
|
|
54
|
-
return [];
|
|
55
|
-
}
|
|
56
|
-
const skills = [];
|
|
57
|
-
for (const entry of readdirSync(baseDir)) {
|
|
58
|
-
const skillDir = path.join(baseDir, entry);
|
|
59
|
-
if (!statSync(skillDir).isDirectory())
|
|
60
|
-
continue;
|
|
61
|
-
const def = loadOne(skillDir);
|
|
62
|
-
if (def)
|
|
63
|
-
skills.push(def);
|
|
64
|
-
}
|
|
65
|
-
skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
66
|
-
return skills;
|
|
67
|
-
}
|
|
68
|
-
export function findSkill(name, baseDir) {
|
|
69
|
-
return loadSkills(baseDir).find((s) => s.name === name) ?? null;
|
|
70
|
-
}
|
|
1
|
+
var p=Object.defineProperty;var a=(r,t)=>p(r,"name",{value:t,configurable:!0});import{readdirSync as u,readFileSync as f,existsSync as m,statSync as d}from"node:fs";import o from"node:path";import{fileURLToPath as k}from"node:url";import{normalizeEntry as y,resolveAuthMode as h,validateSkillJson as S}from"./skill-schema.js";const T=o.dirname(k(import.meta.url)),j=o.resolve(T,"..","skills");function x(r){const t=o.join(r,"skill.json");if(!m(t))return null;const n=o.basename(r);let e;try{e=JSON.parse(f(t,"utf-8"))}catch(l){return console.error(`[remixmate] failed to parse ${t}:`,l),null}const s=S(e,n);if(s.length>0)return console.warn(`[remixmate] skipping ${n}:
|
|
2
|
+
${s.map(l=>` - ${l}`).join(`
|
|
3
|
+
`)}`),null;const i=y(e),c={name:e.name,toolName:e.toolName,description:e.description,parameters:e.parameters??{},entry:i,auth:h(e),createsTake:e.createsTake===!0,joinsTake:e.joinsTake===!0,createsTakeUnless:Array.isArray(e.createsTakeUnless)?e.createsTakeUnless:[],skillDir:r};return i.type==="python"&&(c.scriptAbsolutePath=o.join(r,i.scriptPath)),c}a(x,"loadOne");function $(r=j){if(!m(r))return console.warn(`[remixmate] skills directory not found: ${r}`),[];const t=[];for(const n of u(r)){const e=o.join(r,n);if(!d(e).isDirectory())continue;const s=x(e);s&&t.push(s)}return t.sort((n,e)=>n.name.localeCompare(e.name)),t}a($,"loadSkills");function I(r,t){return $(t).find(n=>n.name===r)??null}a(I,"findSkill");export{j as SKILLS_DIR,I as findSkill,$ as loadSkills};
|
package/dist/runner.js
CHANGED
|
@@ -1,225 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* and never read the credential store themselves.
|
|
9
|
-
*
|
|
10
|
-
* Resolves to the child process's exit code so cli.ts can propagate it.
|
|
11
|
-
*/
|
|
12
|
-
import { diagnostic, cleanDiagnosticText, traceHeaders } from './diagnostics.js';
|
|
13
|
-
import { spawn } from 'node:child_process';
|
|
14
|
-
import path from 'node:path';
|
|
15
|
-
import { findSkill, SKILLS_DIR } from './registry.js';
|
|
16
|
-
import { HANDLERS } from './handlers/index.js';
|
|
17
|
-
import { EXIT, SkillError } from './errors.js';
|
|
18
|
-
import { authChildEnv, ensureAuth } from './auth/ensure.js';
|
|
19
|
-
import { emitBillingFooter } from './billing.js';
|
|
20
|
-
import { resolveHttpContext } from './http.js';
|
|
21
|
-
import { ensureTake, findTake } from './project/take.js';
|
|
22
|
-
import { InvocationTrace } from './project/trace.js';
|
|
23
|
-
/** Read the token override accepted by both the TS handlers and the Python skills. */
|
|
24
|
-
function tokenFlag(args) {
|
|
25
|
-
const value = args.token ?? args.priv_token;
|
|
26
|
-
return typeof value === 'string' ? value : undefined;
|
|
27
|
-
}
|
|
28
|
-
export async function runSkill(skillName, opts) {
|
|
29
|
-
const started = performance.now();
|
|
30
|
-
let exitCode = EXIT.ERROR;
|
|
31
|
-
let failure;
|
|
32
|
-
let trace = null;
|
|
33
|
-
traceHeaders();
|
|
34
|
-
const skill = findSkill(skillName, opts.baseDir ?? SKILLS_DIR);
|
|
35
|
-
if (!skill) {
|
|
36
|
-
process.stderr.write(`❌ skill not found: ${skillName}\n`);
|
|
37
|
-
diagnostic(EXIT.USAGE, performance.now() - started);
|
|
38
|
-
return EXIT.USAGE;
|
|
39
|
-
}
|
|
40
|
-
try {
|
|
41
|
-
const auth = await ensureAuth({
|
|
42
|
-
mode: skill.auth,
|
|
43
|
-
apiBaseUrl: typeof opts.parsedArgs.api_base_url === 'string' ? opts.parsedArgs.api_base_url : undefined,
|
|
44
|
-
flagToken: tokenFlag(opts.parsedArgs),
|
|
45
|
-
});
|
|
46
|
-
const attached = await applyTakeContext(skill, opts, auth);
|
|
47
|
-
if (attached) {
|
|
48
|
-
// Only a take THIS process resolved gets reported — see trace.ts for why
|
|
49
|
-
// the cloud path and nested pipeline children must stay silent.
|
|
50
|
-
trace = InvocationTrace.begin({
|
|
51
|
-
skillName: skill.name,
|
|
52
|
-
args: opts.parsedArgs,
|
|
53
|
-
take: attached.take,
|
|
54
|
-
ctx: attached.ctx,
|
|
55
|
-
});
|
|
56
|
-
trace?.installCapture();
|
|
57
|
-
}
|
|
58
|
-
switch (skill.entry.type) {
|
|
59
|
-
case 'python':
|
|
60
|
-
// Python children talk to ab-api themselves and print their own billing
|
|
61
|
-
// footer; nothing was charged through this process.
|
|
62
|
-
exitCode = await runPython(skill, opts.rawArgs, auth, trace);
|
|
63
|
-
break;
|
|
64
|
-
case 'http':
|
|
65
|
-
case 'builtin':
|
|
66
|
-
exitCode = await runHandler(skill, opts.parsedArgs, auth);
|
|
67
|
-
break;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
failure = err;
|
|
72
|
-
if (err instanceof SkillError) {
|
|
73
|
-
process.stderr.write(cleanDiagnosticText(err.message) + '\n');
|
|
74
|
-
exitCode = err.exitCode;
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
process.stderr.write(`❌ unexpected error: ${cleanDiagnosticText(err.stack ?? err.message)}\n`);
|
|
78
|
-
exitCode = EXIT.ERROR;
|
|
79
|
-
}
|
|
80
|
-
// Single exit point below: the trace flush in `finally` must run before the
|
|
81
|
-
// value leaves, and an `await` there defeats TS's reachability analysis on
|
|
82
|
-
// returns made from inside the try/catch.
|
|
83
|
-
}
|
|
84
|
-
finally {
|
|
85
|
-
// In `finally` because a run can fail *after* a billed step (e.g. the image
|
|
86
|
-
// generated and was charged, then the upload timed out) — spend gets
|
|
87
|
-
// reported either way. No-ops when nothing was charged.
|
|
88
|
-
diagnostic(exitCode, performance.now() - started, failure);
|
|
89
|
-
emitBillingFooter();
|
|
90
|
-
// Last, so the billing footer is still inside the captured tail. Never
|
|
91
|
-
// throws and never changes the exit code — a lost trace row must not cost
|
|
92
|
-
// the user a render they already paid for.
|
|
93
|
-
await trace?.finish(exitCode, performance.now() - started, failure);
|
|
94
|
-
}
|
|
95
|
-
return exitCode;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Decide the take mode from the skill's declaration *and* what this particular
|
|
99
|
-
* invocation was asked to do.
|
|
100
|
-
*
|
|
101
|
-
* The manifest flags alone are not enough, because they describe the skill, not
|
|
102
|
-
* the call: `render-video --help` prints usage and exits, and
|
|
103
|
-
* `render-video --resolve-only` stops after asset resolution. Both used to run
|
|
104
|
-
* the full createsTake path, so a help query was enough to mint an empty take
|
|
105
|
-
* and lazily create 「未分类」 — the exact clutter project/take.ts sets out to
|
|
106
|
-
* avoid ("an empty VideoProject is worse clutter than an unattributed image").
|
|
107
|
-
*
|
|
108
|
-
* Note that a downgraded run still *joins*: when its own pipeline already
|
|
109
|
-
* started a take under the same grouping key, assets it generates stay attached.
|
|
110
|
-
*/
|
|
111
|
-
export function resolveTakeMode(skill, args) {
|
|
112
|
-
// Introspection — the skill prints its usage and exits without producing
|
|
113
|
-
// anything, so it must not touch the backend at all.
|
|
114
|
-
if (args.help === true || args.h === true)
|
|
115
|
-
return 'none';
|
|
116
|
-
if (skill.createsTake) {
|
|
117
|
-
const downgraded = (skill.createsTakeUnless ?? []).some((key) => args[key] === true);
|
|
118
|
-
return downgraded ? 'join' : 'create';
|
|
119
|
-
}
|
|
120
|
-
return skill.joinsTake ? 'join' : 'none';
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Attach this invocation to a take, publishing the result as `CONVERSATION_ID`.
|
|
124
|
-
* That variable is the only thing that makes an uploaded asset reachable from
|
|
125
|
-
* the web app — it rides the upload as `x-conversation-id` and lands in
|
|
126
|
-
* `file.conversation_id`.
|
|
127
|
-
*
|
|
128
|
-
* Writes `process.env` rather than returning a child-env patch, because the two
|
|
129
|
-
* dispatch paths consume it differently: spawned Python inherits the process
|
|
130
|
-
* environment, while http/builtin handlers run in-process and read it via
|
|
131
|
-
* resolveHttpContext(). Four of the seven take-joining skills are in-process
|
|
132
|
-
* handlers, so a child-env-only patch would silently do nothing for them.
|
|
133
|
-
* Safe here because the CLI runs exactly one skill per process.
|
|
134
|
-
*
|
|
135
|
-
* Two deliberate non-behaviours:
|
|
136
|
-
* - An already-set CONVERSATION_ID is left alone. The cloud agent injects its
|
|
137
|
-
* own, and so does our own parent when prepare-video-assets shells out to
|
|
138
|
-
* gen-image / gen-voice. Re-deriving would fork the conversation.
|
|
139
|
-
* - Any failure is downgraded to a warning. Attribution is a convenience;
|
|
140
|
-
* losing it must never cost the user a render they already paid for. The
|
|
141
|
-
* asset still uploads, just unattributed — exactly the old behaviour.
|
|
142
|
-
*/
|
|
143
|
-
async function applyTakeContext(skill, opts, auth) {
|
|
144
|
-
const mode = resolveTakeMode(skill, opts.parsedArgs);
|
|
145
|
-
if (mode === 'none')
|
|
146
|
-
return null;
|
|
147
|
-
if ((process.env.CONVERSATION_ID ?? '').trim())
|
|
148
|
-
return null;
|
|
149
|
-
if (!auth.token)
|
|
150
|
-
return null;
|
|
151
|
-
try {
|
|
152
|
-
const ctx = await resolveHttpContext(skill.name, {
|
|
153
|
-
apiBaseUrl: auth.apiBaseUrl,
|
|
154
|
-
preflightToken: auth.token,
|
|
155
|
-
});
|
|
156
|
-
let take;
|
|
157
|
-
if (mode === 'create') {
|
|
158
|
-
const jobIdArg = opts.parsedArgs.job_id;
|
|
159
|
-
const jobId = typeof jobIdArg === 'number' ? jobIdArg : Number(jobIdArg);
|
|
160
|
-
const flagProject = opts.parsedArgs.project;
|
|
161
|
-
take = await ensureTake(ctx, {
|
|
162
|
-
jobId: Number.isFinite(jobId) ? jobId : undefined,
|
|
163
|
-
flagProjectId: typeof flagProject === 'string' ? flagProject : undefined,
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
take = await findTake(ctx);
|
|
168
|
-
}
|
|
169
|
-
if (!take)
|
|
170
|
-
return null;
|
|
171
|
-
process.env.CONVERSATION_ID = take.conversationId;
|
|
172
|
-
process.stdout.write(`📁 take ${take.created ? 'created' : 'reused'}: ${take.videoProjectId} ` +
|
|
173
|
-
`(project ${take.projectId}${take.attempt ? `, attempt ${take.attempt}` : ''})\n`);
|
|
174
|
-
return { take, ctx };
|
|
175
|
-
}
|
|
176
|
-
catch (err) {
|
|
177
|
-
process.stderr.write(`⚠️ 无法归属本次产物(内容仍会正常生成,但不会出现在项目里): ${cleanDiagnosticText(err.message)}\n`);
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Spawn the Python skill.
|
|
183
|
-
*
|
|
184
|
-
* `stdio: 'inherit'` is the default and stays the default: the child writes
|
|
185
|
-
* straight to the terminal, which is exactly what a CLI should do. It does mean
|
|
186
|
-
* the parent sees nothing, so a failed render can only ever be reported as
|
|
187
|
-
* "exit code 1" — for the most valuable step in the pipeline.
|
|
188
|
-
*
|
|
189
|
-
* So when (and only when) we are tracing, stdout/stderr are piped and re-emitted
|
|
190
|
-
* verbatim, which routes them through the capture tap. stdin stays inherited so
|
|
191
|
-
* interactive prompts still work. Resolving on `close` rather than `exit` in
|
|
192
|
-
* that mode matters: `exit` can fire while the last chunk is still in the pipe,
|
|
193
|
-
* which is precisely the chunk holding the error message.
|
|
194
|
-
*/
|
|
195
|
-
async function runPython(skill, rawArgs, auth, trace) {
|
|
196
|
-
if (!skill.scriptAbsolutePath) {
|
|
197
|
-
throw new SkillError(`skill ${skill.name} has entry.type=python but no scriptAbsolutePath`);
|
|
198
|
-
}
|
|
199
|
-
const cwd = path.dirname(skill.scriptAbsolutePath);
|
|
200
|
-
return await new Promise((resolve) => {
|
|
201
|
-
const proc = spawn('python3', [skill.scriptAbsolutePath, ...rawArgs], {
|
|
202
|
-
cwd,
|
|
203
|
-
stdio: trace ? ['inherit', 'pipe', 'pipe'] : 'inherit',
|
|
204
|
-
env: { ...process.env, ...authChildEnv(auth), PYTHONUNBUFFERED: '1' },
|
|
205
|
-
});
|
|
206
|
-
if (trace) {
|
|
207
|
-
proc.stdout?.on('data', (chunk) => process.stdout.write(chunk));
|
|
208
|
-
proc.stderr?.on('data', (chunk) => process.stderr.write(chunk));
|
|
209
|
-
}
|
|
210
|
-
proc.on(trace ? 'close' : 'exit', (code) => resolve(code ?? EXIT.ERROR));
|
|
211
|
-
proc.on('error', (err) => {
|
|
212
|
-
process.stderr.write(`❌ failed to spawn python3: ${err.message}\n`);
|
|
213
|
-
resolve(EXIT.SPAWN_FAILED);
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
async function runHandler(skill, args, auth) {
|
|
218
|
-
const handlerKey = skill.entry.handler;
|
|
219
|
-
const handler = HANDLERS[handlerKey];
|
|
220
|
-
if (!handler) {
|
|
221
|
-
throw new SkillError(`handler not registered: ${handlerKey} (skill ${skill.name})`);
|
|
222
|
-
}
|
|
223
|
-
await handler(args, { skillName: skill.name, auth });
|
|
224
|
-
return EXIT.OK;
|
|
225
|
-
}
|
|
1
|
+
var m=Object.defineProperty;var c=(r,e)=>m(r,"name",{value:e,configurable:!0});import{diagnostic as l,cleanDiagnosticText as u,traceHeaders as w}from"./diagnostics.js";import{spawn as h}from"node:child_process";import g from"node:path";import{findSkill as y,SKILLS_DIR as A}from"./registry.js";import{HANDLERS as k}from"./handlers/index.js";import{EXIT as d,SkillError as f}from"./errors.js";import{authChildEnv as b,ensureAuth as I}from"./auth/ensure.js";import{emitBillingFooter as T}from"./billing.js";import{resolveHttpContext as E}from"./http.js";import{ensureTake as R,findTake as j}from"./project/take.js";import{InvocationTrace as x}from"./project/trace.js";function $(r){const e=r.token??r.priv_token;return typeof e=="string"?e:void 0}c($,"tokenFlag");async function M(r,e){const s=performance.now();let n=d.ERROR,i,o=null;w();const a=y(r,e.baseDir??A);if(!a)return process.stderr.write(`❌ skill not found: ${r}
|
|
2
|
+
`),l(d.USAGE,performance.now()-s),d.USAGE;try{const t=await I({mode:a.auth,apiBaseUrl:typeof e.parsedArgs.api_base_url=="string"?e.parsedArgs.api_base_url:void 0,flagToken:$(e.parsedArgs)}),p=await N(a,e,t);switch(p&&(o=x.begin({skillName:a.name,args:e.parsedArgs,take:p.take,ctx:p.ctx}),o?.installCapture()),a.entry.type){case"python":n=await P(a,e.rawArgs,t,o);break;case"http":case"builtin":n=await S(a,e.parsedArgs,t);break}}catch(t){i=t,t instanceof f?(process.stderr.write(u(t.message)+`
|
|
3
|
+
`),n=t.exitCode):(process.stderr.write(`❌ unexpected error: ${u(t.stack??t.message)}
|
|
4
|
+
`),n=d.ERROR)}finally{l(n,performance.now()-s,i),T(),await o?.finish(n,performance.now()-s,i)}return n}c(M,"runSkill");function v(r,e){return e.help===!0||e.h===!0?"none":r.createsTake?(r.createsTakeUnless??[]).some(n=>e[n]===!0)?"join":"create":r.joinsTake?"join":"none"}c(v,"resolveTakeMode");async function N(r,e,s){const n=v(r,e.parsedArgs);if(n==="none"||(process.env.CONVERSATION_ID??"").trim()||!s.token)return null;try{const i=await E(r.name,{apiBaseUrl:s.apiBaseUrl,preflightToken:s.token});let o;if(n==="create"){const a=e.parsedArgs.job_id,t=typeof a=="number"?a:Number(a),p=e.parsedArgs.project;o=await R(i,{jobId:Number.isFinite(t)?t:void 0,flagProjectId:typeof p=="string"?p:void 0})}else o=await j(i);return o?(process.env.CONVERSATION_ID=o.conversationId,process.stdout.write(`📁 take ${o.created?"created":"reused"}: ${o.videoProjectId} (project ${o.projectId}${o.attempt?`, attempt ${o.attempt}`:""})
|
|
5
|
+
`),{take:o,ctx:i}):null}catch(i){return process.stderr.write(`⚠️ 无法归属本次产物(内容仍会正常生成,但不会出现在项目里): ${u(i.message)}
|
|
6
|
+
`),null}}c(N,"applyTakeContext");async function P(r,e,s,n){if(!r.scriptAbsolutePath)throw new f(`skill ${r.name} has entry.type=python but no scriptAbsolutePath`);const i=g.dirname(r.scriptAbsolutePath);return await new Promise(o=>{const a=h("python3",[r.scriptAbsolutePath,...e],{cwd:i,stdio:n?["inherit","pipe","pipe"]:"inherit",env:{...process.env,...b(s),PYTHONUNBUFFERED:"1"}});n&&(a.stdout?.on("data",t=>process.stdout.write(t)),a.stderr?.on("data",t=>process.stderr.write(t))),a.on(n?"close":"exit",t=>o(t??d.ERROR)),a.on("error",t=>{process.stderr.write(`❌ failed to spawn python3: ${t.message}
|
|
7
|
+
`),o(d.SPAWN_FAILED)})})}c(P,"runPython");async function S(r,e,s){const n=r.entry.handler,i=k[n];if(!i)throw new f(`handler not registered: ${n} (skill ${r.name})`);return await i(e,{skillName:r.name,auth:s}),d.OK}c(S,"runHandler");export{v as resolveTakeMode,M as runSkill};
|