@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.
Files changed (179) hide show
  1. package/LICENSE +45 -21
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/dist/argv.js +1 -51
  5. package/dist/auth/auth-lock.js +1 -100
  6. package/dist/auth/auto-login.js +7 -75
  7. package/dist/auth/commands.js +40 -290
  8. package/dist/auth/credential-store.js +1 -143
  9. package/dist/auth/device-flow-runner.js +13 -110
  10. package/dist/auth/device-flow.js +1 -132
  11. package/dist/auth/ensure.js +1 -62
  12. package/dist/auth/environment.js +1 -69
  13. package/dist/auth/pending-store.js +1 -45
  14. package/dist/auth/resolve.js +1 -44
  15. package/dist/billing.js +3 -76
  16. package/dist/capabilities.js +1 -75
  17. package/dist/cli.js +39 -211
  18. package/dist/diagnostics.js +2 -70
  19. package/dist/doctor.js +9 -188
  20. package/dist/errors.js +1 -33
  21. package/dist/exec.js +4 -47
  22. package/dist/handlers/gen-digital-human.js +33 -264
  23. package/dist/handlers/gen-image.js +9 -113
  24. package/dist/handlers/gen-post.js +15 -192
  25. package/dist/handlers/gen-video.js +7 -185
  26. package/dist/handlers/gen-voice.js +19 -133
  27. package/dist/handlers/index.js +1 -20
  28. package/dist/handlers/shared.js +1 -66
  29. package/dist/handlers/template-init.js +1 -21
  30. package/dist/http.js +3 -155
  31. package/dist/index.js +1 -8
  32. package/dist/install.js +40 -448
  33. package/dist/manifest.json +44 -3
  34. package/dist/progress.js +2 -15
  35. package/dist/project/commands.js +34 -156
  36. package/dist/project/host.js +1 -151
  37. package/dist/project/resolve.js +1 -65
  38. package/dist/project/store.js +2 -97
  39. package/dist/project/take.js +1 -106
  40. package/dist/project/trace.js +5 -278
  41. package/dist/registry.js +3 -70
  42. package/dist/runner.js +7 -225
  43. package/dist/skill-schema.js +1 -194
  44. package/dist/template/args.js +1 -69
  45. package/dist/template/check.js +2 -30
  46. package/dist/template/commands.js +34 -163
  47. package/dist/template/constants.js +1 -46
  48. package/dist/template/init.js +5 -133
  49. package/dist/template/list.js +1 -46
  50. package/dist/template/preview.js +7 -318
  51. package/dist/template/proc.js +1 -43
  52. package/dist/template/project.js +6 -161
  53. package/dist/template/publish.js +5 -185
  54. package/dist/template/render-client.js +2 -79
  55. package/dist/template/upgrade.js +4 -123
  56. package/dist/text.js +1 -15
  57. package/package.json +2 -35
  58. package/skills/doc-parse/scripts/extract_via_render.py +32 -32
  59. package/skills/export-jianying/scripts/gen_jianying_draft.py +188 -188
  60. package/skills/gen-script/SKILL.md +23 -1
  61. package/skills/gen-script/scripts/gen_script.py +644 -551
  62. package/skills/gen-script/skill.json +12 -0
  63. package/skills/gen-script/version.json +1 -1
  64. package/skills/prepare-video-assets/scripts/prepare_video_assets.py +21 -21
  65. package/skills/render-video/scripts/_chrome_vendor.py +17 -17
  66. package/skills/render-video/scripts/_digital_human_segments.py +34 -34
  67. package/skills/render-video/scripts/_video_probe.py +33 -33
  68. package/skills/render-video/scripts/_vod_polling.py +13 -13
  69. package/skills/render-video/scripts/remote_renderer_client.py +42 -42
  70. package/skills/render-video/scripts/render_video.py +639 -639
  71. package/skills/render-video/scripts/required_props.py +37 -37
  72. package/skills/render-video/scripts/upload_video.py +35 -35
  73. package/skills/render-video/version.json +1 -1
  74. package/skills/template-registry/SKILL.md +16 -0
  75. package/skills/template-registry/scripts/countdown_spec.py +92 -92
  76. package/skills/template-registry/scripts/doc_extract.py +40 -40
  77. package/skills/template-registry/scripts/list_templates.py +303 -191
  78. package/skills/template-registry/scripts/log_diagnostics.py +1 -1
  79. package/skills/template-registry/scripts/match_template.py +87 -87
  80. package/skills/template-registry/scripts/payload_hydrator.py +89 -89
  81. package/skills/template-registry/scripts/registry_loader.py +159 -159
  82. package/skills/template-registry/scripts/render_job_client.py +75 -75
  83. package/skills/template-registry/scripts/showcase_loader.py +44 -44
  84. package/skills/template-registry/skill.json +4 -1
  85. package/skills/template-registry/version.json +1 -1
  86. package/skills/template-registry/video_dsl/runtime/__init__.py +8 -8
  87. package/skills/template-registry/video_dsl/runtime/dsl_validator.py +329 -329
  88. package/skills/template-registry/video_dsl/runtime/narration_budget.py +36 -36
  89. package/skills/template-registry/video_dsl/runtime/payload_contract.py +7 -7
  90. package/skills/template-registry/video_dsl/runtime/prompt_enhancer.py +98 -98
  91. package/skills/template-registry/video_dsl/runtime/timeline_compiler.py +127 -127
  92. package/skills/template-registry/video_dsl/schema/video-dsl-v1alpha1.json +14 -1
  93. package/skills/video-parser/scripts/analyze_video.py +173 -173
  94. package/skills/video-parser/scripts/deconstruct_video.py +91 -91
  95. package/skills/video-parser/scripts/parse_via_render.py +21 -21
  96. package/skills/web-screenshot/scripts/_media_screenshot/__init__.py +4 -4
  97. package/skills/web-screenshot/scripts/_media_screenshot/bootstrap.py +7 -7
  98. package/skills/web-screenshot/scripts/_media_screenshot/browser.py +4 -4
  99. package/skills/web-screenshot/scripts/_media_screenshot/cli_args.py +4 -4
  100. package/skills/web-screenshot/scripts/_media_screenshot/js_loader.py +2 -2
  101. package/skills/web-screenshot/scripts/_media_screenshot/overlay.py +1 -1
  102. package/skills/web-screenshot/scripts/_media_screenshot/reader.py +40 -40
  103. package/skills/web-screenshot/scripts/_media_screenshot/recording.py +11 -11
  104. package/skills/web-screenshot/scripts/_media_screenshot/scenes/__init__.py +8 -8
  105. package/skills/web-screenshot/scripts/_media_screenshot/scenes/arrow.py +2 -2
  106. package/skills/web-screenshot/scripts/_media_screenshot/scenes/caption.py +1 -1
  107. package/skills/web-screenshot/scripts/_media_screenshot/scenes/click.py +1 -1
  108. package/skills/web-screenshot/scripts/_media_screenshot/scenes/code_lines.py +5 -5
  109. package/skills/web-screenshot/scripts/_media_screenshot/scenes/focus.py +1 -1
  110. package/skills/web-screenshot/scripts/_media_screenshot/scenes/highlight.py +3 -3
  111. package/skills/web-screenshot/scripts/_media_screenshot/scenes/hover.py +1 -1
  112. package/skills/web-screenshot/scripts/_media_screenshot/scenes/redact.py +1 -1
  113. package/skills/web-screenshot/scripts/_media_screenshot/scenes/scroll.py +1 -1
  114. package/skills/web-screenshot/scripts/_media_screenshot/scenes/sequence.py +1 -1
  115. package/skills/web-screenshot/scripts/_media_screenshot/scenes/title_card.py +1 -1
  116. package/skills/web-screenshot/scripts/_media_screenshot/scenes/type.py +1 -1
  117. package/skills/web-screenshot/scripts/_media_screenshot/scenes/wait.py +1 -1
  118. package/skills/web-screenshot/scripts/_media_screenshot/scenes/zoom.py +2 -2
  119. package/skills/web-screenshot/scripts/_media_screenshot/screenshot.py +15 -15
  120. package/skills/web-screenshot/scripts/_media_screenshot/storyboard.py +2 -2
  121. package/skills/web-screenshot/scripts/_media_screenshot/template.py +11 -11
  122. package/skills/web-screenshot/scripts/_media_screenshot/trim.py +10 -10
  123. package/skills/web-screenshot/scripts/_media_screenshot/urlguard.py +22 -22
  124. package/skills/web-screenshot/scripts/_media_upload.py +33 -33
  125. package/skills/web-screenshot/scripts/read_page.py +15 -15
  126. package/skills/web-screenshot/scripts/record.py +26 -26
  127. package/skills/web-screenshot/scripts/screenshot.py +5 -5
  128. package/dist/argv.d.ts +0 -15
  129. package/dist/auth/auth-lock.d.ts +0 -26
  130. package/dist/auth/auto-login.d.ts +0 -20
  131. package/dist/auth/commands.d.ts +0 -8
  132. package/dist/auth/credential-store.d.ts +0 -60
  133. package/dist/auth/device-flow-runner.d.ts +0 -58
  134. package/dist/auth/device-flow.d.ts +0 -52
  135. package/dist/auth/ensure.d.ts +0 -55
  136. package/dist/auth/environment.d.ts +0 -38
  137. package/dist/auth/pending-store.d.ts +0 -33
  138. package/dist/auth/resolve.d.ts +0 -30
  139. package/dist/billing.d.ts +0 -44
  140. package/dist/capabilities.d.ts +0 -63
  141. package/dist/cli.d.ts +0 -13
  142. package/dist/diagnostics.d.ts +0 -24
  143. package/dist/doctor.d.ts +0 -22
  144. package/dist/errors.d.ts +0 -29
  145. package/dist/exec.d.ts +0 -18
  146. package/dist/handlers/gen-digital-human.d.ts +0 -17
  147. package/dist/handlers/gen-image.d.ts +0 -20
  148. package/dist/handlers/gen-post.d.ts +0 -22
  149. package/dist/handlers/gen-video.d.ts +0 -19
  150. package/dist/handlers/gen-voice.d.ts +0 -26
  151. package/dist/handlers/index.d.ts +0 -20
  152. package/dist/handlers/shared.d.ts +0 -26
  153. package/dist/handlers/template-init.d.ts +0 -10
  154. package/dist/http.d.ts +0 -72
  155. package/dist/index.d.ts +0 -9
  156. package/dist/install.d.ts +0 -71
  157. package/dist/progress.d.ts +0 -23
  158. package/dist/project/commands.d.ts +0 -19
  159. package/dist/project/host.d.ts +0 -91
  160. package/dist/project/resolve.d.ts +0 -43
  161. package/dist/project/store.d.ts +0 -31
  162. package/dist/project/take.d.ts +0 -50
  163. package/dist/project/trace.d.ts +0 -98
  164. package/dist/registry.d.ts +0 -30
  165. package/dist/runner.d.ts +0 -39
  166. package/dist/skill-schema.d.ts +0 -138
  167. package/dist/template/args.d.ts +0 -25
  168. package/dist/template/check.d.ts +0 -23
  169. package/dist/template/commands.d.ts +0 -25
  170. package/dist/template/constants.d.ts +0 -39
  171. package/dist/template/init.d.ts +0 -28
  172. package/dist/template/list.d.ts +0 -23
  173. package/dist/template/preview.d.ts +0 -60
  174. package/dist/template/proc.d.ts +0 -26
  175. package/dist/template/project.d.ts +0 -47
  176. package/dist/template/publish.d.ts +0 -43
  177. package/dist/template/render-client.d.ts +0 -39
  178. package/dist/template/upgrade.d.ts +0 -44
  179. package/dist/text.d.ts +0 -8
