@remixmate/cli 0.9.72 → 0.9.74
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 -192
- 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 +44 -3
- 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-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 -22
- 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/cli.js
CHANGED
|
@@ -1,212 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
function describeEntryTail(s) {
|
|
43
|
-
if (s.entry.type === 'python') {
|
|
44
|
-
// scriptPath 可以指向别的 skill 目录(如 web-record → ../web-screenshot/…),
|
|
45
|
-
// 这种相对路径原样打印,只给同目录脚本补上 scripts/ 前缀。
|
|
46
|
-
const p = s.entry.scriptPath;
|
|
47
|
-
return `(${p.startsWith('.') ? p : `scripts/${p.replace(/^scripts\//, '')}`})`;
|
|
48
|
-
}
|
|
49
|
-
return `[${s.entry.handler}]`;
|
|
50
|
-
}
|
|
51
|
-
function printUsage() {
|
|
52
|
-
// Match runSkill: honor SKILL_BASE_DIR so `remixmate --help` and the
|
|
53
|
-
// subsequent `remixmate <name>` invocation list / run from the same tree.
|
|
54
|
-
const skills = loadSkills(resolveBaseDir());
|
|
55
|
-
process.stdout.write('Usage:\n');
|
|
56
|
-
process.stdout.write(' remixmate --list List all skills\n');
|
|
57
|
-
process.stdout.write(' remixmate install [--host <h>] Install the skills into Claude Code / Codex\n');
|
|
58
|
-
process.stdout.write(' remixmate uninstall Remove skills installed by this CLI\n');
|
|
59
|
-
process.stdout.write(' remixmate doctor Diagnose environment, credentials, backend\n');
|
|
60
|
-
process.stdout.write(' remixmate login Authorize this CLI via the browser\n');
|
|
61
|
-
process.stdout.write(' remixmate login --start | --wait Non-blocking authorization (for agent hosts)\n');
|
|
62
|
-
process.stdout.write(' remixmate logout Remove stored credentials\n');
|
|
63
|
-
process.stdout.write(' remixmate whoami Show the current identity\n');
|
|
64
|
-
process.stdout.write(' remixmate exec -- <cmd> [args...] Run a command with credentials injected\n');
|
|
65
|
-
process.stdout.write(' remixmate project list List your projects\n');
|
|
66
|
-
process.stdout.write(' remixmate project use <prj> [--global] Bind output to a project (this dir, or globally)\n');
|
|
67
|
-
process.stdout.write(' remixmate project show Show the current project binding\n');
|
|
68
|
-
process.stdout.write(' remixmate host probe Diagnose host detection and take grouping\n');
|
|
69
|
-
process.stdout.write(' remixmate template init <dir> Create a video-template project (no login)\n');
|
|
70
|
-
process.stdout.write(' remixmate template check [name] Validate templates locally (no login)\n');
|
|
71
|
-
process.stdout.write(' remixmate template preview <name> Render a draft preview mp4 remotely\n');
|
|
72
|
-
process.stdout.write(' remixmate template publish <name> Publish as your private template\n');
|
|
73
|
-
process.stdout.write(' remixmate template list List your private templates\n');
|
|
74
|
-
process.stdout.write(' remixmate template upgrade Update the project\'s local check rules (no login)\n');
|
|
75
|
-
process.stdout.write(' remixmate <name> [--flag value ...] Invoke a skill\n');
|
|
76
|
-
process.stdout.write(' remixmate <name> --help Show skill-specific help\n');
|
|
77
|
-
process.stdout.write(' remixmate --version Print the CLI version\n');
|
|
78
|
-
process.stdout.write('\nAvailable skills:\n');
|
|
79
|
-
for (const s of skills) {
|
|
80
|
-
const summary = s.description.split('\n')[0].slice(0, 60);
|
|
81
|
-
process.stdout.write(` ${s.name.padEnd(22)} ${s.entry.type.padEnd(8)} ${summary}\n`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
/** Human-facing note on what a skill's auth mode means in practice. */
|
|
85
|
-
const AUTH_NOTE = {
|
|
86
|
-
required: '需登录',
|
|
87
|
-
optional: '可选',
|
|
88
|
-
none: '免登录',
|
|
89
|
-
};
|
|
90
|
-
/**
|
|
91
|
-
* List skills.
|
|
92
|
-
*
|
|
93
|
-
* The default output is for a person: what each skill is and whether it needs a
|
|
94
|
-
* credential. The previous tab-separated form leaked internals (toolName, entry
|
|
95
|
-
* type, script path) that only tooling cares about — that shape now lives behind
|
|
96
|
-
* `--json`, which is a stabler contract for tooling anyway.
|
|
97
|
-
*/
|
|
98
|
-
function printList(json) {
|
|
99
|
-
const skills = loadSkills(resolveBaseDir());
|
|
100
|
-
if (json) {
|
|
101
|
-
process.stdout.write(JSON.stringify(skills.map((s) => ({
|
|
102
|
-
name: s.name,
|
|
103
|
-
toolName: s.toolName,
|
|
104
|
-
runtime: s.entry.type,
|
|
105
|
-
auth: s.auth,
|
|
106
|
-
entry: describeEntryTail(s),
|
|
107
|
-
description: s.description.split('\n')[0],
|
|
108
|
-
}))) + '\n');
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
for (const s of skills) {
|
|
112
|
-
const summary = s.description.split('\n')[0].slice(0, 58);
|
|
113
|
-
process.stdout.write(` ${s.name.padEnd(22)} ${padDisplay(AUTH_NOTE[s.auth], 8)}${summary}\n`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Auth status footer for the listing commands, on stderr so stdout stays clean
|
|
118
|
-
* for anything piping the list. A fresh install used to say nothing at all about
|
|
119
|
-
* credentials until a skill failed.
|
|
120
|
-
*/
|
|
121
|
-
async function printAuthFooter() {
|
|
122
|
-
try {
|
|
123
|
-
process.stderr.write(`\n${await authStatusLine()}\n`);
|
|
124
|
-
// `npm i -g` 之后第一条命令通常就是 --help / --list:这里提醒一次「还没 remixmate install」,
|
|
125
|
-
// 代替会被 npm 吞掉输出、且侵入家目录的 postinstall。
|
|
126
|
-
const hint = skillInstallHint();
|
|
127
|
-
if (hint)
|
|
128
|
-
process.stderr.write(`${hint}\n`);
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
// A diagnostic footer must never be the reason a listing command fails.
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
async function main() {
|
|
135
|
-
const argv = process.argv.slice(2);
|
|
136
|
-
// 只认首位参数:`remixmate <skill> --version` 之类仍原样交给 skill。
|
|
137
|
-
if (argv[0] === '--version' || argv[0] === '-v' || argv[0] === '-V' || argv[0] === 'version') {
|
|
138
|
-
process.stdout.write(`${packageVersion()}\n`);
|
|
139
|
-
process.exit(EXIT.OK);
|
|
140
|
-
}
|
|
141
|
-
if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
|
|
142
|
-
printUsage();
|
|
143
|
-
await printAuthFooter();
|
|
144
|
-
process.exit(EXIT.OK);
|
|
145
|
-
}
|
|
146
|
-
if (argv[0] === '--list' || argv[0] === 'list') {
|
|
147
|
-
const json = argv.includes('--json') || argv.includes('--json-output');
|
|
148
|
-
printList(json);
|
|
149
|
-
if (!json)
|
|
150
|
-
await printAuthFooter();
|
|
151
|
-
process.exit(EXIT.OK);
|
|
152
|
-
}
|
|
153
|
-
// Host-install verbs, like the auth verbs, resolve before skill dispatch so a
|
|
154
|
-
// future skill named `install` could never shadow them.
|
|
155
|
-
if (argv[0] === INSTALL_VERB) {
|
|
156
|
-
process.exit(runInstall(argv.slice(1)));
|
|
157
|
-
}
|
|
158
|
-
if (argv[0] === UNINSTALL_VERB) {
|
|
159
|
-
process.exit(runUninstall(argv.slice(1)));
|
|
160
|
-
}
|
|
161
|
-
if (argv[0] === 'doctor') {
|
|
162
|
-
const code = await runDoctor({ skipNetwork: argv.includes('--offline') });
|
|
163
|
-
process.exit(code);
|
|
164
|
-
}
|
|
165
|
-
// Auth verbs are handled before skill dispatch so they never collide with skill names.
|
|
166
|
-
if (AUTH_VERBS.has(argv[0])) {
|
|
167
|
-
const code = await runAuthCommand(argv[0], argv.slice(1));
|
|
168
|
-
process.exit(code);
|
|
169
|
-
}
|
|
170
|
-
// Project / host verbs, like the auth verbs, are resolved before skill dispatch.
|
|
171
|
-
if (argv[0] === PROJECT_VERB) {
|
|
172
|
-
process.exit(await runProjectCommand(argv.slice(1)));
|
|
173
|
-
}
|
|
174
|
-
if (argv[0] === HOST_VERB) {
|
|
175
|
-
process.exit(await runHostCommand(argv.slice(1)));
|
|
176
|
-
}
|
|
177
|
-
if (argv[0] === TEMPLATE_VERB) {
|
|
178
|
-
const started = performance.now();
|
|
179
|
-
const code = await runTemplateCommand(argv.slice(1));
|
|
180
|
-
diagnostic(code, performance.now() - started);
|
|
181
|
-
process.exit(code);
|
|
182
|
-
}
|
|
183
|
-
if (argv[0] === 'exec') {
|
|
184
|
-
// Raw argv (not parsed) — everything after `--` belongs to the child command.
|
|
185
|
-
const code = await runExec(argv.slice(1));
|
|
186
|
-
process.exit(code);
|
|
187
|
-
}
|
|
188
|
-
if (argv[0].startsWith('-')) {
|
|
189
|
-
process.stderr.write(`❌ unknown option: ${argv[0]}\n`);
|
|
190
|
-
printUsage();
|
|
191
|
-
process.exit(EXIT.USAGE);
|
|
192
|
-
}
|
|
193
|
-
const skillName = argv[0];
|
|
194
|
-
const rest = argv.slice(1);
|
|
195
|
-
const code = await runSkill(skillName, {
|
|
196
|
-
baseDir: resolveBaseDir(),
|
|
197
|
-
rawArgs: rest,
|
|
198
|
-
parsedArgs: parseArgv(rest),
|
|
199
|
-
});
|
|
200
|
-
process.exit(code);
|
|
201
|
-
}
|
|
202
|
-
main().catch((err) => {
|
|
203
|
-
// A SkillError is an expected, already-worded failure (most often "not
|
|
204
|
-
// authenticated", exit 4) — print the message and honor its exit code rather
|
|
205
|
-
// than dumping a stack trace the user can do nothing with.
|
|
206
|
-
if (err instanceof SkillError) {
|
|
207
|
-
process.stderr.write(err.message + '\n');
|
|
208
|
-
process.exit(err.exitCode);
|
|
209
|
-
}
|
|
210
|
-
process.stderr.write(`❌ fatal: ${err?.stack ?? err?.message ?? String(err)}\n`);
|
|
211
|
-
process.exit(EXIT.ERROR);
|
|
212
|
-
});
|
|
2
|
+
var u=Object.defineProperty;var o=(t,s)=>u(t,"name",{value:s,configurable:!0});import p from"node:path";import{loadSkills as l,SKILLS_DIR as w}from"./registry.js";import{parseArgv as f}from"./argv.js";import{runSkill as h}from"./runner.js";import{runAuthCommand as x}from"./auth/commands.js";import{HOST_VERB as g,PROJECT_VERB as y,runHostCommand as k,runProjectCommand as v}from"./project/commands.js";import{INSTALL_VERB as E,UNINSTALL_VERB as S,runInstall as L,runUninstall as R,skillInstallHint as b,packageVersion as A}from"./install.js";import{runExec as j}from"./exec.js";import{authStatusLine as I,runDoctor as $}from"./doctor.js";import{padDisplay as C}from"./text.js";import{EXIT as i,SkillError as T}from"./errors.js";import{TEMPLATE_VERB as N,runTemplateCommand as _}from"./template/commands.js";import{diagnostic as B}from"./diagnostics.js";const V=new Set(["login","logout","whoami"]);function a(){const t=process.env.SKILL_BASE_DIR;return t?p.isAbsolute(t)?t:p.resolve(process.cwd(),t):w}o(a,"resolveBaseDir");function D(t){if(t.entry.type==="python"){const s=t.entry.scriptPath;return`(${s.startsWith(".")?s:`scripts/${s.replace(/^scripts\//,"")}`})`}return`[${t.entry.handler}]`}o(D,"describeEntryTail");function m(){const t=l(a());process.stdout.write(`Usage:
|
|
3
|
+
`),process.stdout.write(` remixmate --list List all skills
|
|
4
|
+
`),process.stdout.write(` remixmate install [--host <h>] Install the skills into Claude Code / Codex
|
|
5
|
+
`),process.stdout.write(` remixmate uninstall Remove skills installed by this CLI
|
|
6
|
+
`),process.stdout.write(` remixmate doctor Diagnose environment, credentials, backend
|
|
7
|
+
`),process.stdout.write(` remixmate login Authorize this CLI via the browser
|
|
8
|
+
`),process.stdout.write(` remixmate login --start | --wait Non-blocking authorization (for agent hosts)
|
|
9
|
+
`),process.stdout.write(` remixmate logout Remove stored credentials
|
|
10
|
+
`),process.stdout.write(` remixmate whoami Show the current identity
|
|
11
|
+
`),process.stdout.write(` remixmate exec -- <cmd> [args...] Run a command with credentials injected
|
|
12
|
+
`),process.stdout.write(` remixmate project list List your projects
|
|
13
|
+
`),process.stdout.write(` remixmate project use <prj> [--global] Bind output to a project (this dir, or globally)
|
|
14
|
+
`),process.stdout.write(` remixmate project show Show the current project binding
|
|
15
|
+
`),process.stdout.write(` remixmate host probe Diagnose host detection and take grouping
|
|
16
|
+
`),process.stdout.write(` remixmate template init <dir> Create a video-template project (no login)
|
|
17
|
+
`),process.stdout.write(` remixmate template check [name] Validate templates locally (no login)
|
|
18
|
+
`),process.stdout.write(` remixmate template preview <name> Render a draft preview mp4 remotely
|
|
19
|
+
`),process.stdout.write(` remixmate template publish <name> Publish as your private template
|
|
20
|
+
`),process.stdout.write(` remixmate template list List your private templates
|
|
21
|
+
`),process.stdout.write(` remixmate template upgrade Update the project's local check rules (no login)
|
|
22
|
+
`),process.stdout.write(` remixmate <name> [--flag value ...] Invoke a skill
|
|
23
|
+
`),process.stdout.write(` remixmate <name> --help Show skill-specific help
|
|
24
|
+
`),process.stdout.write(` remixmate --version Print the CLI version
|
|
25
|
+
`),process.stdout.write(`
|
|
26
|
+
Available skills:
|
|
27
|
+
`);for(const s of t){const e=s.description.split(`
|
|
28
|
+
`)[0].slice(0,60);process.stdout.write(` ${s.name.padEnd(22)} ${s.entry.type.padEnd(8)} ${e}
|
|
29
|
+
`)}}o(m,"printUsage");const O={required:"需登录",optional:"可选",none:"免登录"};function U(t){const s=l(a());if(t){process.stdout.write(JSON.stringify(s.map(e=>({name:e.name,toolName:e.toolName,runtime:e.entry.type,auth:e.auth,entry:D(e),description:e.description.split(`
|
|
30
|
+
`)[0]})))+`
|
|
31
|
+
`);return}for(const e of s){const n=e.description.split(`
|
|
32
|
+
`)[0].slice(0,58);process.stdout.write(` ${e.name.padEnd(22)} ${C(O[e.auth],8)}${n}
|
|
33
|
+
`)}}o(U,"printList");async function d(){try{process.stderr.write(`
|
|
34
|
+
${await I()}
|
|
35
|
+
`);const t=b();t&&process.stderr.write(`${t}
|
|
36
|
+
`)}catch{}}o(d,"printAuthFooter");async function P(){const t=process.argv.slice(2);if((t[0]==="--version"||t[0]==="-v"||t[0]==="-V"||t[0]==="version")&&(process.stdout.write(`${A()}
|
|
37
|
+
`),process.exit(i.OK)),(t.length===0||t[0]==="--help"||t[0]==="-h")&&(m(),await d(),process.exit(i.OK)),t[0]==="--list"||t[0]==="list"){const r=t.includes("--json")||t.includes("--json-output");U(r),r||await d(),process.exit(i.OK)}if(t[0]===E&&process.exit(L(t.slice(1))),t[0]===S&&process.exit(R(t.slice(1))),t[0]==="doctor"){const r=await $({skipNetwork:t.includes("--offline")});process.exit(r)}if(V.has(t[0])){const r=await x(t[0],t.slice(1));process.exit(r)}if(t[0]===y&&process.exit(await v(t.slice(1))),t[0]===g&&process.exit(await k(t.slice(1))),t[0]===N){const r=performance.now(),c=await _(t.slice(1));B(c,performance.now()-r),process.exit(c)}if(t[0]==="exec"){const r=await j(t.slice(1));process.exit(r)}t[0].startsWith("-")&&(process.stderr.write(`❌ unknown option: ${t[0]}
|
|
38
|
+
`),m(),process.exit(i.USAGE));const s=t[0],e=t.slice(1),n=await h(s,{baseDir:a(),rawArgs:e,parsedArgs:f(e)});process.exit(n)}o(P,"main"),P().catch(t=>{t instanceof T&&(process.stderr.write(t.message+`
|
|
39
|
+
`),process.exit(t.exitCode)),process.stderr.write(`❌ fatal: ${t?.stack??t?.message??String(t)}
|
|
40
|
+
`),process.exit(i.ERROR)});
|
package/dist/diagnostics.js
CHANGED
|
@@ -1,70 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
const parent = process.env.AB_TRACEPARENT ?? '';
|
|
4
|
-
const match = /^00-([a-f0-9]{32})-([a-f0-9]{16})-[a-f0-9]{2}$/.exec(parent);
|
|
5
|
-
const trace = match && !/^0+$/.test(match[1]) && !/^0+$/.test(match[2]) ? match[1] : randomBytes(16).toString('hex');
|
|
6
|
-
const header = `00-${trace}-${randomBytes(8).toString('hex')}-01`;
|
|
7
|
-
process.env.AB_TRACEPARENT = header;
|
|
8
|
-
process.env.AB_OPERATION_ID ||= randomUUID();
|
|
9
|
-
return { traceparent: header };
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Redact a string for anything that leaves this process (diagnostic lines,
|
|
13
|
-
* execution traces).
|
|
14
|
-
*
|
|
15
|
-
* Kept in lockstep with ab-agent's `lib/telemetry.cleanText` — the two feed the
|
|
16
|
-
* same `media_chat_turn_trace` table, and a table whose rows were sanitized by
|
|
17
|
-
* two different rulesets is a table nobody can reason about. ANSI stripping,
|
|
18
|
-
* e-mail and phone redaction were added here to close that gap.
|
|
19
|
-
*
|
|
20
|
-
* `max` caps the result in BYTES, keeping the head. Callers that need the tail
|
|
21
|
-
* (a command's decisive line is its last one) slice before calling.
|
|
22
|
-
*/
|
|
23
|
-
export function cleanDiagnosticText(value, max = 2048) {
|
|
24
|
-
let text = value
|
|
25
|
-
.replace(/\x1b\[[0-9;]*m/g, '')
|
|
26
|
-
.replace(/https?:\/\/[^\s"'<>]+/g, raw => {
|
|
27
|
-
try {
|
|
28
|
-
const url = new URL(raw);
|
|
29
|
-
return url.origin + url.pathname;
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return '[url]';
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
.replace(/(Bearer\s+|(?:token|secret|password|api[_-]?key)\s*[=:]\s*)[^\s,;}]+/gi, '$1[redacted]')
|
|
36
|
-
.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, '[email]')
|
|
37
|
-
.replace(/\b1[3-9]\d{9}\b/g, '[phone]');
|
|
38
|
-
for (const [key, secret] of Object.entries(process.env)) {
|
|
39
|
-
if (/TOKEN|SECRET|PASSWORD|API_KEY/i.test(key) && secret && secret.length >= 6)
|
|
40
|
-
text = text.split(secret).join('[redacted]');
|
|
41
|
-
}
|
|
42
|
-
return Buffer.from(text).subarray(0, max).toString('utf8');
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Exit code → the outcome vocabulary of the logging contract.
|
|
46
|
-
*
|
|
47
|
-
* Exported because the execution trace (project/trace.ts) reports the same
|
|
48
|
-
* verdict for the same run: two mappings would let a run be `failure` in the
|
|
49
|
-
* diagnostic line and `success` in the database.
|
|
50
|
-
*/
|
|
51
|
-
export function outcomeOf(exitCode) {
|
|
52
|
-
if (exitCode === 0)
|
|
53
|
-
return 'success';
|
|
54
|
-
if (exitCode === 130)
|
|
55
|
-
return 'cancelled';
|
|
56
|
-
return [2, 3, 4].includes(exitCode) ? 'rejected' : 'failure';
|
|
57
|
-
}
|
|
58
|
-
/** Opt-in line framing for hosts; stdout is exclusively the existing skill protocol. */
|
|
59
|
-
export function diagnostic(exitCode, durationMs, error) {
|
|
60
|
-
if (process.env.AB_DIAGNOSTICS !== '1')
|
|
61
|
-
return;
|
|
62
|
-
traceHeaders();
|
|
63
|
-
const outcome = outcomeOf(exitCode);
|
|
64
|
-
const record = { schema_version: 1, timestamp: new Date().toISOString(), service: 'remixmate-cli', environment: process.env.APP_ENV ?? 'dev',
|
|
65
|
-
level: outcome === 'failure' ? 'error' : 'info', event: 'cli.execution.completed', message: 'CLI execution completed', outcome,
|
|
66
|
-
trace_id: process.env.AB_TRACEPARENT?.split('-')[1], operation_id: process.env.AB_OPERATION_ID,
|
|
67
|
-
duration_ms: durationMs, attributes: { exit_code: exitCode },
|
|
68
|
-
error: error instanceof Error ? { type: error.name, message: cleanDiagnosticText(error.message), stack: cleanDiagnosticText(error.stack ?? '') } : undefined };
|
|
69
|
-
process.stderr.write('__diagnostic_v1__ ' + JSON.stringify(record) + '\n');
|
|
70
|
-
}
|
|
1
|
+
var i=Object.defineProperty;var c=(e,t)=>i(e,"name",{value:t,configurable:!0});import{randomBytes as o,randomUUID as p}from"node:crypto";function u(){const e=process.env.AB_TRACEPARENT??"",t=/^00-([a-f0-9]{32})-([a-f0-9]{16})-[a-f0-9]{2}$/.exec(e),n=`00-${t&&!/^0+$/.test(t[1])&&!/^0+$/.test(t[2])?t[1]:o(16).toString("hex")}-${o(8).toString("hex")}-01`;return process.env.AB_TRACEPARENT=n,process.env.AB_OPERATION_ID||=p(),{traceparent:n}}c(u,"traceHeaders");function a(e,t=2048){let r=e.replace(/\x1b\[[0-9;]*m/g,"").replace(/https?:\/\/[^\s"'<>]+/g,n=>{try{const s=new URL(n);return s.origin+s.pathname}catch{return"[url]"}}).replace(/(Bearer\s+|(?:token|secret|password|api[_-]?key)\s*[=:]\s*)[^\s,;}]+/gi,"$1[redacted]").replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi,"[email]").replace(/\b1[3-9]\d{9}\b/g,"[phone]");for(const[n,s]of Object.entries(process.env))/TOKEN|SECRET|PASSWORD|API_KEY/i.test(n)&&s&&s.length>=6&&(r=r.split(s).join("[redacted]"));return Buffer.from(r).subarray(0,t).toString("utf8")}c(a,"cleanDiagnosticText");function f(e){return e===0?"success":e===130?"cancelled":[2,3,4].includes(e)?"rejected":"failure"}c(f,"outcomeOf");function d(e,t,r){if(process.env.AB_DIAGNOSTICS!=="1")return;u();const n=f(e),s={schema_version:1,timestamp:new Date().toISOString(),service:"remixmate-cli",environment:process.env.APP_ENV??"dev",level:n==="failure"?"error":"info",event:"cli.execution.completed",message:"CLI execution completed",outcome:n,trace_id:process.env.AB_TRACEPARENT?.split("-")[1],operation_id:process.env.AB_OPERATION_ID,duration_ms:t,attributes:{exit_code:e},error:r instanceof Error?{type:r.name,message:a(r.message),stack:a(r.stack??"")}:void 0};process.stderr.write("__diagnostic_v1__ "+JSON.stringify(s)+`
|
|
2
|
+
`)}c(d,"diagnostic");export{a as cleanDiagnosticText,d as diagnostic,f as outcomeOf,u as traceHeaders};
|
package/dist/doctor.js
CHANGED
|
@@ -1,188 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
* Also exports the one-line status shown under `--list` / `--help`, so a first
|
|
12
|
-
* run says "you are not logged in" instead of leaving the user to find out by
|
|
13
|
-
* invoking a skill.
|
|
14
|
-
*/
|
|
15
|
-
import { spawnSync } from 'node:child_process';
|
|
16
|
-
import { resolveApiBaseUrl } from './http.js';
|
|
17
|
-
import { EXIT } from './errors.js';
|
|
18
|
-
import { resolvePrivToken } from './auth/resolve.js';
|
|
19
|
-
import { listCredentials } from './auth/credential-store.js';
|
|
20
|
-
import { resolveTakeKey } from './project/host.js';
|
|
21
|
-
import { readBinding } from './project/store.js';
|
|
22
|
-
import { canAutoAuth, isHeadless } from './auth/environment.js';
|
|
23
|
-
import { installStates, skillInstallHint } from './install.js';
|
|
24
|
-
import { padDisplay } from './text.js';
|
|
25
|
-
/**
|
|
26
|
-
* Short auth status suitable for a footer. Never performs network I/O — a
|
|
27
|
-
* listing command must stay instant and work offline.
|
|
28
|
-
*/
|
|
29
|
-
export async function authStatusLine() {
|
|
30
|
-
const apiBaseUrl = resolveApiBaseUrl();
|
|
31
|
-
const resolved = await resolvePrivToken({ apiBaseUrl });
|
|
32
|
-
if (resolved) {
|
|
33
|
-
const who = resolved.userLabel ? `${resolved.userLabel} ` : '';
|
|
34
|
-
return `✅ 已登录 ${who}@ ${apiBaseUrl}(来源:${resolved.source})`;
|
|
35
|
-
}
|
|
36
|
-
const others = await listCredentials();
|
|
37
|
-
if (others.length > 0) {
|
|
38
|
-
return (`⚠️ 当前后端 ${apiBaseUrl} 未登录;` +
|
|
39
|
-
`已有凭证的后端:${others.map((c) => c.apiBaseUrl).join(', ')}`);
|
|
40
|
-
}
|
|
41
|
-
return `⚠️ 未登录(${apiBaseUrl})→ 运行 remixmate login`;
|
|
42
|
-
}
|
|
43
|
-
const ICON = { ok: '✅', warn: '⚠️ ', fail: '❌' };
|
|
44
|
-
/** Probe a command's presence and version line without failing on absence. */
|
|
45
|
-
function probe(cmd, args) {
|
|
46
|
-
try {
|
|
47
|
-
const res = spawnSync(cmd, args, { encoding: 'utf8', timeout: 10_000 });
|
|
48
|
-
if (res.error || res.status !== 0)
|
|
49
|
-
return null;
|
|
50
|
-
return (res.stdout || res.stderr || '').trim().split('\n')[0] || null;
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
async function checkBackend(apiBaseUrl) {
|
|
57
|
-
const controller = new AbortController();
|
|
58
|
-
const timer = setTimeout(() => controller.abort(), 8_000);
|
|
59
|
-
try {
|
|
60
|
-
// The device-code endpoint is public, so reachability can be probed without
|
|
61
|
-
// a credential — and without side effects, since we never redeem the code.
|
|
62
|
-
const resp = await fetch(`${apiBaseUrl}/auth/device/code`, {
|
|
63
|
-
method: 'POST',
|
|
64
|
-
headers: { 'Content-Type': 'application/json' },
|
|
65
|
-
body: JSON.stringify({ clientLabel: 'remixmate-doctor' }),
|
|
66
|
-
signal: controller.signal,
|
|
67
|
-
});
|
|
68
|
-
return {
|
|
69
|
-
name: '后端可达性',
|
|
70
|
-
status: resp.ok ? 'ok' : 'warn',
|
|
71
|
-
detail: `${apiBaseUrl} → HTTP ${resp.status}`,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
catch (err) {
|
|
75
|
-
const reason = err instanceof Error && err.name === 'AbortError' ? '超时' : err.message;
|
|
76
|
-
return { name: '后端可达性', status: 'fail', detail: `${apiBaseUrl} 不可达(${reason})` };
|
|
77
|
-
}
|
|
78
|
-
finally {
|
|
79
|
-
clearTimeout(timer);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
export async function runDoctor(opts = {}) {
|
|
83
|
-
const apiBaseUrl = resolveApiBaseUrl();
|
|
84
|
-
const checks = [];
|
|
85
|
-
const major = Number(process.versions.node.split('.')[0]);
|
|
86
|
-
checks.push({
|
|
87
|
-
name: 'Node',
|
|
88
|
-
status: major >= 18 ? 'ok' : 'fail',
|
|
89
|
-
detail: `v${process.versions.node}${major >= 18 ? '' : '(需要 >= 18)'}`,
|
|
90
|
-
});
|
|
91
|
-
const py = probe('python3', ['--version']);
|
|
92
|
-
checks.push({
|
|
93
|
-
name: 'python3',
|
|
94
|
-
status: py ? 'ok' : 'fail',
|
|
95
|
-
detail: py ?? '未找到 —— 12 个 skill 中的 8 个需要 python3 >= 3.10',
|
|
96
|
-
});
|
|
97
|
-
const playwright = py ? probe('python3', ['-c', 'import playwright; print(playwright.__version__)']) : null;
|
|
98
|
-
checks.push({
|
|
99
|
-
name: 'Playwright',
|
|
100
|
-
status: playwright ? 'ok' : 'warn',
|
|
101
|
-
detail: playwright ?? '当前解释器下未安装 —— 仅 web-screenshot / web-record 需要,首次运行会自动装',
|
|
102
|
-
});
|
|
103
|
-
checks.push({ name: '后端地址', status: 'ok', detail: apiBaseUrl });
|
|
104
|
-
// 宿主安装状态。这套方案唯一的静默失败模式是漂移:`npm update -g` 只动包,不动
|
|
105
|
-
// 已经铺进 ~/.claude/skills、~/.codex/skills 的副本,用户会拿着旧 SKILL.md 打新
|
|
106
|
-
// 后端,而且第一个症状往往是某个参数「莫名其妙不认」。
|
|
107
|
-
const hostStates = installStates();
|
|
108
|
-
for (const s of hostStates) {
|
|
109
|
-
checks.push({
|
|
110
|
-
// 只取宿主名的第一段(Claude Code → Claude)——检查名要留在 padDisplay 的
|
|
111
|
-
// 对齐宽度内,宿主目录已经写在 detail 里,不会有歧义。
|
|
112
|
-
name: `${s.label.split(' ')[0]} skill`,
|
|
113
|
-
status: s.installedVersion == null ? 'warn' : s.drifted ? 'warn' : 'ok',
|
|
114
|
-
detail: s.installedVersion == null
|
|
115
|
-
? `未安装 —— 该 agent 不会自动发现这些 skill(remixmate install 可安装到 ${s.skillsDir})`
|
|
116
|
-
: s.drifted
|
|
117
|
-
? `已装 ${s.installedVersion},当前包 ${s.currentVersion} → 运行 remixmate install 覆盖`
|
|
118
|
-
: `${s.installedVersion}(${s.skillsDir})`,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
// Where will this machine's renders land, and will repeated renders group?
|
|
122
|
-
// Both were previously invisible — the first sign of trouble was a finished
|
|
123
|
-
// video that appeared nowhere in the web app.
|
|
124
|
-
const binding = await readBinding(apiBaseUrl);
|
|
125
|
-
checks.push({
|
|
126
|
-
name: '项目归属',
|
|
127
|
-
status: 'ok',
|
|
128
|
-
detail: binding
|
|
129
|
-
? `${binding.projectId}(${binding.scope === 'path' ? '目录绑定' : '全局绑定'})`
|
|
130
|
-
: '未绑定 → 产物落进默认项目「未分类」(remixmate project use <prj> 可改)',
|
|
131
|
-
});
|
|
132
|
-
const take = resolveTakeKey(undefined);
|
|
133
|
-
checks.push({
|
|
134
|
-
name: 'take 分组',
|
|
135
|
-
status: take.source === 'none' ? 'warn' : 'ok',
|
|
136
|
-
detail: take.source === 'none'
|
|
137
|
-
? '宿主未提供会话 ID —— 每次渲染各自成为一个 take。可 export REMIXMATE_TAKE_KEY 手动分组,详见 remixmate host probe'
|
|
138
|
-
: `${take.key}(宿主:${take.host || 'unknown'},来源:${take.source})`,
|
|
139
|
-
});
|
|
140
|
-
const resolved = await resolvePrivToken({ apiBaseUrl });
|
|
141
|
-
const stored = await listCredentials();
|
|
142
|
-
if (resolved) {
|
|
143
|
-
checks.push({
|
|
144
|
-
name: '凭证',
|
|
145
|
-
status: 'ok',
|
|
146
|
-
detail: `${resolved.userLabel ?? '(已认证)'}(来源:${resolved.source})`,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
else if (stored.length > 0) {
|
|
150
|
-
checks.push({
|
|
151
|
-
name: '凭证',
|
|
152
|
-
status: 'fail',
|
|
153
|
-
detail: `当前后端无凭证;已存储:${stored.map((c) => c.apiBaseUrl).join(', ')}。` +
|
|
154
|
-
' 设置 MM_API_BASE_URL 指向其中之一,或重新 remixmate login',
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
checks.push({ name: '凭证', status: 'fail', detail: '未登录 → 运行 remixmate login' });
|
|
159
|
-
}
|
|
160
|
-
checks.push({
|
|
161
|
-
name: '浏览器授权',
|
|
162
|
-
status: canAutoAuth() ? 'ok' : 'warn',
|
|
163
|
-
detail: canAutoAuth()
|
|
164
|
-
? '可用(缺凭证时会自动打开授权页)'
|
|
165
|
-
: `不可用(${isHeadless() ? 'CI / 无桌面会话' : '已被 REMIXMATE_NO_BROWSER_AUTH 关闭'})→ 请用 PRIV_TOKEN`,
|
|
166
|
-
});
|
|
167
|
-
if (!opts.skipNetwork)
|
|
168
|
-
checks.push(await checkBackend(apiBaseUrl));
|
|
169
|
-
for (const c of checks) {
|
|
170
|
-
// 16 而不是 14:宿主检查名最长是「WorkBuddy skill」,窄一格就会把那两行的
|
|
171
|
-
// detail 列顶出去,报告看起来像是漏了对齐。
|
|
172
|
-
process.stdout.write(`${ICON[c.status]} ${padDisplay(c.name, 16)}${c.detail}\n`);
|
|
173
|
-
}
|
|
174
|
-
// 宿主未装 skill 只算 warn(不影响退出码),但它是「agent 说不知道 RemixMate」的头号原因,
|
|
175
|
-
// 单独在报告末尾点名,避免淹没在一屏 ✅ 里。
|
|
176
|
-
const installHint = skillInstallHint(hostStates);
|
|
177
|
-
if (installHint)
|
|
178
|
-
process.stdout.write(`\n${installHint}\n`);
|
|
179
|
-
const failed = checks.filter((c) => c.status === 'fail');
|
|
180
|
-
if (failed.length === 0) {
|
|
181
|
-
process.stdout.write('\n一切正常。\n');
|
|
182
|
-
return EXIT.OK;
|
|
183
|
-
}
|
|
184
|
-
process.stdout.write(`\n${failed.length} 项需要处理:${failed.map((c) => c.name).join('、')}\n`);
|
|
185
|
-
// A missing credential is the single most common cause and has a dedicated
|
|
186
|
-
// exit code, so a host can act on it without parsing the report.
|
|
187
|
-
return failed.some((c) => c.name === '凭证') ? EXIT.NOT_AUTHENTICATED : EXIT.ERROR;
|
|
188
|
-
}
|
|
1
|
+
var T=Object.defineProperty;var i=(n,s)=>T(n,"name",{value:s,configurable:!0});import{spawnSync as _}from"node:child_process";import{resolveApiBaseUrl as $}from"./http.js";import{EXIT as m}from"./errors.js";import{resolvePrivToken as k}from"./auth/resolve.js";import{listCredentials as w}from"./auth/credential-store.js";import{resolveTakeKey as b}from"./project/host.js";import{readBinding as E}from"./project/store.js";import{canAutoAuth as y,isHeadless as A}from"./auth/environment.js";import{installStates as v,skillInstallHint as x}from"./install.js";import{padDisplay as I}from"./text.js";async function V(){const n=$(),s=await k({apiBaseUrl:n});if(s)return`✅ 已登录 ${s.userLabel?`${s.userLabel} `:""}@ ${n}(来源:${s.source})`;const t=await w();return t.length>0?`⚠️ 当前后端 ${n} 未登录;已有凭证的后端:${t.map(o=>o.apiBaseUrl).join(", ")}`:`⚠️ 未登录(${n})→ 运行 remixmate login`}i(V,"authStatusLine");const N={ok:"✅",warn:"⚠️ ",fail:"❌"};function g(n,s){try{const t=_(n,s,{encoding:"utf8",timeout:1e4});return t.error||t.status!==0?null:(t.stdout||t.stderr||"").trim().split(`
|
|
2
|
+
`)[0]||null}catch{return null}}i(g,"probe");async function O(n){const s=new AbortController,t=setTimeout(()=>s.abort(),8e3);try{const o=await fetch(`${n}/auth/device/code`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({clientLabel:"remixmate-doctor"}),signal:s.signal});return{name:"后端可达性",status:o.ok?"ok":"warn",detail:`${n} → HTTP ${o.status}`}}catch(o){const r=o instanceof Error&&o.name==="AbortError"?"超时":o.message;return{name:"后端可达性",status:"fail",detail:`${n} 不可达(${r})`}}finally{clearTimeout(t)}}i(O,"checkBackend");async function K(n={}){const s=$(),t=[],o=Number(process.versions.node.split(".")[0]);t.push({name:"Node",status:o>=18?"ok":"fail",detail:`v${process.versions.node}${o>=18?"":"(需要 >= 18)"}`});const r=g("python3",["--version"]);t.push({name:"python3",status:r?"ok":"fail",detail:r??"未找到 —— 12 个 skill 中的 8 个需要 python3 >= 3.10"});const p=r?g("python3",["-c","import playwright; print(playwright.__version__)"]):null;t.push({name:"Playwright",status:p?"ok":"warn",detail:p??"当前解释器下未安装 —— 仅 web-screenshot / web-record 需要,首次运行会自动装"}),t.push({name:"后端地址",status:"ok",detail:s});const d=v();for(const e of d)t.push({name:`${e.label.split(" ")[0]} skill`,status:e.installedVersion==null||e.drifted?"warn":"ok",detail:e.installedVersion==null?`未安装 —— 该 agent 不会自动发现这些 skill(remixmate install 可安装到 ${e.skillsDir})`:e.drifted?`已装 ${e.installedVersion},当前包 ${e.currentVersion} → 运行 remixmate install 覆盖`:`${e.installedVersion}(${e.skillsDir})`});const u=await E(s);t.push({name:"项目归属",status:"ok",detail:u?`${u.projectId}(${u.scope==="path"?"目录绑定":"全局绑定"})`:"未绑定 → 产物落进默认项目「未分类」(remixmate project use <prj> 可改)"});const a=b(void 0);t.push({name:"take 分组",status:a.source==="none"?"warn":"ok",detail:a.source==="none"?"宿主未提供会话 ID —— 每次渲染各自成为一个 take。可 export REMIXMATE_TAKE_KEY 手动分组,详见 remixmate host probe":`${a.key}(宿主:${a.host||"unknown"},来源:${a.source})`});const c=await k({apiBaseUrl:s}),f=await w();c?t.push({name:"凭证",status:"ok",detail:`${c.userLabel??"(已认证)"}(来源:${c.source})`}):f.length>0?t.push({name:"凭证",status:"fail",detail:`当前后端无凭证;已存储:${f.map(e=>e.apiBaseUrl).join(", ")}。 设置 MM_API_BASE_URL 指向其中之一,或重新 remixmate login`}):t.push({name:"凭证",status:"fail",detail:"未登录 → 运行 remixmate login"}),t.push({name:"浏览器授权",status:y()?"ok":"warn",detail:y()?"可用(缺凭证时会自动打开授权页)":`不可用(${A()?"CI / 无桌面会话":"已被 REMIXMATE_NO_BROWSER_AUTH 关闭"})→ 请用 PRIV_TOKEN`}),n.skipNetwork||t.push(await O(s));for(const e of t)process.stdout.write(`${N[e.status]} ${I(e.name,16)}${e.detail}
|
|
3
|
+
`);const h=x(d);h&&process.stdout.write(`
|
|
4
|
+
${h}
|
|
5
|
+
`);const l=t.filter(e=>e.status==="fail");return l.length===0?(process.stdout.write(`
|
|
6
|
+
一切正常。
|
|
7
|
+
`),m.OK):(process.stdout.write(`
|
|
8
|
+
${l.length} 项需要处理:${l.map(e=>e.name).join("、")}
|
|
9
|
+
`),l.some(e=>e.name==="凭证")?m.NOT_AUTHENTICATED:m.ERROR)}i(K,"runDoctor");export{V as authStatusLine,K as runDoctor};
|
package/dist/errors.js
CHANGED
|
@@ -1,33 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Process-wide error type and exit-code contract.
|
|
3
|
-
*
|
|
4
|
-
* Extracted from http.ts so the auth gateway (auth/ensure.ts) can throw the same
|
|
5
|
-
* error type without creating an import cycle (http.ts → ensure.ts → http.ts).
|
|
6
|
-
* `SkillError` is still re-exported from http.ts, so existing imports are unchanged.
|
|
7
|
-
*
|
|
8
|
-
* Exit codes are a CONTRACT with hosts (Claude Code / Codex / ab-agent): an agent
|
|
9
|
-
* reads the code to decide whether to guide the user through authorization, retry
|
|
10
|
-
* later, or surface a hard failure. Never reuse a code for a different meaning.
|
|
11
|
-
*/
|
|
12
|
-
export const EXIT = {
|
|
13
|
-
/** Success. */
|
|
14
|
-
OK: 0,
|
|
15
|
-
/** Generic runtime failure. */
|
|
16
|
-
ERROR: 1,
|
|
17
|
-
/** Usage error — unknown skill / unknown option. */
|
|
18
|
-
USAGE: 2,
|
|
19
|
-
/** No usable credential, or the credential was rejected. Host should offer to authorize. */
|
|
20
|
-
NOT_AUTHENTICATED: 4,
|
|
21
|
-
/** Backend unreachable / timed out. Distinct from 4 so "log in" isn't suggested for an outage. */
|
|
22
|
-
BACKEND_UNREACHABLE: 5,
|
|
23
|
-
/** Failed to spawn the underlying interpreter. */
|
|
24
|
-
SPAWN_FAILED: 127,
|
|
25
|
-
};
|
|
26
|
-
export class SkillError extends Error {
|
|
27
|
-
exitCode;
|
|
28
|
-
constructor(message, exitCode = EXIT.ERROR) {
|
|
29
|
-
super(message);
|
|
30
|
-
this.exitCode = exitCode;
|
|
31
|
-
this.name = 'SkillError';
|
|
32
|
-
}
|
|
33
|
-
}
|
|
1
|
+
var t=Object.defineProperty;var e=(E,r)=>t(E,"name",{value:r,configurable:!0});const s={OK:0,ERROR:1,USAGE:2,NOT_AUTHENTICATED:4,BACKEND_UNREACHABLE:5,SPAWN_FAILED:127};class R extends Error{static{e(this,"SkillError")}exitCode;constructor(r,o=s.ERROR){super(r),this.exitCode=o,this.name="SkillError"}}export{s as EXIT,R as SkillError};
|
package/dist/exec.js
CHANGED
|
@@ -1,47 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* environment (`PRIV_TOKEN` + `MM_API_BASE_URL`), using the same gateway that
|
|
6
|
-
* skill dispatch uses.
|
|
7
|
-
*
|
|
8
|
-
* This is the general form of the invariant the auth rework establishes: Node
|
|
9
|
-
* is the only thing that reads the credential store / keychain / device flow,
|
|
10
|
-
* and every child process receives the result through its environment. Without
|
|
11
|
-
* it, maintenance scripts that import `registry_loader` directly (e.g.
|
|
12
|
-
* scripts/test-template-pipeline.py) would have to re-implement credential
|
|
13
|
-
* lookup — which is exactly the duplication, and the cross-origin token leak,
|
|
14
|
-
* that was just removed.
|
|
15
|
-
*
|
|
16
|
-
* The token is never printed; it only ever reaches the child's environment.
|
|
17
|
-
*/
|
|
18
|
-
import { spawn } from 'node:child_process';
|
|
19
|
-
import { parseArgv } from './argv.js';
|
|
20
|
-
import { EXIT } from './errors.js';
|
|
21
|
-
import { authChildEnv, ensureAuth } from './auth/ensure.js';
|
|
22
|
-
const USAGE = 'Usage: remixmate exec [--api-base-url URL] [--token TOKEN] -- <command> [args...]\n';
|
|
23
|
-
export async function runExec(argv) {
|
|
24
|
-
const separator = argv.indexOf('--');
|
|
25
|
-
if (separator === -1 || separator === argv.length - 1) {
|
|
26
|
-
process.stderr.write(`❌ exec 需要一个 \`--\` 分隔符与要执行的命令。\n${USAGE}`);
|
|
27
|
-
return EXIT.USAGE;
|
|
28
|
-
}
|
|
29
|
-
const flags = parseArgv(argv.slice(0, separator));
|
|
30
|
-
const [command, ...args] = argv.slice(separator + 1);
|
|
31
|
-
const auth = await ensureAuth({
|
|
32
|
-
mode: 'required',
|
|
33
|
-
apiBaseUrl: typeof flags.api_base_url === 'string' ? flags.api_base_url : undefined,
|
|
34
|
-
flagToken: typeof flags.token === 'string' ? flags.token : undefined,
|
|
35
|
-
});
|
|
36
|
-
return await new Promise((resolve) => {
|
|
37
|
-
const proc = spawn(command, args, {
|
|
38
|
-
stdio: 'inherit',
|
|
39
|
-
env: { ...process.env, ...authChildEnv(auth) },
|
|
40
|
-
});
|
|
41
|
-
proc.on('exit', (code, signal) => resolve(signal ? EXIT.ERROR : code ?? EXIT.ERROR));
|
|
42
|
-
proc.on('error', (err) => {
|
|
43
|
-
process.stderr.write(`❌ failed to spawn ${command}: ${err.message}\n`);
|
|
44
|
-
resolve(EXIT.SPAWN_FAILED);
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
}
|
|
1
|
+
var f=Object.defineProperty;var c=(e,r)=>f(e,"name",{value:r,configurable:!0});import{spawn as u}from"node:child_process";import{parseArgv as l}from"./argv.js";import{EXIT as t}from"./errors.js";import{authChildEnv as E,ensureAuth as g}from"./auth/ensure.js";const w=`Usage: remixmate exec [--api-base-url URL] [--token TOKEN] -- <command> [args...]
|
|
2
|
+
`;async function _(e){const r=e.indexOf("--");if(r===-1||r===e.length-1)return process.stderr.write(`❌ exec 需要一个 \`--\` 分隔符与要执行的命令。
|
|
3
|
+
${w}`),t.USAGE;const n=l(e.slice(0,r)),[s,...p]=e.slice(r+1),m=await g({mode:"required",apiBaseUrl:typeof n.api_base_url=="string"?n.api_base_url:void 0,flagToken:typeof n.token=="string"?n.token:void 0});return await new Promise(i=>{const a=u(s,p,{stdio:"inherit",env:{...process.env,...E(m)}});a.on("exit",(o,d)=>i(d?t.ERROR:o??t.ERROR)),a.on("error",o=>{process.stderr.write(`❌ failed to spawn ${s}: ${o.message}
|
|
4
|
+
`),i(t.SPAWN_FAILED)})})}c(_,"runExec");export{_ as runExec};
|