@remixmate/cli 0.9.71 → 0.9.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +45 -21
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/argv.js +1 -51
- package/dist/auth/auth-lock.js +1 -100
- package/dist/auth/auto-login.js +7 -75
- package/dist/auth/commands.js +40 -290
- package/dist/auth/credential-store.js +1 -143
- package/dist/auth/device-flow-runner.js +13 -110
- package/dist/auth/device-flow.js +1 -132
- package/dist/auth/ensure.js +1 -62
- package/dist/auth/environment.js +1 -69
- package/dist/auth/pending-store.js +1 -45
- package/dist/auth/resolve.js +1 -44
- package/dist/billing.js +3 -76
- package/dist/capabilities.js +1 -75
- package/dist/cli.js +39 -211
- package/dist/diagnostics.js +2 -70
- package/dist/doctor.js +9 -188
- package/dist/errors.js +1 -33
- package/dist/exec.js +4 -47
- package/dist/handlers/gen-digital-human.js +33 -264
- package/dist/handlers/gen-image.js +9 -113
- package/dist/handlers/gen-post.js +15 -188
- package/dist/handlers/gen-video.js +7 -185
- package/dist/handlers/gen-voice.js +19 -133
- package/dist/handlers/index.js +1 -20
- package/dist/handlers/shared.js +1 -66
- package/dist/handlers/template-init.js +1 -21
- package/dist/http.js +3 -155
- package/dist/index.js +1 -8
- package/dist/install.js +40 -448
- package/dist/manifest.json +53 -13
- package/dist/progress.js +2 -15
- package/dist/project/commands.js +34 -156
- package/dist/project/host.js +1 -151
- package/dist/project/resolve.js +1 -65
- package/dist/project/store.js +2 -97
- package/dist/project/take.js +1 -106
- package/dist/project/trace.js +5 -278
- package/dist/registry.js +3 -70
- package/dist/runner.js +7 -225
- package/dist/skill-schema.js +1 -194
- package/dist/template/args.js +1 -69
- package/dist/template/check.js +2 -30
- package/dist/template/commands.js +34 -163
- package/dist/template/constants.js +1 -46
- package/dist/template/init.js +5 -133
- package/dist/template/list.js +1 -46
- package/dist/template/preview.js +7 -318
- package/dist/template/proc.js +1 -43
- package/dist/template/project.js +6 -161
- package/dist/template/publish.js +5 -185
- package/dist/template/render-client.js +2 -79
- package/dist/template/upgrade.js +4 -123
- package/dist/text.js +1 -15
- package/package.json +2 -35
- package/skills/doc-parse/scripts/extract_via_render.py +32 -32
- package/skills/export-jianying/scripts/gen_jianying_draft.py +188 -188
- package/skills/gen-post/SKILL.md +23 -13
- package/skills/gen-post/skill.json +9 -10
- package/skills/gen-script/SKILL.md +23 -1
- package/skills/gen-script/scripts/gen_script.py +644 -551
- package/skills/gen-script/skill.json +12 -0
- package/skills/gen-script/version.json +1 -1
- package/skills/prepare-video-assets/scripts/prepare_video_assets.py +21 -21
- package/skills/render-video/scripts/_chrome_vendor.py +17 -17
- package/skills/render-video/scripts/_digital_human_segments.py +34 -34
- package/skills/render-video/scripts/_video_probe.py +33 -33
- package/skills/render-video/scripts/_vod_polling.py +13 -13
- package/skills/render-video/scripts/remote_renderer_client.py +42 -42
- package/skills/render-video/scripts/render_video.py +639 -639
- package/skills/render-video/scripts/required_props.py +37 -37
- package/skills/render-video/scripts/upload_video.py +35 -35
- package/skills/render-video/version.json +1 -1
- package/skills/template-registry/SKILL.md +16 -0
- package/skills/template-registry/scripts/countdown_spec.py +92 -92
- package/skills/template-registry/scripts/doc_extract.py +40 -40
- package/skills/template-registry/scripts/list_templates.py +303 -191
- package/skills/template-registry/scripts/log_diagnostics.py +1 -1
- package/skills/template-registry/scripts/match_template.py +87 -87
- package/skills/template-registry/scripts/payload_hydrator.py +89 -89
- package/skills/template-registry/scripts/registry_loader.py +159 -159
- package/skills/template-registry/scripts/render_job_client.py +75 -75
- package/skills/template-registry/scripts/showcase_loader.py +44 -44
- package/skills/template-registry/skill.json +4 -1
- package/skills/template-registry/version.json +1 -1
- package/skills/template-registry/video_dsl/runtime/__init__.py +8 -8
- package/skills/template-registry/video_dsl/runtime/dsl_validator.py +329 -329
- package/skills/template-registry/video_dsl/runtime/narration_budget.py +36 -36
- package/skills/template-registry/video_dsl/runtime/payload_contract.py +7 -7
- package/skills/template-registry/video_dsl/runtime/prompt_enhancer.py +98 -98
- package/skills/template-registry/video_dsl/runtime/timeline_compiler.py +127 -127
- package/skills/template-registry/video_dsl/schema/video-dsl-v1alpha1.json +14 -1
- package/skills/video-parser/scripts/analyze_video.py +173 -173
- package/skills/video-parser/scripts/deconstruct_video.py +91 -91
- package/skills/video-parser/scripts/parse_via_render.py +21 -21
- package/skills/web-screenshot/scripts/_media_screenshot/__init__.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/bootstrap.py +7 -7
- package/skills/web-screenshot/scripts/_media_screenshot/browser.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/cli_args.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/js_loader.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/overlay.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/reader.py +40 -40
- package/skills/web-screenshot/scripts/_media_screenshot/recording.py +11 -11
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/__init__.py +8 -8
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/arrow.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/caption.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/click.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/code_lines.py +5 -5
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/focus.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/highlight.py +3 -3
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/hover.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/redact.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/scroll.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/sequence.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/title_card.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/type.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/wait.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/zoom.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/screenshot.py +15 -15
- package/skills/web-screenshot/scripts/_media_screenshot/storyboard.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/template.py +11 -11
- package/skills/web-screenshot/scripts/_media_screenshot/trim.py +10 -10
- package/skills/web-screenshot/scripts/_media_screenshot/urlguard.py +22 -22
- package/skills/web-screenshot/scripts/_media_upload.py +33 -33
- package/skills/web-screenshot/scripts/read_page.py +15 -15
- package/skills/web-screenshot/scripts/record.py +26 -26
- package/skills/web-screenshot/scripts/screenshot.py +5 -5
- package/dist/argv.d.ts +0 -15
- package/dist/auth/auth-lock.d.ts +0 -26
- package/dist/auth/auto-login.d.ts +0 -20
- package/dist/auth/commands.d.ts +0 -8
- package/dist/auth/credential-store.d.ts +0 -60
- package/dist/auth/device-flow-runner.d.ts +0 -58
- package/dist/auth/device-flow.d.ts +0 -52
- package/dist/auth/ensure.d.ts +0 -55
- package/dist/auth/environment.d.ts +0 -38
- package/dist/auth/pending-store.d.ts +0 -33
- package/dist/auth/resolve.d.ts +0 -30
- package/dist/billing.d.ts +0 -44
- package/dist/capabilities.d.ts +0 -63
- package/dist/cli.d.ts +0 -13
- package/dist/diagnostics.d.ts +0 -24
- package/dist/doctor.d.ts +0 -22
- package/dist/errors.d.ts +0 -29
- package/dist/exec.d.ts +0 -18
- package/dist/handlers/gen-digital-human.d.ts +0 -17
- package/dist/handlers/gen-image.d.ts +0 -20
- package/dist/handlers/gen-post.d.ts +0 -21
- package/dist/handlers/gen-video.d.ts +0 -19
- package/dist/handlers/gen-voice.d.ts +0 -26
- package/dist/handlers/index.d.ts +0 -20
- package/dist/handlers/shared.d.ts +0 -26
- package/dist/handlers/template-init.d.ts +0 -10
- package/dist/http.d.ts +0 -72
- package/dist/index.d.ts +0 -9
- package/dist/install.d.ts +0 -71
- package/dist/progress.d.ts +0 -23
- package/dist/project/commands.d.ts +0 -19
- package/dist/project/host.d.ts +0 -91
- package/dist/project/resolve.d.ts +0 -43
- package/dist/project/store.d.ts +0 -31
- package/dist/project/take.d.ts +0 -50
- package/dist/project/trace.d.ts +0 -98
- package/dist/registry.d.ts +0 -30
- package/dist/runner.d.ts +0 -39
- package/dist/skill-schema.d.ts +0 -138
- package/dist/template/args.d.ts +0 -25
- package/dist/template/check.d.ts +0 -23
- package/dist/template/commands.d.ts +0 -25
- package/dist/template/constants.d.ts +0 -39
- package/dist/template/init.d.ts +0 -28
- package/dist/template/list.d.ts +0 -23
- package/dist/template/preview.d.ts +0 -60
- package/dist/template/proc.d.ts +0 -26
- package/dist/template/project.d.ts +0 -47
- package/dist/template/publish.d.ts +0 -43
- package/dist/template/render-client.d.ts +0 -39
- package/dist/template/upgrade.d.ts +0 -44
- package/dist/text.d.ts +0 -8
package/dist/progress.js
CHANGED
|
@@ -1,15 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* executor parses (see apps/ab-agent/src/skills/executor.ts).
|
|
4
|
-
*
|
|
5
|
-
* Always writes to stdout because the consumer reads stdout line-by-line.
|
|
6
|
-
* Each event is a single JSON object on its own line, with the marker
|
|
7
|
-
* field `__progress__: true` so non-progress stdout content is ignored.
|
|
8
|
-
*
|
|
9
|
-
* The consumer flattens the entire parsed JSON into `extra`, so any
|
|
10
|
-
* additional context (model id, generationId, ...) can be passed as
|
|
11
|
-
* top-level keys and will reach the SSE client unchanged.
|
|
12
|
-
*/
|
|
13
|
-
export function emitProgress(ev) {
|
|
14
|
-
process.stdout.write(JSON.stringify({ __progress__: true, ...ev }) + '\n');
|
|
15
|
-
}
|
|
1
|
+
var e=Object.defineProperty;var s=(r,t)=>e(r,"name",{value:t,configurable:!0});function i(r){process.stdout.write(JSON.stringify({__progress__:!0,...r})+`
|
|
2
|
+
`)}s(i,"emitProgress");export{i as emitProgress};
|
package/dist/project/commands.js
CHANGED
|
@@ -1,156 +1,34 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
.filter(Boolean)
|
|
36
|
-
.join(' ');
|
|
37
|
-
process.stdout.write(` ${p.id.padEnd(20)} ${projectLabel(p)}${marks ? ` ${marks}` : ''}\n`);
|
|
38
|
-
}
|
|
39
|
-
return EXIT.OK;
|
|
40
|
-
}
|
|
41
|
-
async function cmdUse(args) {
|
|
42
|
-
const global = args.includes('--global');
|
|
43
|
-
const projectId = args.find((a) => !a.startsWith('-'));
|
|
44
|
-
if (!projectId) {
|
|
45
|
-
process.stderr.write('用法: remixmate project use <prj_xxx> [--global]\n');
|
|
46
|
-
return EXIT.USAGE;
|
|
47
|
-
}
|
|
48
|
-
const ctx = await ctxFor('use');
|
|
49
|
-
// Verify before persisting: a typo'd uid would otherwise sit in the config and
|
|
50
|
-
// silently send every later render to the default project.
|
|
51
|
-
const projects = await listProjects(ctx);
|
|
52
|
-
const match = projects.find((p) => p.id === projectId);
|
|
53
|
-
if (!match) {
|
|
54
|
-
process.stderr.write(`❌ 项目不存在或不属于当前账号: ${projectId}\n 用 remixmate project list 查看可用项目。\n`);
|
|
55
|
-
return EXIT.USAGE;
|
|
56
|
-
}
|
|
57
|
-
await writeBinding(ctx.apiBaseUrl, projectId, global ? 'default' : 'path');
|
|
58
|
-
const scopeText = global ? '全局默认' : `当前目录 (${process.cwd()})`;
|
|
59
|
-
process.stdout.write(`✅ 已绑定到 ${projectLabel(match)} [${projectId}]\n 作用域: ${scopeText}\n`);
|
|
60
|
-
if (!global) {
|
|
61
|
-
// Not a hypothetical: WorkBuddy spawns ~/WorkBuddy/<timestamp> per conversation,
|
|
62
|
-
// so a path binding made there is gone by the next conversation.
|
|
63
|
-
process.stdout.write(' 提示: 若宿主每次会话都换工作目录,请改用 --global。\n');
|
|
64
|
-
}
|
|
65
|
-
return EXIT.OK;
|
|
66
|
-
}
|
|
67
|
-
async function cmdUnuse(args) {
|
|
68
|
-
const global = args.includes('--global');
|
|
69
|
-
const apiBaseUrl = resolveApiBaseUrl();
|
|
70
|
-
const removed = await clearBinding(apiBaseUrl, global ? 'default' : 'path');
|
|
71
|
-
process.stdout.write(removed ? '✅ 绑定已清除\n' : 'ℹ️ 没有可清除的绑定\n');
|
|
72
|
-
return EXIT.OK;
|
|
73
|
-
}
|
|
74
|
-
async function cmdShow() {
|
|
75
|
-
const apiBaseUrl = resolveApiBaseUrl();
|
|
76
|
-
const bound = await readBinding(apiBaseUrl);
|
|
77
|
-
process.stdout.write(`后端: ${apiBaseUrl}\n`);
|
|
78
|
-
process.stdout.write(`配置文件: ${PROJECTS_FILE}\n`);
|
|
79
|
-
if (bound) {
|
|
80
|
-
const scope = bound.scope === 'path' ? `目录绑定 (${process.cwd()})` : '全局默认绑定';
|
|
81
|
-
process.stdout.write(`绑定项目: ${bound.projectId} [${scope}]\n`);
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
process.stdout.write('绑定项目: (无) — 产物会落进默认项目「未分类」\n');
|
|
85
|
-
}
|
|
86
|
-
const envOverride = (process.env.REMIXMATE_PROJECT_ID ?? '').trim();
|
|
87
|
-
if (envOverride) {
|
|
88
|
-
process.stdout.write(`⚠️ REMIXMATE_PROJECT_ID=${envOverride} 会覆盖上面的绑定\n`);
|
|
89
|
-
}
|
|
90
|
-
return EXIT.OK;
|
|
91
|
-
}
|
|
92
|
-
async function cmdDefault() {
|
|
93
|
-
const ctx = await ctxFor('default');
|
|
94
|
-
const p = await fetchDefaultProject(ctx);
|
|
95
|
-
process.stdout.write(`${p.id} ${projectLabel(p)}\n`);
|
|
96
|
-
return EXIT.OK;
|
|
97
|
-
}
|
|
98
|
-
export async function runProjectCommand(argv) {
|
|
99
|
-
const sub = argv[0] ?? 'show';
|
|
100
|
-
try {
|
|
101
|
-
switch (sub) {
|
|
102
|
-
case 'list':
|
|
103
|
-
return await cmdList();
|
|
104
|
-
case 'use':
|
|
105
|
-
return await cmdUse(argv.slice(1));
|
|
106
|
-
case 'unuse':
|
|
107
|
-
return await cmdUnuse(argv.slice(1));
|
|
108
|
-
case 'show':
|
|
109
|
-
return await cmdShow();
|
|
110
|
-
case 'default':
|
|
111
|
-
return await cmdDefault();
|
|
112
|
-
default:
|
|
113
|
-
process.stderr.write(`❌ 未知子命令: ${sub}\n用法: remixmate project <list|use|unuse|show|default>\n`);
|
|
114
|
-
return EXIT.USAGE;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
catch (err) {
|
|
118
|
-
if (err instanceof SkillError) {
|
|
119
|
-
process.stderr.write(err.message + '\n');
|
|
120
|
-
return err.exitCode;
|
|
121
|
-
}
|
|
122
|
-
process.stderr.write(`❌ ${err.message}\n`);
|
|
123
|
-
return EXIT.ERROR;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Report what the CLI can see about its host. Prints variable NAMES only —
|
|
128
|
-
* an environment dump is a routine way to leak tokens into a chat transcript.
|
|
129
|
-
*/
|
|
130
|
-
export async function runHostCommand(argv) {
|
|
131
|
-
const sub = argv[0] ?? 'probe';
|
|
132
|
-
if (sub !== 'probe') {
|
|
133
|
-
process.stderr.write(`❌ 未知子命令: ${sub}\n用法: remixmate host probe\n`);
|
|
134
|
-
return EXIT.USAGE;
|
|
135
|
-
}
|
|
136
|
-
const host = detectHostLabel();
|
|
137
|
-
const take = resolveTakeKey(undefined);
|
|
138
|
-
process.stdout.write(`识别宿主: ${host || '(未识别)'}\n`);
|
|
139
|
-
process.stdout.write(`分组键: ${take.key || '(无 — 每次渲染会各自成为一个 take)'}\n`);
|
|
140
|
-
process.stdout.write(`来源: ${take.source}\n`);
|
|
141
|
-
const names = probeSessionVarNames();
|
|
142
|
-
process.stdout.write('\n环境里疑似会话标识的变量(仅名称,不含取值):\n');
|
|
143
|
-
if (names.length === 0) {
|
|
144
|
-
process.stdout.write(' (无)\n');
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
for (const n of names)
|
|
148
|
-
process.stdout.write(` ${n}\n`);
|
|
149
|
-
}
|
|
150
|
-
if (!host && names.length > 0) {
|
|
151
|
-
process.stdout.write('\n宿主未被识别。若上面某个变量在整个会话期间保持不变,可以:\n' +
|
|
152
|
-
' 1) 临时方案: export REMIXMATE_TAKE_KEY="$该变量"\n' +
|
|
153
|
-
' 2) 长期方案: 在 src/project/host.ts 的 HOST_PROVIDERS 里加一行\n');
|
|
154
|
-
}
|
|
155
|
-
return EXIT.OK;
|
|
156
|
-
}
|
|
1
|
+
var m=Object.defineProperty;var n=(t,e)=>m(t,"name",{value:e,configurable:!0});import{EXIT as o,SkillError as $}from"../errors.js";import{resolveApiBaseUrl as d,resolveHttpContext as E}from"../http.js";import{detectHostLabel as h,probeSessionVarNames as x,resolveTakeKey as j}from"./host.js";import{fetchDefaultProject as b,listProjects as l}from"./resolve.js";import{clearBinding as O,readBinding as w,writeBinding as R,PROJECTS_FILE as T}from"./store.js";const k="project",D="host";function u(t){return t.displayName||t.fullName||t.sourceUrl||"(未命名)"}n(u,"projectLabel");async function p(t){return await E(`project-${t}`)}n(p,"ctxFor");async function y(){const t=await p("list"),e=await l(t);if(e.length===0)return process.stdout.write(`(还没有项目。渲染一次即可自动创建「未分类」)
|
|
2
|
+
`),o.OK;const s=await w(t.apiBaseUrl);for(const r of e){const c=[r.isDefault?"默认":"",s?.projectId===r.id?"← 当前绑定":""].filter(Boolean).join(" ");process.stdout.write(` ${r.id.padEnd(20)} ${u(r)}${c?` ${c}`:""}
|
|
3
|
+
`)}return o.OK}n(y,"cmdList");async function U(t){const e=t.includes("--global"),s=t.find(a=>!a.startsWith("-"));if(!s)return process.stderr.write(`用法: remixmate project use <prj_xxx> [--global]
|
|
4
|
+
`),o.USAGE;const r=await p("use"),i=(await l(r)).find(a=>a.id===s);if(!i)return process.stderr.write(`❌ 项目不存在或不属于当前账号: ${s}
|
|
5
|
+
用 remixmate project list 查看可用项目。
|
|
6
|
+
`),o.USAGE;await R(r.apiBaseUrl,s,e?"default":"path");const f=e?"全局默认":`当前目录 (${process.cwd()})`;return process.stdout.write(`✅ 已绑定到 ${u(i)} [${s}]
|
|
7
|
+
作用域: ${f}
|
|
8
|
+
`),e||process.stdout.write(` 提示: 若宿主每次会话都换工作目录,请改用 --global。
|
|
9
|
+
`),o.OK}n(U,"cmdUse");async function g(t){const e=t.includes("--global"),s=d(),r=await O(s,e?"default":"path");return process.stdout.write(r?`✅ 绑定已清除
|
|
10
|
+
`:`ℹ️ 没有可清除的绑定
|
|
11
|
+
`),o.OK}n(g,"cmdUnuse");async function B(){const t=d(),e=await w(t);if(process.stdout.write(`后端: ${t}
|
|
12
|
+
`),process.stdout.write(`配置文件: ${T}
|
|
13
|
+
`),e){const r=e.scope==="path"?`目录绑定 (${process.cwd()})`:"全局默认绑定";process.stdout.write(`绑定项目: ${e.projectId} [${r}]
|
|
14
|
+
`)}else process.stdout.write(`绑定项目: (无) — 产物会落进默认项目「未分类」
|
|
15
|
+
`);const s=(process.env.REMIXMATE_PROJECT_ID??"").trim();return s&&process.stdout.write(`⚠️ REMIXMATE_PROJECT_ID=${s} 会覆盖上面的绑定
|
|
16
|
+
`),o.OK}n(B,"cmdShow");async function I(){const t=await p("default"),e=await b(t);return process.stdout.write(`${e.id} ${u(e)}
|
|
17
|
+
`),o.OK}n(I,"cmdDefault");async function M(t){const e=t[0]??"show";try{switch(e){case"list":return await y();case"use":return await U(t.slice(1));case"unuse":return await g(t.slice(1));case"show":return await B();case"default":return await I();default:return process.stderr.write(`❌ 未知子命令: ${e}
|
|
18
|
+
用法: remixmate project <list|use|unuse|show|default>
|
|
19
|
+
`),o.USAGE}}catch(s){return s instanceof $?(process.stderr.write(s.message+`
|
|
20
|
+
`),s.exitCode):(process.stderr.write(`❌ ${s.message}
|
|
21
|
+
`),o.ERROR)}}n(M,"runProjectCommand");async function v(t){const e=t[0]??"probe";if(e!=="probe")return process.stderr.write(`❌ 未知子命令: ${e}
|
|
22
|
+
用法: remixmate host probe
|
|
23
|
+
`),o.USAGE;const s=h(),r=j(void 0);process.stdout.write(`识别宿主: ${s||"(未识别)"}
|
|
24
|
+
`),process.stdout.write(`分组键: ${r.key||"(无 — 每次渲染会各自成为一个 take)"}
|
|
25
|
+
`),process.stdout.write(`来源: ${r.source}
|
|
26
|
+
`);const c=x();if(process.stdout.write(`
|
|
27
|
+
环境里疑似会话标识的变量(仅名称,不含取值):
|
|
28
|
+
`),c.length===0)process.stdout.write(` (无)
|
|
29
|
+
`);else for(const i of c)process.stdout.write(` ${i}
|
|
30
|
+
`);return!s&&c.length>0&&process.stdout.write(`
|
|
31
|
+
宿主未被识别。若上面某个变量在整个会话期间保持不变,可以:
|
|
32
|
+
1) 临时方案: export REMIXMATE_TAKE_KEY="$该变量"
|
|
33
|
+
2) 长期方案: 在 src/project/host.ts 的 HOST_PROVIDERS 里加一行
|
|
34
|
+
`),o.OK}n(v,"runHostCommand");export{D as HOST_VERB,k as PROJECT_VERB,v as runHostCommand,M as runProjectCommand};
|
package/dist/project/host.js
CHANGED
|
@@ -1,151 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Host detection — which agent shell is this CLI running inside, and what is
|
|
3
|
-
* its conversation id?
|
|
4
|
-
*
|
|
5
|
-
* The id is used as the *grouping key* for takes: every render fired from the
|
|
6
|
-
* same host conversation lands in one VideoProject as successive takes. The
|
|
7
|
-
* creative loop ("generate → watch → shorten the intro → re-render") spans
|
|
8
|
-
* multiple skill invocations and multiple render jobs, so the conversation —
|
|
9
|
-
* not the render job — is its natural boundary.
|
|
10
|
-
*
|
|
11
|
-
* Everything here reads the **child process environment only**. We deliberately
|
|
12
|
-
* do not speak any host's private IPC (Codex exposes its thread id through an
|
|
13
|
-
* App Server JSON-RPC channel, and finding "the current thread" there means
|
|
14
|
-
* matching on cwd + status + recency — a heuristic, not an identity lookup,
|
|
15
|
-
* which would silently group takes wrong). A host that hands us nothing simply
|
|
16
|
-
* degrades one rung; see resolveTakeKey().
|
|
17
|
-
*
|
|
18
|
-
* See docs/product/cli/cli-project-binding-design.md §5.1 and appendix A.
|
|
19
|
-
*/
|
|
20
|
-
/**
|
|
21
|
-
* Adding a host is a data-only change: append a row, touch nothing else.
|
|
22
|
-
* Run `remixmate host probe` inside the new host to discover its variable names.
|
|
23
|
-
*
|
|
24
|
-
* ⚠️ ORDER IS LOAD-BEARING. CodeBuddy also sets `CLAUDE_SESSION_ID` (same value
|
|
25
|
-
* as its own id), while real Claude Code uses the longer `CLAUDE_CODE_*` prefix.
|
|
26
|
-
* Detecting Claude Code first — or matching on a bare `CLAUDE_` prefix — would
|
|
27
|
-
* mislabel every CodeBuddy session.
|
|
28
|
-
*
|
|
29
|
-
* ⚠️ Prefer the ROOT session over a child/sub-agent one. Claude Code exposes
|
|
30
|
-
* both (`CLAUDE_CODE_HOST_SESSION_ID` alongside a per-subagent
|
|
31
|
-
* `CLAUDE_CODE_SESSION_ID`); grouping must follow the root so a render fired
|
|
32
|
-
* from a subagent still joins the main take.
|
|
33
|
-
*/
|
|
34
|
-
export const HOST_PROVIDERS = [
|
|
35
|
-
{
|
|
36
|
-
host: 'codebuddy',
|
|
37
|
-
detect: (e) => !!e.CODEBUDDY_SESSION_ID || e.CODEBUDDY_HOST != null,
|
|
38
|
-
sessionVars: ['CODEBUDDY_SESSION_ID'],
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
host: 'claude-code',
|
|
42
|
-
detect: (e) => e.CLAUDECODE === '1' || !!e.CLAUDE_CODE_SESSION_ID,
|
|
43
|
-
sessionVars: ['CLAUDE_CODE_HOST_SESSION_ID', 'CLAUDE_CODE_SESSION_ID'],
|
|
44
|
-
},
|
|
45
|
-
// Untested placeholders — verify with `remixmate host probe` before trusting.
|
|
46
|
-
{ host: 'qoder', detect: (e) => !!e.QODER_SESSION_ID, sessionVars: ['QODER_SESSION_ID'] },
|
|
47
|
-
{ host: 'trae', detect: (e) => !!e.TRAE_SESSION_ID, sessionVars: ['TRAE_SESSION_ID'] },
|
|
48
|
-
];
|
|
49
|
-
/** Identify the host and its root session id, or null when unrecognised. */
|
|
50
|
-
export function detectHostSession(env = process.env) {
|
|
51
|
-
for (const provider of HOST_PROVIDERS) {
|
|
52
|
-
if (!provider.detect(env))
|
|
53
|
-
continue;
|
|
54
|
-
for (const name of provider.sessionVars) {
|
|
55
|
-
const value = (env[name] ?? '').trim();
|
|
56
|
-
if (value)
|
|
57
|
-
return { host: provider.host, sessionId: value };
|
|
58
|
-
}
|
|
59
|
-
// Recognised the host but it gave us no id — don't fall through to a later
|
|
60
|
-
// provider, that would mislabel. Report the host, leave grouping to a lower rung.
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Explicit host declaration, checked *before* the detection table.
|
|
67
|
-
*
|
|
68
|
-
* Two callers, both of which detection cannot serve:
|
|
69
|
-
*
|
|
70
|
-
* - **WorkBuddy (ab-agent)** spawns this CLI itself. Sniffing our own process
|
|
71
|
-
* would turn a fact we already know into a guess — and a fragile one, since
|
|
72
|
-
* the generic variables it injects (CONVERSATION_ID …) are exactly the kind
|
|
73
|
-
* another host might also set. It declares `REMIXMATE_HOST=workbuddy`.
|
|
74
|
-
* - **Codex** does not expose anything: per the appendix-A survey it
|
|
75
|
-
* "官方明确不通过环境变量暴露". No `detect()` we could write would be honest.
|
|
76
|
-
* A user or the host itself can export `REMIXMATE_HOST=codex` and get the
|
|
77
|
-
* provenance label without waiting for a CLI release.
|
|
78
|
-
*
|
|
79
|
-
* Symmetric with `REMIXMATE_TAKE_KEY`: that one overrides *grouping*, this one
|
|
80
|
-
* overrides *identity*. They are independent — declaring a host does not group,
|
|
81
|
-
* and grouping does not imply a host.
|
|
82
|
-
*/
|
|
83
|
-
const HOST_OVERRIDE_VAR = 'REMIXMATE_HOST';
|
|
84
|
-
/**
|
|
85
|
-
* Normalise a declared host label.
|
|
86
|
-
*
|
|
87
|
-
* `video_project.client_host` is `varchar(32)`, and the value is rendered as a
|
|
88
|
-
* badge, so free-form input gets slugified rather than trusted: lowercase,
|
|
89
|
-
* non-alphanumerics collapsed to `-`, capped at 32. Anything that normalises to
|
|
90
|
-
* empty is treated as absent — an unusable label should degrade to "no badge",
|
|
91
|
-
* never to a garbage one.
|
|
92
|
-
*/
|
|
93
|
-
export function normaliseHostLabel(raw) {
|
|
94
|
-
return (raw ?? '')
|
|
95
|
-
.trim()
|
|
96
|
-
.toLowerCase()
|
|
97
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
98
|
-
.replace(/^-+|-+$/g, '')
|
|
99
|
-
.slice(0, 32);
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Host label for provenance, even when no session id could be read.
|
|
103
|
-
*
|
|
104
|
-
* Declaration wins over detection: the declaring party knows, the table guesses.
|
|
105
|
-
*/
|
|
106
|
-
export function detectHostLabel(env = process.env) {
|
|
107
|
-
const declared = normaliseHostLabel(env[HOST_OVERRIDE_VAR]);
|
|
108
|
-
if (declared)
|
|
109
|
-
return declared;
|
|
110
|
-
return HOST_PROVIDERS.find((p) => p.detect(env))?.host ?? '';
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Resolve the take grouping key.
|
|
114
|
-
*
|
|
115
|
-
* 1. a recognised host's root session id → "<host>:<id>"
|
|
116
|
-
* 2. REMIXMATE_TAKE_KEY → "custom:<value>"
|
|
117
|
-
* 3. the render job id → "job:<id>"
|
|
118
|
-
*
|
|
119
|
-
* Rung 2 is the escape hatch that matters: an unsupported host (or a user on
|
|
120
|
-
* Codex) can export one variable and get correct grouping without waiting for a
|
|
121
|
-
* CLI release. Rung 3 keeps the feature working everywhere, at the cost of
|
|
122
|
-
* splitting one video's iterations across takes — acceptable as a floor, not as
|
|
123
|
-
* a default.
|
|
124
|
-
*/
|
|
125
|
-
export function resolveTakeKey(jobId, env = process.env) {
|
|
126
|
-
const host = detectHostLabel(env);
|
|
127
|
-
const detected = detectHostSession(env);
|
|
128
|
-
if (detected) {
|
|
129
|
-
// key 的命名空间用**探测到的**宿主,不用声明值:它是既有分组的命名空间,
|
|
130
|
-
// 跟着声明变会把同一会话的 take 拆成两组。而 host(溯源标签)以声明为准。
|
|
131
|
-
return { key: `${detected.host}:${detected.sessionId}`, host, source: 'host' };
|
|
132
|
-
}
|
|
133
|
-
const override = (env.REMIXMATE_TAKE_KEY ?? '').trim();
|
|
134
|
-
if (override)
|
|
135
|
-
return { key: `custom:${override}`, host, source: 'env-override' };
|
|
136
|
-
if (jobId && jobId > 0)
|
|
137
|
-
return { key: `job:${jobId}`, host, source: 'render-job' };
|
|
138
|
-
return { key: '', host, source: 'none' };
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Environment variable names that look like a session/thread identifier.
|
|
142
|
-
* Used by `remixmate host probe` to onboard an unknown host in a minute.
|
|
143
|
-
* Returns NAMES ONLY — values may carry tokens and are never printed.
|
|
144
|
-
*/
|
|
145
|
-
export function probeSessionVarNames(env = process.env) {
|
|
146
|
-
const interesting = /(SESSION|THREAD|CONVERSATION|CHAT|TASK)/i;
|
|
147
|
-
const secretish = /(TOKEN|KEY|SECRET|AUTH|PASSWORD|COOKIE|CREDENTIAL)/i;
|
|
148
|
-
return Object.keys(env)
|
|
149
|
-
.filter((name) => interesting.test(name) && !secretish.test(name))
|
|
150
|
-
.sort();
|
|
151
|
-
}
|
|
1
|
+
var E=Object.defineProperty;var o=(e,t)=>E(e,"name",{value:t,configurable:!0});const c=[{host:"codebuddy",detect:o(e=>!!e.CODEBUDDY_SESSION_ID||e.CODEBUDDY_HOST!=null,"detect"),sessionVars:["CODEBUDDY_SESSION_ID"]},{host:"claude-code",detect:o(e=>e.CLAUDECODE==="1"||!!e.CLAUDE_CODE_SESSION_ID,"detect"),sessionVars:["CLAUDE_CODE_HOST_SESSION_ID","CLAUDE_CODE_SESSION_ID"]},{host:"qoder",detect:o(e=>!!e.QODER_SESSION_ID,"detect"),sessionVars:["QODER_SESSION_ID"]},{host:"trae",detect:o(e=>!!e.TRAE_SESSION_ID,"detect"),sessionVars:["TRAE_SESSION_ID"]}];function S(e=process.env){for(const t of c)if(t.detect(e)){for(const s of t.sessionVars){const r=(e[s]??"").trim();if(r)return{host:t.host,sessionId:r}}return null}return null}o(S,"detectHostSession");const i="REMIXMATE_HOST";function D(e){return(e??"").trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,32)}o(D,"normaliseHostLabel");function O(e=process.env){const t=D(e[i]);return t||(c.find(s=>s.detect(e))?.host??"")}o(O,"detectHostLabel");function u(e,t=process.env){const s=O(t),r=S(t);if(r)return{key:`${r.host}:${r.sessionId}`,host:s,source:"host"};const n=(t.REMIXMATE_TAKE_KEY??"").trim();return n?{key:`custom:${n}`,host:s,source:"env-override"}:e&&e>0?{key:`job:${e}`,host:s,source:"render-job"}:{key:"",host:s,source:"none"}}o(u,"resolveTakeKey");function I(e=process.env){const t=/(SESSION|THREAD|CONVERSATION|CHAT|TASK)/i,s=/(TOKEN|KEY|SECRET|AUTH|PASSWORD|COOKIE|CREDENTIAL)/i;return Object.keys(e).filter(r=>t.test(r)&&!s.test(r)).sort()}o(I,"probeSessionVarNames");export{c as HOST_PROVIDERS,O as detectHostLabel,S as detectHostSession,D as normaliseHostLabel,I as probeSessionVarNames,u as resolveTakeKey};
|
package/dist/project/resolve.js
CHANGED
|
@@ -1,65 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Project resolution ladder — which Project does this invocation's output
|
|
3
|
-
* belong to? First rung that matches wins.
|
|
4
|
-
*
|
|
5
|
-
* 0. CONVERSATION_ID already set → do nothing at all (cloud ab-agent path)
|
|
6
|
-
* 1. --project prj_xxx → explicit flag
|
|
7
|
-
* 2. REMIXMATE_PROJECT_ID → explicit env
|
|
8
|
-
* 3. ~/.config/remixmate/projects.json → `project use` binding (byPath → default)
|
|
9
|
-
* 4. the default project 「未分类」 → lazily created server-side
|
|
10
|
-
*
|
|
11
|
-
* Only "explicit" and "fallback" — there is deliberately no inference step in
|
|
12
|
-
* between. Deriving a project from the git remote reads well until you notice
|
|
13
|
-
* it *creates* one on every unfamiliar repo (`/project/create` is an upsert),
|
|
14
|
-
* and that its rules have to keep growing for private remotes, non-GitHub
|
|
15
|
-
* remotes, monorepos and repos with no origin — each misfire silently filing
|
|
16
|
-
* output under a project the user never made.
|
|
17
|
-
*
|
|
18
|
-
* See docs/product/cli/cli-project-binding-design.md §3.
|
|
19
|
-
*/
|
|
20
|
-
import { mmPost } from '../http.js';
|
|
21
|
-
import { readBinding } from './store.js';
|
|
22
|
-
/** POST /project/default — get-or-lazily-create 「未分类」. */
|
|
23
|
-
export async function fetchDefaultProject(ctx) {
|
|
24
|
-
return await mmPost(ctx, '/project/default', {});
|
|
25
|
-
}
|
|
26
|
-
/** POST /project/list — for `remixmate project list`. */
|
|
27
|
-
export async function listProjects(ctx) {
|
|
28
|
-
const data = await mmPost(ctx, '/project/list', {});
|
|
29
|
-
if (Array.isArray(data))
|
|
30
|
-
return data;
|
|
31
|
-
return data?.list ?? [];
|
|
32
|
-
}
|
|
33
|
-
export async function resolveProject(ctx, opts = {}) {
|
|
34
|
-
const env = opts.env ?? process.env;
|
|
35
|
-
const flag = (opts.flagProjectId ?? '').trim();
|
|
36
|
-
if (flag)
|
|
37
|
-
return { projectId: flag, source: 'flag' };
|
|
38
|
-
const fromEnv = (env.REMIXMATE_PROJECT_ID ?? '').trim();
|
|
39
|
-
if (fromEnv)
|
|
40
|
-
return { projectId: fromEnv, source: 'env' };
|
|
41
|
-
const bound = await readBinding(ctx.apiBaseUrl, opts.cwd);
|
|
42
|
-
if (bound) {
|
|
43
|
-
return {
|
|
44
|
-
projectId: bound.projectId,
|
|
45
|
-
source: bound.scope === 'path' ? 'binding-path' : 'binding-default',
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
const fallback = await fetchDefaultProject(ctx);
|
|
49
|
-
return { projectId: fallback.id, source: 'default-project' };
|
|
50
|
-
}
|
|
51
|
-
/** Human-readable label for `doctor` / `project show`. */
|
|
52
|
-
export function describeProjectSource(source) {
|
|
53
|
-
switch (source) {
|
|
54
|
-
case 'flag':
|
|
55
|
-
return '--project 参数';
|
|
56
|
-
case 'env':
|
|
57
|
-
return 'REMIXMATE_PROJECT_ID 环境变量';
|
|
58
|
-
case 'binding-path':
|
|
59
|
-
return `${process.cwd()} 的目录绑定`;
|
|
60
|
-
case 'binding-default':
|
|
61
|
-
return '全局默认绑定';
|
|
62
|
-
case 'default-project':
|
|
63
|
-
return '默认项目「未分类」';
|
|
64
|
-
}
|
|
65
|
-
}
|
|
1
|
+
var s=Object.defineProperty;var t=(r,e)=>s(r,"name",{value:e,configurable:!0});import{mmPost as a}from"../http.js";import{readBinding as u}from"./store.js";async function f(r){return await a(r,"/project/default",{})}t(f,"fetchDefaultProject");async function I(r){const e=await a(r,"/project/list",{});return Array.isArray(e)?e:e?.list??[]}t(I,"listProjects");async function g(r,e={}){const i=e.env??process.env,n=(e.flagProjectId??"").trim();if(n)return{projectId:n,source:"flag"};const o=(i.REMIXMATE_PROJECT_ID??"").trim();if(o)return{projectId:o,source:"env"};const c=await u(r.apiBaseUrl,e.cwd);return c?{projectId:c.projectId,source:c.scope==="path"?"binding-path":"binding-default"}:{projectId:(await f(r)).id,source:"default-project"}}t(g,"resolveProject");function m(r){switch(r){case"flag":return"--project 参数";case"env":return"REMIXMATE_PROJECT_ID 环境变量";case"binding-path":return`${process.cwd()} 的目录绑定`;case"binding-default":return"全局默认绑定";case"default-project":return"默认项目「未分类」"}}t(m,"describeProjectSource");export{m as describeProjectSource,f as fetchDefaultProject,I as listProjects,g as resolveProject};
|
package/dist/project/store.js
CHANGED
|
@@ -1,97 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* belong to?
|
|
4
|
-
*
|
|
5
|
-
* Stored at ~/.config/remixmate/projects.json, NOT in credentials.json: that
|
|
6
|
-
* file is a 0600 secret store keyed by API base URL, with different lifetime
|
|
7
|
-
* and different blast radius on corruption.
|
|
8
|
-
*
|
|
9
|
-
* Two layers, because host cwd semantics are not uniform:
|
|
10
|
-
* - `byPath` — keyed by absolute cwd. Hosts that run inside the user's real
|
|
11
|
-
* repo (Codex, Claude Code) get per-repo bindings.
|
|
12
|
-
* - `default` — a single fallback. Hosts that spawn a throwaway working
|
|
13
|
-
* directory per conversation (WorkBuddy uses
|
|
14
|
-
* ~/WorkBuddy/<timestamp>) would never hit a byPath entry, so a
|
|
15
|
-
* cwd-only design silently degrades to nothing for them.
|
|
16
|
-
*
|
|
17
|
-
* See docs/product/cli/cli-project-binding-design.md §3.
|
|
18
|
-
*/
|
|
19
|
-
import { homedir } from 'node:os';
|
|
20
|
-
import { dirname, join, resolve } from 'node:path';
|
|
21
|
-
import { promises as fs } from 'node:fs';
|
|
22
|
-
export const CONFIG_DIR = join(homedir(), '.config', 'remixmate');
|
|
23
|
-
export const PROJECTS_FILE = join(CONFIG_DIR, 'projects.json');
|
|
24
|
-
/**
|
|
25
|
-
* Must build a NEW object every call, not spread a shared constant: `backends`
|
|
26
|
-
* would then be one object aliased by every "empty" read, and writeBinding
|
|
27
|
-
* mutates it — so a later read of a missing/corrupt file would hand back
|
|
28
|
-
* bindings accumulated earlier in the process instead of nothing.
|
|
29
|
-
*/
|
|
30
|
-
function emptyFile() {
|
|
31
|
-
return { version: 1, backends: {} };
|
|
32
|
-
}
|
|
33
|
-
async function readFile() {
|
|
34
|
-
try {
|
|
35
|
-
const raw = await fs.readFile(PROJECTS_FILE, 'utf-8');
|
|
36
|
-
const parsed = JSON.parse(raw);
|
|
37
|
-
if (!parsed || typeof parsed !== 'object' || !parsed.backends)
|
|
38
|
-
return emptyFile();
|
|
39
|
-
return parsed;
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
// Missing or corrupt → behave as unbound. A broken binding file must never
|
|
43
|
-
// block a render; the resolution ladder just falls through to the default project.
|
|
44
|
-
return emptyFile();
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
async function writeFile(data) {
|
|
48
|
-
await fs.mkdir(dirname(PROJECTS_FILE), { recursive: true, mode: 0o700 });
|
|
49
|
-
await fs.writeFile(PROJECTS_FILE, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
|
|
50
|
-
}
|
|
51
|
-
/** Look up the binding for a cwd: exact byPath match first, then the default. */
|
|
52
|
-
export async function readBinding(apiBaseUrl, cwd = process.cwd()) {
|
|
53
|
-
const file = await readFile();
|
|
54
|
-
const entry = file.backends[apiBaseUrl];
|
|
55
|
-
if (!entry)
|
|
56
|
-
return null;
|
|
57
|
-
const byPath = entry.byPath?.[resolve(cwd)];
|
|
58
|
-
if (byPath)
|
|
59
|
-
return { projectId: byPath, scope: 'path' };
|
|
60
|
-
if (entry.default)
|
|
61
|
-
return { projectId: entry.default, scope: 'default' };
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
/** Bind a project to this cwd (`scope: 'path'`) or as the fallback (`'default'`). */
|
|
65
|
-
export async function writeBinding(apiBaseUrl, projectId, scope, cwd = process.cwd()) {
|
|
66
|
-
const file = await readFile();
|
|
67
|
-
const entry = file.backends[apiBaseUrl] ?? {};
|
|
68
|
-
if (scope === 'default') {
|
|
69
|
-
entry.default = projectId;
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
entry.byPath = { ...(entry.byPath ?? {}), [resolve(cwd)]: projectId };
|
|
73
|
-
}
|
|
74
|
-
file.backends[apiBaseUrl] = entry;
|
|
75
|
-
await writeFile(file);
|
|
76
|
-
}
|
|
77
|
-
/** Drop a binding. Returns true when something was actually removed. */
|
|
78
|
-
export async function clearBinding(apiBaseUrl, scope, cwd = process.cwd()) {
|
|
79
|
-
const file = await readFile();
|
|
80
|
-
const entry = file.backends[apiBaseUrl];
|
|
81
|
-
if (!entry)
|
|
82
|
-
return false;
|
|
83
|
-
let removed = false;
|
|
84
|
-
if (scope === 'default') {
|
|
85
|
-
removed = entry.default != null;
|
|
86
|
-
delete entry.default;
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
const key = resolve(cwd);
|
|
90
|
-
removed = entry.byPath?.[key] != null;
|
|
91
|
-
if (entry.byPath)
|
|
92
|
-
delete entry.byPath[key];
|
|
93
|
-
}
|
|
94
|
-
if (removed)
|
|
95
|
-
await writeFile(file);
|
|
96
|
-
return removed;
|
|
97
|
-
}
|
|
1
|
+
var m=Object.defineProperty;var i=(n,e)=>m(n,"name",{value:e,configurable:!0});import{homedir as w}from"node:os";import{dirname as b,join as u,resolve as s}from"node:path";import{promises as c}from"node:fs";const h=u(w(),".config","remixmate"),f=u(h,"projects.json");function p(){return{version:1,backends:{}}}i(p,"emptyFile");async function l(){try{const n=await c.readFile(f,"utf-8"),e=JSON.parse(n);return!e||typeof e!="object"||!e.backends?p():e}catch{return p()}}i(l,"readFile");async function y(n){await c.mkdir(b(f),{recursive:!0,mode:448}),await c.writeFile(f,JSON.stringify(n,null,2)+`
|
|
2
|
+
`,{mode:384})}i(y,"writeFile");async function g(n,e=process.cwd()){const r=(await l()).backends[n];if(!r)return null;const t=r.byPath?.[s(e)];return t?{projectId:t,scope:"path"}:r.default?{projectId:r.default,scope:"default"}:null}i(g,"readBinding");async function j(n,e,o,r=process.cwd()){const t=await l(),a=t.backends[n]??{};o==="default"?a.default=e:a.byPath={...a.byPath??{},[s(r)]:e},t.backends[n]=a,await y(t)}i(j,"writeBinding");async function v(n,e,o=process.cwd()){const r=await l(),t=r.backends[n];if(!t)return!1;let a=!1;if(e==="default")a=t.default!=null,delete t.default;else{const d=s(o);a=t.byPath?.[d]!=null,t.byPath&&delete t.byPath[d]}return a&&await y(r),a}i(v,"clearBinding");export{h as CONFIG_DIR,f as PROJECTS_FILE,v as clearBinding,g as readBinding,j as writeBinding};
|
package/dist/project/take.js
CHANGED
|
@@ -1,106 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Take creation — turn a render invocation into a VideoProject the web app can
|
|
3
|
-
* show, and hand its conversation id down to the skill.
|
|
4
|
-
*
|
|
5
|
-
* Why this exists at all: ab-web derives a video's project from
|
|
6
|
-
* `file.conversation_id → media_video_project → media_project`. A render whose
|
|
7
|
-
* upload carries no conversation id is stored correctly but belongs to nothing,
|
|
8
|
-
* so it shows up only in the raw asset list. The cloud agent injects
|
|
9
|
-
* CONVERSATION_ID for its own runs; locally, nobody did — that is the bug this
|
|
10
|
-
* module fixes.
|
|
11
|
-
*
|
|
12
|
-
* Idempotency lives on the server (`/video-project/ensure` is get-or-create on
|
|
13
|
-
* `(user_id, client_session_key)`), not in a local state file: a pipeline is N
|
|
14
|
-
* separate CLI processes, sometimes concurrent, and a local cache would have to
|
|
15
|
-
* solve staleness and write races that a unique index solves for free.
|
|
16
|
-
*
|
|
17
|
-
* See docs/product/cli/cli-project-binding-design.md §5.
|
|
18
|
-
*/
|
|
19
|
-
import { mmPost } from '../http.js';
|
|
20
|
-
import { resolveTakeKey } from './host.js';
|
|
21
|
-
import { resolveProject } from './resolve.js';
|
|
22
|
-
/**
|
|
23
|
-
* Pull title / aspectRatio / templateId off the persisted render job.
|
|
24
|
-
*
|
|
25
|
-
* The VideoProject row needs them at creation time, but this runs in Node
|
|
26
|
-
* before the Python renderer is spawned, so the DSL itself isn't in reach —
|
|
27
|
-
* `render_jobs.dsl_meta` is the summary of exactly those fields. Best-effort:
|
|
28
|
-
* a take with a placeholder title beats no take at all.
|
|
29
|
-
*/
|
|
30
|
-
async function fetchJobMeta(ctx, jobId) {
|
|
31
|
-
try {
|
|
32
|
-
const detail = await mmPost(ctx, '/renderJob/get', { jobId });
|
|
33
|
-
return detail?.dslMeta ?? {};
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return {};
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Look up the take for the current grouping key without creating anything.
|
|
41
|
-
*
|
|
42
|
-
* Used by pipeline skills that should attach to a take when one exists but must
|
|
43
|
-
* never bring one into being: someone running `remixmate gen-image` on its own
|
|
44
|
-
* isn't making a video, and an empty VideoProject is worse clutter than an
|
|
45
|
-
* unattributed image.
|
|
46
|
-
*
|
|
47
|
-
* Deliberately does NOT resolve a project — resolveProject()'s last rung
|
|
48
|
-
* lazily *creates* 「未分类」, which a read-only lookup has no business doing.
|
|
49
|
-
*/
|
|
50
|
-
export async function findTake(ctx) {
|
|
51
|
-
const takeKey = resolveTakeKey(undefined);
|
|
52
|
-
if (!takeKey.key)
|
|
53
|
-
return null;
|
|
54
|
-
const res = await mmPost(ctx, '/video-project/find-by-session', {
|
|
55
|
-
clientSessionKey: takeKey.key,
|
|
56
|
-
});
|
|
57
|
-
if (!res?.conversationId)
|
|
58
|
-
return null;
|
|
59
|
-
return {
|
|
60
|
-
conversationId: res.conversationId,
|
|
61
|
-
videoProjectId: res.id,
|
|
62
|
-
projectId: res.projectId ?? '',
|
|
63
|
-
created: false,
|
|
64
|
-
attempt: res.attempt ?? 0,
|
|
65
|
-
sessionKey: takeKey.key,
|
|
66
|
-
host: takeKey.host,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Resolve project + grouping key, then get-or-create the take.
|
|
71
|
-
* Returns null when this invocation shouldn't own a take.
|
|
72
|
-
*/
|
|
73
|
-
export async function ensureTake(ctx, opts) {
|
|
74
|
-
const jobId = opts.jobId && opts.jobId > 0 ? opts.jobId : undefined;
|
|
75
|
-
const takeKey = resolveTakeKey(jobId);
|
|
76
|
-
// No grouping key at all → don't create. Only reachable for a take-creating
|
|
77
|
-
// skill that has no jobId (prepare-video-assets) on a host that exposes no
|
|
78
|
-
// session id. Creating here would produce an ungrouped take that the later
|
|
79
|
-
// render-video call — which *does* have a jobId, hence the `job:N` key —
|
|
80
|
-
// could not join, leaving two takes for one video.
|
|
81
|
-
if (!takeKey.key)
|
|
82
|
-
return null;
|
|
83
|
-
const { projectId } = await resolveProject(ctx, { flagProjectId: opts.flagProjectId });
|
|
84
|
-
const meta = jobId ? await fetchJobMeta(ctx, jobId) : {};
|
|
85
|
-
const res = await mmPost(ctx, '/video-project/ensure', {
|
|
86
|
-
projectId,
|
|
87
|
-
clientSessionKey: takeKey.key,
|
|
88
|
-
clientHost: takeKey.host,
|
|
89
|
-
jobId: jobId ?? 0,
|
|
90
|
-
title: meta?.title ?? '',
|
|
91
|
-
archetype: meta?.archetype ?? '',
|
|
92
|
-
aspectRatio: meta?.aspectRatio ?? '',
|
|
93
|
-
templateId: meta?.templateId ?? '',
|
|
94
|
-
});
|
|
95
|
-
if (!res?.conversationId)
|
|
96
|
-
return null;
|
|
97
|
-
return {
|
|
98
|
-
conversationId: res.conversationId,
|
|
99
|
-
videoProjectId: res.id,
|
|
100
|
-
projectId,
|
|
101
|
-
created: res.created,
|
|
102
|
-
attempt: res.attempt,
|
|
103
|
-
sessionKey: takeKey.key,
|
|
104
|
-
host: takeKey.host,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
1
|
+
var l=Object.defineProperty;var i=(o,e)=>l(o,"name",{value:e,configurable:!0});import{mmPost as s}from"../http.js";import{resolveTakeKey as d}from"./host.js";import{resolveProject as y}from"./resolve.js";async function I(o,e){try{return(await s(o,"/renderJob/get",{jobId:e}))?.dslMeta??{}}catch{return{}}}i(I,"fetchJobMeta");async function m(o){const e=d(void 0);if(!e.key)return null;const t=await s(o,"/video-project/find-by-session",{clientSessionKey:e.key});return t?.conversationId?{conversationId:t.conversationId,videoProjectId:t.id,projectId:t.projectId??"",created:!1,attempt:t.attempt??0,sessionKey:e.key,host:e.host}:null}i(m,"findTake");async function v(o,e){const t=e.jobId&&e.jobId>0?e.jobId:void 0,n=d(t);if(!n.key)return null;const{projectId:c}=await y(o,{flagProjectId:e.flagProjectId}),a=t?await I(o,t):{},r=await s(o,"/video-project/ensure",{projectId:c,clientSessionKey:n.key,clientHost:n.host,jobId:t??0,title:a?.title??"",archetype:a?.archetype??"",aspectRatio:a?.aspectRatio??"",templateId:a?.templateId??""});return r?.conversationId?{conversationId:r.conversationId,videoProjectId:r.id,projectId:c,created:r.created,attempt:r.attempt,sessionKey:n.key,host:n.host}:null}i(v,"ensureTake");export{v as ensureTake,m as findTake};
|