package/dist/cli.js CHANGED
@@ -1,212 +1,40 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * remixmate CLI entrypoint.
4
- *
5
- * remixmate --list list registered skills
6
- * remixmate <name> --help forward --help to underlying skill
7
- * remixmate <name> [--flag value ...] invoke the skill
8
- *
9
- * For python skills (entry.type === 'python') we spawn the script and inherit
10
- * its stdio so progress / errors flow through unchanged.
11
- * For http / builtin skills we dispatch in-process to handlers/.
12
- */
13
- import path from 'node:path';
14
- import { loadSkills, SKILLS_DIR } from './registry.js';
15
- import { parseArgv } from './argv.js';
16
- import { runSkill } from './runner.js';
17
- import { runAuthCommand } from './auth/commands.js';
18
- import { HOST_VERB, PROJECT_VERB, runHostCommand, runProjectCommand } from './project/commands.js';
19
- import { INSTALL_VERB, UNINSTALL_VERB, runInstall, runUninstall, skillInstallHint, packageVersion } from './install.js';
20
- import { runExec } from './exec.js';
21
- import { authStatusLine, runDoctor } from './doctor.js';
22
- import { padDisplay } from './text.js';
23
- import { EXIT, SkillError } from './errors.js';
24
- import { TEMPLATE_VERB, runTemplateCommand } from './template/commands.js';
25
- import { diagnostic } from './diagnostics.js';
26
- /** Reserved auth verbs handled before skill dispatch. */
27
- const AUTH_VERBS = new Set(['login', 'logout', 'whoami']);
28
- /**
29
- * SKILL_BASE_DIR can arrive as a relative path (e.g. from a developer .env or
30
- * from ab-agent's spawn env). The cli script's cwd isn't a stable anchor for
31
- * that — it's whatever the caller set, often the bundled `skills-cli/`
32
- * directory. Resolve against process.cwd() (= the caller's intended root) up
33
- * front so any later existsSync / readdirSync call sees an absolute,
34
- * predictable path.
35
- */
36
- function resolveBaseDir() {
37
- const raw = process.env.SKILL_BASE_DIR;
38
- if (!raw)
39
- return SKILLS_DIR;
40
- return path.isAbsolute(raw) ? raw : path.resolve(process.cwd(), raw);
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)});
@@ -1,70 +1,2 @@
1
- import { randomBytes, randomUUID } from 'node:crypto';
2
- export function traceHeaders() {
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
- * `remixmate doctor` — one command that answers "why isn't this working?".
3
- *
4
- * The failure modes this package actually produces in the field are: no
5
- * credential, a credential bound to a different backend, a missing python3, a
6
- * missing Playwright, and an unreachable backend. Each of those used to surface
7
- * only as a downstream error from whichever skill happened to be run first, in
8
- * that skill's own wording. Checking them up front, in one place, turns a
9
- * scavenger hunt into a list.
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
- * `remixmate exec -- <command> [args...]`
3
- *
4
- * Runs an arbitrary command with the resolved credential injected into its
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};