@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.
Files changed (181) 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 -188
  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 +53 -13
  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-post/SKILL.md +23 -13
  61. package/skills/gen-post/skill.json +9 -10
  62. package/skills/gen-script/SKILL.md +23 -1
  63. package/skills/gen-script/scripts/gen_script.py +644 -551
  64. package/skills/gen-script/skill.json +12 -0
  65. package/skills/gen-script/version.json +1 -1
  66. package/skills/prepare-video-assets/scripts/prepare_video_assets.py +21 -21
  67. package/skills/render-video/scripts/_chrome_vendor.py +17 -17
  68. package/skills/render-video/scripts/_digital_human_segments.py +34 -34
  69. package/skills/render-video/scripts/_video_probe.py +33 -33
  70. package/skills/render-video/scripts/_vod_polling.py +13 -13
  71. package/skills/render-video/scripts/remote_renderer_client.py +42 -42
  72. package/skills/render-video/scripts/render_video.py +639 -639
  73. package/skills/render-video/scripts/required_props.py +37 -37
  74. package/skills/render-video/scripts/upload_video.py +35 -35
  75. package/skills/render-video/version.json +1 -1
  76. package/skills/template-registry/SKILL.md +16 -0
  77. package/skills/template-registry/scripts/countdown_spec.py +92 -92
  78. package/skills/template-registry/scripts/doc_extract.py +40 -40
  79. package/skills/template-registry/scripts/list_templates.py +303 -191
  80. package/skills/template-registry/scripts/log_diagnostics.py +1 -1
  81. package/skills/template-registry/scripts/match_template.py +87 -87
  82. package/skills/template-registry/scripts/payload_hydrator.py +89 -89
  83. package/skills/template-registry/scripts/registry_loader.py +159 -159
  84. package/skills/template-registry/scripts/render_job_client.py +75 -75
  85. package/skills/template-registry/scripts/showcase_loader.py +44 -44
  86. package/skills/template-registry/skill.json +4 -1
  87. package/skills/template-registry/version.json +1 -1
  88. package/skills/template-registry/video_dsl/runtime/__init__.py +8 -8
  89. package/skills/template-registry/video_dsl/runtime/dsl_validator.py +329 -329
  90. package/skills/template-registry/video_dsl/runtime/narration_budget.py +36 -36
  91. package/skills/template-registry/video_dsl/runtime/payload_contract.py +7 -7
  92. package/skills/template-registry/video_dsl/runtime/prompt_enhancer.py +98 -98
  93. package/skills/template-registry/video_dsl/runtime/timeline_compiler.py +127 -127
  94. package/skills/template-registry/video_dsl/schema/video-dsl-v1alpha1.json +14 -1
  95. package/skills/video-parser/scripts/analyze_video.py +173 -173
  96. package/skills/video-parser/scripts/deconstruct_video.py +91 -91
  97. package/skills/video-parser/scripts/parse_via_render.py +21 -21
  98. package/skills/web-screenshot/scripts/_media_screenshot/__init__.py +4 -4
  99. package/skills/web-screenshot/scripts/_media_screenshot/bootstrap.py +7 -7
  100. package/skills/web-screenshot/scripts/_media_screenshot/browser.py +4 -4
  101. package/skills/web-screenshot/scripts/_media_screenshot/cli_args.py +4 -4
  102. package/skills/web-screenshot/scripts/_media_screenshot/js_loader.py +2 -2
  103. package/skills/web-screenshot/scripts/_media_screenshot/overlay.py +1 -1
  104. package/skills/web-screenshot/scripts/_media_screenshot/reader.py +40 -40
  105. package/skills/web-screenshot/scripts/_media_screenshot/recording.py +11 -11
  106. package/skills/web-screenshot/scripts/_media_screenshot/scenes/__init__.py +8 -8
  107. package/skills/web-screenshot/scripts/_media_screenshot/scenes/arrow.py +2 -2
  108. package/skills/web-screenshot/scripts/_media_screenshot/scenes/caption.py +1 -1
  109. package/skills/web-screenshot/scripts/_media_screenshot/scenes/click.py +1 -1
  110. package/skills/web-screenshot/scripts/_media_screenshot/scenes/code_lines.py +5 -5
  111. package/skills/web-screenshot/scripts/_media_screenshot/scenes/focus.py +1 -1
  112. package/skills/web-screenshot/scripts/_media_screenshot/scenes/highlight.py +3 -3
  113. package/skills/web-screenshot/scripts/_media_screenshot/scenes/hover.py +1 -1
  114. package/skills/web-screenshot/scripts/_media_screenshot/scenes/redact.py +1 -1
  115. package/skills/web-screenshot/scripts/_media_screenshot/scenes/scroll.py +1 -1
  116. package/skills/web-screenshot/scripts/_media_screenshot/scenes/sequence.py +1 -1
  117. package/skills/web-screenshot/scripts/_media_screenshot/scenes/title_card.py +1 -1
  118. package/skills/web-screenshot/scripts/_media_screenshot/scenes/type.py +1 -1
  119. package/skills/web-screenshot/scripts/_media_screenshot/scenes/wait.py +1 -1
  120. package/skills/web-screenshot/scripts/_media_screenshot/scenes/zoom.py +2 -2
  121. package/skills/web-screenshot/scripts/_media_screenshot/screenshot.py +15 -15
  122. package/skills/web-screenshot/scripts/_media_screenshot/storyboard.py +2 -2
  123. package/skills/web-screenshot/scripts/_media_screenshot/template.py +11 -11
  124. package/skills/web-screenshot/scripts/_media_screenshot/trim.py +10 -10
  125. package/skills/web-screenshot/scripts/_media_screenshot/urlguard.py +22 -22
  126. package/skills/web-screenshot/scripts/_media_upload.py +33 -33
  127. package/skills/web-screenshot/scripts/read_page.py +15 -15
  128. package/skills/web-screenshot/scripts/record.py +26 -26
  129. package/skills/web-screenshot/scripts/screenshot.py +5 -5
  130. package/dist/argv.d.ts +0 -15
  131. package/dist/auth/auth-lock.d.ts +0 -26
  132. package/dist/auth/auto-login.d.ts +0 -20
  133. package/dist/auth/commands.d.ts +0 -8
  134. package/dist/auth/credential-store.d.ts +0 -60
  135. package/dist/auth/device-flow-runner.d.ts +0 -58
  136. package/dist/auth/device-flow.d.ts +0 -52
  137. package/dist/auth/ensure.d.ts +0 -55
  138. package/dist/auth/environment.d.ts +0 -38
  139. package/dist/auth/pending-store.d.ts +0 -33
  140. package/dist/auth/resolve.d.ts +0 -30
  141. package/dist/billing.d.ts +0 -44
  142. package/dist/capabilities.d.ts +0 -63
  143. package/dist/cli.d.ts +0 -13
  144. package/dist/diagnostics.d.ts +0 -24
  145. package/dist/doctor.d.ts +0 -22
  146. package/dist/errors.d.ts +0 -29
  147. package/dist/exec.d.ts +0 -18
  148. package/dist/handlers/gen-digital-human.d.ts +0 -17
  149. package/dist/handlers/gen-image.d.ts +0 -20
  150. package/dist/handlers/gen-post.d.ts +0 -21
  151. package/dist/handlers/gen-video.d.ts +0 -19
  152. package/dist/handlers/gen-voice.d.ts +0 -26
  153. package/dist/handlers/index.d.ts +0 -20
  154. package/dist/handlers/shared.d.ts +0 -26
  155. package/dist/handlers/template-init.d.ts +0 -10
  156. package/dist/http.d.ts +0 -72
  157. package/dist/index.d.ts +0 -9
  158. package/dist/install.d.ts +0 -71
  159. package/dist/progress.d.ts +0 -23
  160. package/dist/project/commands.d.ts +0 -19
  161. package/dist/project/host.d.ts +0 -91
  162. package/dist/project/resolve.d.ts +0 -43
  163. package/dist/project/store.d.ts +0 -31
  164. package/dist/project/take.d.ts +0 -50
  165. package/dist/project/trace.d.ts +0 -98
  166. package/dist/registry.d.ts +0 -30
  167. package/dist/runner.d.ts +0 -39
  168. package/dist/skill-schema.d.ts +0 -138
  169. package/dist/template/args.d.ts +0 -25
  170. package/dist/template/check.d.ts +0 -23
  171. package/dist/template/commands.d.ts +0 -25
  172. package/dist/template/constants.d.ts +0 -39
  173. package/dist/template/init.d.ts +0 -28
  174. package/dist/template/list.d.ts +0 -23
  175. package/dist/template/preview.d.ts +0 -60
  176. package/dist/template/proc.d.ts +0 -26
  177. package/dist/template/project.d.ts +0 -47
  178. package/dist/template/publish.d.ts +0 -43
  179. package/dist/template/render-client.d.ts +0 -39
  180. package/dist/template/upgrade.d.ts +0 -44
  181. package/dist/text.d.ts +0 -8
@@ -1,185 +1,7 @@
1
- /**
2
- * gen-video handler — POST ab-api /model/genVideo, then poll /model/getVideoStatus.
3
- *
4
- * Thin forwarder: the model roster, friendly aliases, defaults and per-model
5
- * constraints (aspect ratios / resolutions / durations / reference-image caps)
6
- * come from ab-api's capabilities catalog (see capabilities.ts), not from this
7
- * package. Pre-flight validation here is purely data-driven from that catalog —
8
- * the CLI carries no model matrix — and ab-api re-validates authoritatively.
9
- *
10
- * --model accepts a friendly alias or a full id; ab-api also resolves aliases
11
- * server-side, so an unknown value still passes through to the backend.
12
- *
13
- * Local image paths for first/last/reference frames are read and base64-encoded
14
- * into a data: URI; https / data: URIs pass through unchanged. Reference videos
15
- * and audio are the exception — they must already be public URLs, see
16
- * `requirePublicUrls`.
17
- */
18
- import { mmPost, pollUntil, resolveHttpContext, SkillError } from '../http.js';
19
- import { getCapabilities, matchModel, defaultModel } from '../capabilities.js';
20
- import { emitProgress } from '../progress.js';
21
- import { isTrue, resolveImageInput, toNumber, toStringArray } from './shared.js';
22
- /** Generic, catalog-driven validation. No model names live here. */
23
- function validateAgainstConstraints(descriptor, duration, ratio, resolution, refs) {
24
- const c = descriptor.constraints;
25
- const label = descriptor.label || descriptor.id;
26
- if (c.durations && c.durations.length > 0) {
27
- if (!c.durations.includes(duration)) {
28
- throw new SkillError(`❌ ${label} duration must be one of: ${c.durations.join(', ')} s; got: ${duration}`);
29
- }
30
- }
31
- else if (c.durationMin !== undefined && c.durationMax !== undefined) {
32
- if (duration < c.durationMin || duration > c.durationMax) {
33
- throw new SkillError(`❌ ${label} duration must be ${c.durationMin}–${c.durationMax} s, got: ${duration}`);
34
- }
35
- }
36
- if (c.aspectRatios && c.aspectRatios.length > 0 && !c.aspectRatios.includes(ratio)) {
37
- throw new SkillError(`❌ ${label} aspect ratio must be one of: ${c.aspectRatios.join(', ')}; got: ${ratio}`);
38
- }
39
- if (c.resolutions && c.resolutions.length > 0 && !c.resolutions.includes(resolution)) {
40
- throw new SkillError(`❌ ${label} resolution must be one of: ${c.resolutions.join(', ')}; got: ${resolution}`);
41
- }
42
- if (c.refImageMax !== undefined && refs.images > c.refImageMax) {
43
- if (c.refImageMax === 0) {
44
- throw new SkillError(`❌ ${label} does not support a reference-image array; use --first-frame / --last-frame instead`);
45
- }
46
- throw new SkillError(`❌ ${label} accepts at most ${c.refImageMax} reference images; got: ${refs.images}`);
47
- }
48
- if (c.refVideoMax !== undefined && refs.videos > c.refVideoMax) {
49
- throw new SkillError(`❌ ${label} accepts at most ${c.refVideoMax} reference videos; got: ${refs.videos}`);
50
- }
51
- if (c.refAudioMax !== undefined && refs.audios > c.refAudioMax) {
52
- throw new SkillError(`❌ ${label} accepts at most ${c.refAudioMax} reference audio clips; got: ${refs.audios}`);
53
- }
54
- }
55
- /**
56
- * Reference videos / audio must be public https URLs.
57
- *
58
- * Unlike images, they are NOT inlined as data URIs: the caps are 50 MB per video
59
- * and 15 MB per clip, and base64 inflates that by a third before it ever reaches
60
- * the gateway. Failing here with the offending path beats a truncated upload or
61
- * an opaque 413 three hops downstream.
62
- */
63
- function requirePublicUrls(values, flag) {
64
- return values.map((value) => {
65
- const url = value.trim();
66
- if (!/^https?:\/\//i.test(url)) {
67
- throw new SkillError(`❌ ${flag} needs a public http(s) URL (local files are not uploaded), received: ${value}`);
68
- }
69
- return url;
70
- });
71
- }
72
- async function pollVideoStatus(ctx, taskId) {
73
- return pollUntil(async () => {
74
- const data = await mmPost(ctx, '/model/getVideoStatus', { taskId }, { timeoutMs: 30_000 });
75
- const status = data.status ?? 'unknown';
76
- emitProgress({ phase: 'gen-video:poll', taskId, status });
77
- if (status === 'succeeded' || status === 'completed') {
78
- return data.videoUrl ?? '';
79
- }
80
- if (status === 'failed') {
81
- const err = data.errorMsg ?? data.error ?? data.message ?? 'unknown error';
82
- throw new SkillError(`❌ video generation failed: ${err}`);
83
- }
84
- return null;
85
- }, { intervalMs: 10_000, timeoutMs: 300_000 });
86
- }
87
- export async function genVideo(input, ctxIn) {
88
- const apiBaseUrl = input.api_base_url;
89
- const caps = await getCapabilities(apiBaseUrl);
90
- // Resolve the model against the catalog: explicit alias/id, env override, or default.
91
- const requested = input.model ?? process.env.MM_VIDEO_MODEL;
92
- const descriptor = requested ? matchModel(caps.video, requested) : defaultModel(caps.video);
93
- const model = descriptor?.id ?? requested ?? defaultModel(caps.video)?.id ?? '';
94
- const firstFrame = input.first_frame ? resolveImageInput(String(input.first_frame)) : '';
95
- const lastFrame = input.last_frame ? resolveImageInput(String(input.last_frame)) : '';
96
- const references = toStringArray(input.reference).map(resolveImageInput).filter(Boolean);
97
- const referenceVideos = requirePublicUrls(toStringArray(input.reference_video).filter(Boolean), '--reference-video');
98
- const referenceAudios = requirePublicUrls(toStringArray(input.reference_audio).filter(Boolean), '--reference-audio');
99
- const prompt = (input.prompt ?? '').trim();
100
- const hasVisual = Boolean(firstFrame || lastFrame || references.length > 0 || referenceVideos.length > 0);
101
- if (!prompt && !hasVisual) {
102
- throw new SkillError('❌ Provide at least one of: a non-empty --prompt, or --first-frame / --last-frame / --reference / --reference-video');
103
- }
104
- /**
105
- * Two shapes the backend rejects outright; catching them here saves a round
106
- * trip and names the flag that caused it.
107
- *
108
- * - first/last frame pins exact frames, omni-modal reference lets the model
109
- * re-cut — the API has no meaning for both at once;
110
- * - audio-only (or text + audio) input is not a supported modality mix.
111
- */
112
- if ((firstFrame || lastFrame) && (referenceVideos.length > 0 || referenceAudios.length > 0)) {
113
- throw new SkillError('❌ --reference-video / --reference-audio cannot be combined with --first-frame / --last-frame; use --reference for images instead');
114
- }
115
- if (referenceAudios.length > 0 && references.length === 0 && referenceVideos.length === 0) {
116
- throw new SkillError('❌ --reference-audio needs at least one --reference image or --reference-video alongside it');
117
- }
118
- const ratio = input.ratio ?? '16:9';
119
- const resolution = input.resolution ?? '720p';
120
- const duration = toNumber(input.duration, 'duration') ?? descriptor?.constraints.durationDefault ?? 5;
121
- const seed = toNumber(input.seed, 'seed');
122
- // Pre-flight validation only when we recognize the model (have its constraints).
123
- if (descriptor) {
124
- validateAgainstConstraints(descriptor, duration, ratio, resolution, {
125
- images: references.length,
126
- videos: referenceVideos.length,
127
- audios: referenceAudios.length,
128
- });
129
- }
130
- const ctx = await resolveHttpContext(ctxIn.skillName, {
131
- apiBaseUrl,
132
- privateToken: input.priv_token,
133
- preflightToken: ctxIn.auth?.token,
134
- });
135
- const payload = {
136
- model,
137
- duration,
138
- ratio,
139
- resolution,
140
- generateAudio: isTrue(input.generate_audio),
141
- cameraFixed: isTrue(input.camera_fixed),
142
- };
143
- if (prompt)
144
- payload.prompt = prompt;
145
- if (firstFrame)
146
- payload.firstFrameImage = firstFrame;
147
- if (lastFrame)
148
- payload.lastFrameImage = lastFrame;
149
- if (references.length > 0)
150
- payload.referenceImages = references;
151
- if (referenceVideos.length > 0)
152
- payload.referenceVideos = referenceVideos;
153
- if (referenceAudios.length > 0)
154
- payload.referenceAudios = referenceAudios;
155
- // Web search only fires on text-only input; sending it alongside attachments
156
- // would be a knob the model silently ignores.
157
- if (isTrue(input.web_search) && !hasVisual)
158
- payload.webSearch = true;
159
- if (isTrue(input.return_last_frame))
160
- payload.returnLastFrame = true;
161
- if (input.negative_prompt && String(input.negative_prompt).trim()) {
162
- payload.negativePrompt = String(input.negative_prompt).trim();
163
- }
164
- if (seed !== undefined)
165
- payload.seed = seed;
166
- if (input.person_generation)
167
- payload.personGeneration = String(input.person_generation);
168
- emitProgress({ phase: 'gen-video:request', model, ratio, resolution, duration });
169
- const data = await mmPost(ctx, '/model/genVideo', payload, { timeoutMs: 120_000 });
170
- const taskId = data.taskId;
171
- if (!taskId) {
172
- throw new SkillError(`❌ could not parse the task id; API returned: ${JSON.stringify(data)}`);
173
- }
174
- emitProgress({ phase: 'gen-video:async', taskId });
175
- const videoUrl = await pollVideoStatus(ctx, taskId);
176
- if (!videoUrl) {
177
- throw new SkillError(`❌ video generated but no URL was returned, task id: ${taskId}`);
178
- }
179
- if (isTrue(input.json_output)) {
180
- process.stdout.write(JSON.stringify({ url: videoUrl }) + '\n');
181
- return;
182
- }
183
- process.stdout.write(`\n🔗 Video URL (renders directly, no download needed):\n${videoUrl}\n`);
184
- process.stdout.write(`\n🎉 Video generation done!\n`);
185
- }
1
+ var P=Object.defineProperty;var m=(e,o)=>P(e,"name",{value:o,configurable:!0});import{mmPost as A,pollUntil as B,resolveHttpContext as L,SkillError as r}from"../http.js";import{getCapabilities as j,matchModel as N,defaultModel as U}from"../capabilities.js";import{emitProgress as x}from"../progress.js";import{isTrue as u,resolveImageInput as b,toNumber as R,toStringArray as V}from"./shared.js";function O(e,o,n,i,a){const t=e.constraints,d=e.label||e.id;if(t.durations&&t.durations.length>0){if(!t.durations.includes(o))throw new r(`❌ ${d} duration must be one of: ${t.durations.join(", ")} s; got: ${o}`)}else if(t.durationMin!==void 0&&t.durationMax!==void 0&&(o<t.durationMin||o>t.durationMax))throw new r(`❌ ${d} duration must be ${t.durationMin}–${t.durationMax} s, got: ${o}`);if(t.aspectRatios&&t.aspectRatios.length>0&&!t.aspectRatios.includes(n))throw new r(`❌ ${d} aspect ratio must be one of: ${t.aspectRatios.join(", ")}; got: ${n}`);if(t.resolutions&&t.resolutions.length>0&&!t.resolutions.includes(i))throw new r(`❌ ${d} resolution must be one of: ${t.resolutions.join(", ")}; got: ${i}`);if(t.refImageMax!==void 0&&a.images>t.refImageMax)throw t.refImageMax===0?new r(`❌ ${d} does not support a reference-image array; use --first-frame / --last-frame instead`):new r(`❌ ${d} accepts at most ${t.refImageMax} reference images; got: ${a.images}`);if(t.refVideoMax!==void 0&&a.videos>t.refVideoMax)throw new r(`❌ ${d} accepts at most ${t.refVideoMax} reference videos; got: ${a.videos}`);if(t.refAudioMax!==void 0&&a.audios>t.refAudioMax)throw new r(`❌ ${d} accepts at most ${t.refAudioMax} reference audio clips; got: ${a.audios}`)}m(O,"validateAgainstConstraints");function F(e,o){return e.map(n=>{const i=n.trim();if(!/^https?:\/\//i.test(i))throw new r(`❌ ${o} needs a public http(s) URL (local files are not uploaded), received: ${n}`);return i})}m(F,"requirePublicUrls");async function q(e,o){return B(async()=>{const n=await A(e,"/model/getVideoStatus",{taskId:o},{timeoutMs:3e4}),i=n.status??"unknown";if(x({phase:"gen-video:poll",taskId:o,status:i}),i==="succeeded"||i==="completed")return n.videoUrl??"";if(i==="failed"){const a=n.errorMsg??n.error??n.message??"unknown error";throw new r(`❌ video generation failed: ${a}`)}return null},{intervalMs:1e4,timeoutMs:3e5})}m(q,"pollVideoStatus");async function G(e,o){const n=e.api_base_url,i=await j(n),a=e.model??process.env.MM_VIDEO_MODEL,t=a?N(i.video,a):U(i.video),d=t?.id??a??U(i.video)?.id??"",g=e.first_frame?b(String(e.first_frame)):"",w=e.last_frame?b(String(e.last_frame)):"",c=V(e.reference).map(b).filter(Boolean),f=F(V(e.reference_video).filter(Boolean),"--reference-video"),l=F(V(e.reference_audio).filter(Boolean),"--reference-audio"),p=(e.prompt??"").trim(),S=!!(g||w||c.length>0||f.length>0);if(!p&&!S)throw new r("❌ Provide at least one of: a non-empty --prompt, or --first-frame / --last-frame / --reference / --reference-video");if((g||w)&&(f.length>0||l.length>0))throw new r("❌ --reference-video / --reference-audio cannot be combined with --first-frame / --last-frame; use --reference for images instead");if(l.length>0&&c.length===0&&f.length===0)throw new r("❌ --reference-audio needs at least one --reference image or --reference-video alongside it");const v=e.ratio??"16:9",$=e.resolution??"720p",M=R(e.duration,"duration")??t?.constraints.durationDefault??5,k=R(e.seed,"seed");t&&O(t,M,v,$,{images:c.length,videos:f.length,audios:l.length});const y=await L(o.skillName,{apiBaseUrl:n,privateToken:e.priv_token,preflightToken:o.auth?.token}),s={model:d,duration:M,ratio:v,resolution:$,generateAudio:u(e.generate_audio),cameraFixed:u(e.camera_fixed)};p&&(s.prompt=p),g&&(s.firstFrameImage=g),w&&(s.lastFrameImage=w),c.length>0&&(s.referenceImages=c),f.length>0&&(s.referenceVideos=f),l.length>0&&(s.referenceAudios=l),u(e.web_search)&&!S&&(s.webSearch=!0),u(e.return_last_frame)&&(s.returnLastFrame=!0),e.negative_prompt&&String(e.negative_prompt).trim()&&(s.negativePrompt=String(e.negative_prompt).trim()),k!==void 0&&(s.seed=k),e.person_generation&&(s.personGeneration=String(e.person_generation)),x({phase:"gen-video:request",model:d,ratio:v,resolution:$,duration:M});const I=await A(y,"/model/genVideo",s,{timeoutMs:12e4}),h=I.taskId;if(!h)throw new r(`❌ could not parse the task id; API returned: ${JSON.stringify(I)}`);x({phase:"gen-video:async",taskId:h});const _=await q(y,h);if(!_)throw new r(`❌ video generated but no URL was returned, task id: ${h}`);if(u(e.json_output)){process.stdout.write(JSON.stringify({url:_})+`
2
+ `);return}process.stdout.write(`
3
+ 🔗 Video URL (renders directly, no download needed):
4
+ ${_}
5
+ `),process.stdout.write(`
6
+ 🎉 Video generation done!
7
+ `)}m(G,"genVideo");export{G as genVideo};
@@ -1,133 +1,19 @@
1
- /**
2
- * gen-voice handler — Minimax TTS via ab-api unified /tool/tts (provider=minimax).
3
- *
4
- * Operations:
5
- * --list-voices true → GET available voice IDs from /voice/page (live)
6
- * --list-voices true --local true → print the built-in language-tagged
7
- * fallback catalog (offline, no API call).
8
- * **CLI-only escape hatch for offline dev.**
9
- * default → synthesize text into an MP3 URL
10
- *
11
- * `local` is deliberately absent from `skills/gen-voice/skill.json`'s
12
- * `parameters`, which is what ab-agent turns into the model-facing tool schema.
13
- * A model asked to check a voice id would reach for `local: true`, get a
14
- * two-entry hardcoded list back, and conclude that a perfectly valid voice does
15
- * not exist — which is exactly what happened on the hosted web agent
16
- * ("本地回退列表中没有该音色"). The remote catalog is the only authority on
17
- * which voices exist; the local list is a developer convenience for working
18
- * without a token, and it is only reachable by typing the flag yourself.
19
- *
20
- * The previous Python script also offered `--download` to write the MP3 to
21
- * disk. That's intentionally dropped here: the agent flow never used it (the
22
- * audio URL is persisted in cloud) and removing local file I/O keeps the
23
- * handler stateless.
24
- */
25
- import { mmPost, resolveHttpContext, SkillError } from '../http.js';
26
- import { getCapabilities } from '../capabilities.js';
27
- import { emitProgress } from '../progress.js';
28
- import { isTrue, toNumber } from './shared.js';
29
- /**
30
- * Fallback voice catalog printed when `--list-voices --local` is passed.
31
- * Mirrors the language-keyed defaults that gen_script.py uses, kept in sync
32
- * by hand because the two callers live on different runtimes.
33
- *
34
- * This is **not** a list of available voices — it is the two ids the resolver
35
- * falls back to when nothing else is specified, and one of them is still a
36
- * placeholder. Never treat it as evidence about what the service offers.
37
- *
38
- * Promote to a JSON config once this grows beyond a handful of entries.
39
- */
40
- const LOCAL_VOICE_CATALOG = [
41
- { id: 'Chinese (Mandarin)_Male_Announcer', language: 'zh', name: 'Mandarin Male Announcer (legacy default)' },
42
- // TODO: replace once gen-voice service confirms an English default voice id.
43
- { id: 'english-male-friendly-01', language: 'en', name: 'Friendly Male (English, TBD)' },
44
- ];
45
- async function listVoicesRemote(ctx) {
46
- const data = await mmPost(ctx, '/voice/page', { current: 1, pageSize: 100 }, { timeoutMs: 30_000 });
47
- const voices = data.list ?? [];
48
- if (voices.length === 0) {
49
- process.stdout.write('⚠️ No voices available\n');
50
- return;
51
- }
52
- process.stdout.write(`\n${'Voice ID'.padEnd(30)} Name\n`);
53
- process.stdout.write(`${'-'.repeat(30)} ${'-'.repeat(20)}\n`);
54
- for (const v of voices) {
55
- process.stdout.write(`${(v.uniqId ?? '').padEnd(30)} ${v.name ?? ''}\n`);
56
- }
57
- process.stdout.write(`\n${voices.length} voice(s) available\n`);
58
- process.stdout.write(`💡 Use --voice-id <id> to select a voice for synthesis\n`);
59
- }
60
- function listVoicesLocal() {
61
- process.stderr.write('⚠️ --local prints a hardcoded 2-entry fallback list, NOT the available voices. ' +
62
- 'Drop --local to query the live catalog.\n');
63
- for (const v of LOCAL_VOICE_CATALOG) {
64
- process.stdout.write(`${v.id}\t${v.language}\t${v.name}\n`);
65
- }
66
- }
67
- export async function genVoice(input, ctxIn) {
68
- // ── --list-voices: catalog mode (no synthesis) ──────────────────────────
69
- if (isTrue(input.list_voices)) {
70
- if (isTrue(input.local))
71
- return listVoicesLocal();
72
- const ctx = await resolveHttpContext(ctxIn.skillName, {
73
- apiBaseUrl: input.api_base_url,
74
- privateToken: input.priv_token,
75
- preflightToken: ctxIn.auth?.token,
76
- });
77
- return listVoicesRemote(ctx);
78
- }
79
- // ── synthesize ──────────────────────────────────────────────────────────
80
- const text = input.text;
81
- if (!text || typeof text !== 'string' || !text.trim()) {
82
- throw new SkillError('❌ Please provide --text to specify the text to synthesize');
83
- }
84
- const apiBaseUrl = input.api_base_url;
85
- const voice = (await getCapabilities(apiBaseUrl)).voice;
86
- const voiceId = input.voice_id ?? voice.defaultVoiceId;
87
- const speed = toNumber(input.speed, 'speed') ?? 1.0;
88
- if (speed < voice.speedMin || speed > voice.speedMax) {
89
- throw new SkillError(`❌ speed must be in the range ${voice.speedMin}~${voice.speedMax}`);
90
- }
91
- // Language hint for mixed-script narration. Like `local`, this is deliberately
92
- // absent from skill.json's `parameters`: the service already applies a sane
93
- // default for every caller, so handing the model a 40-value enum only invites
94
- // it to guess. Reachable from the DSL (global/scene `narration.languageBoost`,
95
- // forwarded by render_video) and by typing the flag yourself. Omitted =
96
- // the service default.
97
- const languageBoost = input.language_boost?.trim() || undefined;
98
- const ctx = await resolveHttpContext(ctxIn.skillName, {
99
- apiBaseUrl,
100
- privateToken: input.priv_token,
101
- preflightToken: ctxIn.auth?.token,
102
- });
103
- emitProgress({ phase: 'gen-voice:request', voiceId, speed });
104
- const data = await mmPost(ctx, '/tool/tts', {
105
- provider: voice.provider,
106
- text,
107
- voiceSetting: { voiceId, speed },
108
- outputFormat: 'url',
109
- ...(languageBoost ? { languageBoost } : {}),
110
- }, { timeoutMs: 120_000 });
111
- const audioUrl = data.audio;
112
- if (!audioUrl) {
113
- throw new SkillError(`❌ API response did not contain an audio URL: ${JSON.stringify(data)}`);
114
- }
115
- if (isTrue(input.json_output)) {
116
- process.stdout.write(JSON.stringify({
117
- url: audioUrl,
118
- audio_length_ms: data.extraInfo?.audioLength ?? null,
119
- subtitles: (data.subtitles ?? []).map((s) => ({
120
- text: s.text ?? '',
121
- timeBegin: s.timeBegin ?? 0,
122
- timeEnd: s.timeEnd ?? 0,
123
- })),
124
- }) + '\n');
125
- return;
126
- }
127
- const lengthMs = data.extraInfo?.audioLength;
128
- if (typeof lengthMs === 'number') {
129
- process.stdout.write(`\n🕐 Audio length: ${(lengthMs / 1000).toFixed(1)} s\n`);
130
- }
131
- process.stdout.write(`\n🔗 Audio URL (plays directly, no download needed):\n${audioUrl}\n`);
132
- process.stdout.write(`\n🎉 TTS synthesis done!\n`);
133
- }
1
+ var w=Object.defineProperty;var l=(e,o)=>w(e,"name",{value:o,configurable:!0});import{mmPost as m,resolveHttpContext as v,SkillError as c}from"../http.js";import{getCapabilities as _}from"../capabilities.js";import{emitProgress as y}from"../progress.js";import{isTrue as u,toNumber as $}from"./shared.js";const b=[{id:"Chinese (Mandarin)_Male_Announcer",language:"zh",name:"Mandarin Male Announcer (legacy default)"},{id:"english-male-friendly-01",language:"en",name:"Friendly Male (English, TBD)"}];async function k(e){const t=(await m(e,"/voice/page",{current:1,pageSize:100},{timeoutMs:3e4})).list??[];if(t.length===0){process.stdout.write(`⚠️ No voices available
2
+ `);return}process.stdout.write(`
3
+ ${"Voice ID".padEnd(30)} Name
4
+ `),process.stdout.write(`${"-".repeat(30)} ${"-".repeat(20)}
5
+ `);for(const s of t)process.stdout.write(`${(s.uniqId??"").padEnd(30)} ${s.name??""}
6
+ `);process.stdout.write(`
7
+ ${t.length} voice(s) available
8
+ `),process.stdout.write(`💡 Use --voice-id <id> to select a voice for synthesis
9
+ `)}l(k,"listVoicesRemote");function x(){process.stderr.write(`⚠️ --local prints a hardcoded 2-entry fallback list, NOT the available voices. Drop --local to query the live catalog.
10
+ `);for(const e of b)process.stdout.write(`${e.id} ${e.language} ${e.name}
11
+ `)}l(x,"listVoicesLocal");async function E(e,o){if(u(e.list_voices)){if(u(e.local))return x();const a=await v(o.skillName,{apiBaseUrl:e.api_base_url,privateToken:e.priv_token,preflightToken:o.auth?.token});return k(a)}const t=e.text;if(!t||typeof t!="string"||!t.trim())throw new c("❌ Please provide --text to specify the text to synthesize");const s=e.api_base_url,i=(await _(s)).voice,p=e.voice_id??i.defaultVoiceId,r=$(e.speed,"speed")??1;if(r<i.speedMin||r>i.speedMax)throw new c(`❌ speed must be in the range ${i.speedMin}~${i.speedMax}`);const g=e.language_boost?.trim()||void 0,h=await v(o.skillName,{apiBaseUrl:s,privateToken:e.priv_token,preflightToken:o.auth?.token});y({phase:"gen-voice:request",voiceId:p,speed:r});const n=await m(h,"/tool/tts",{provider:i.provider,text:t,voiceSetting:{voiceId:p,speed:r},outputFormat:"url",...g?{languageBoost:g}:{}},{timeoutMs:12e4}),d=n.audio;if(!d)throw new c(`❌ API response did not contain an audio URL: ${JSON.stringify(n)}`);if(u(e.json_output)){process.stdout.write(JSON.stringify({url:d,audio_length_ms:n.extraInfo?.audioLength??null,subtitles:(n.subtitles??[]).map(a=>({text:a.text??"",timeBegin:a.timeBegin??0,timeEnd:a.timeEnd??0}))})+`
12
+ `);return}const f=n.extraInfo?.audioLength;typeof f=="number"&&process.stdout.write(`
13
+ 🕐 Audio length: ${(f/1e3).toFixed(1)} s
14
+ `),process.stdout.write(`
15
+ 🔗 Audio URL (plays directly, no download needed):
16
+ ${d}
17
+ `),process.stdout.write(`
18
+ 🎉 TTS synthesis done!
19
+ `)}l(E,"genVoice");export{E as genVoice};
@@ -1,20 +1 @@
1
- /**
2
- * Handler registry — maps `entry.handler` strings to their implementation.
3
- *
4
- * Only `entry.type` in ('http' | 'builtin') uses this registry; python
5
- * skills go through spawn() in runner.ts and bypass handlers entirely.
6
- */
7
- import { genImage } from './gen-image.js';
8
- import { genVoice } from './gen-voice.js';
9
- import { genVideo } from './gen-video.js';
10
- import { genDigitalHuman } from './gen-digital-human.js';
11
- import { templateInit } from './template-init.js';
12
- import { genPost } from './gen-post.js';
13
- export const HANDLERS = {
14
- 'gen-image': genImage,
15
- 'gen-voice': genVoice,
16
- 'gen-video': genVideo,
17
- 'gen-digital-human': genDigitalHuman,
18
- 'template-init': templateInit,
19
- 'gen-post': genPost,
20
- };
1
+ import{genImage as e}from"./gen-image.js";import{genVoice as o}from"./gen-voice.js";import{genVideo as i}from"./gen-video.js";import{genDigitalHuman as m}from"./gen-digital-human.js";import{templateInit as t}from"./template-init.js";import{genPost as n}from"./gen-post.js";const c={"gen-image":e,"gen-voice":o,"gen-video":i,"gen-digital-human":m,"template-init":t,"gen-post":n};export{c as HANDLERS};
@@ -1,66 +1 @@
1
- /**
2
- * Shared helpers for TS skill handlers.
3
- *
4
- * These were previously copy-pasted across gen-image / gen-video /
5
- * gen-digital-human / gen-voice. Centralizing them keeps argument coercion and
6
- * image-input resolution identical across every atomic generator.
7
- *
8
- * Lives under handlers/ (not src/ root) so the compiled `dist/handlers/shared.js`
9
- * travels with the rest of the handler bundle when ab-agent copies `dist/`.
10
- */
11
- import { readFileSync, existsSync, statSync } from 'node:fs';
12
- import path from 'node:path';
13
- import { SkillError } from '../http.js';
14
- const MIME_BY_EXT = {
15
- '.png': 'image/png',
16
- '.jpg': 'image/jpeg',
17
- '.jpeg': 'image/jpeg',
18
- '.webp': 'image/webp',
19
- '.gif': 'image/gif',
20
- };
21
- /** Truthy check tolerant of CLI string values (`--flag` → true, `--flag true` → 'true'). */
22
- export function isTrue(value) {
23
- return value === true || value === 'true';
24
- }
25
- /**
26
- * Coerce a flag value into a finite number, or `undefined` when absent/empty.
27
- * Throws SkillError on a non-numeric value so callers fail fast with a clear msg.
28
- */
29
- export function toNumber(value, name) {
30
- if (value === undefined || value === null || value === '')
31
- return undefined;
32
- const n = Number(value);
33
- if (!Number.isFinite(n))
34
- throw new SkillError(`❌ ${name} must be a number, received: ${value}`);
35
- return n;
36
- }
37
- /** Coerce a repeatable flag (string | string[] | undefined) into a string array. */
38
- export function toStringArray(value) {
39
- if (value === undefined || value === null)
40
- return [];
41
- if (Array.isArray(value))
42
- return value.map(String);
43
- return [String(value)];
44
- }
45
- /**
46
- * Resolve an image reference into a value the backend accepts:
47
- * - http(s) URLs and data: URIs pass through unchanged
48
- * - local file paths are read and base64-encoded into a `data:<mime>;base64,…` URI
49
- * Throws SkillError when a local path does not exist or is not a file.
50
- */
51
- export function resolveImageInput(pathOrUrl) {
52
- const trimmed = pathOrUrl.trim();
53
- if (!trimmed)
54
- return '';
55
- const lower = trimmed.toLowerCase();
56
- if (lower.startsWith('http://') || lower.startsWith('https://') || lower.startsWith('data:')) {
57
- return trimmed;
58
- }
59
- if (!existsSync(trimmed) || !statSync(trimmed).isFile()) {
60
- throw new SkillError(`❌ image path does not exist or is not a file: ${trimmed}`);
61
- }
62
- const ext = path.extname(trimmed).toLowerCase();
63
- const mime = MIME_BY_EXT[ext] ?? 'image/jpeg';
64
- const b64 = readFileSync(trimmed).toString('base64');
65
- return `data:${mime};base64,${b64}`;
66
- }
1
+ var p=Object.defineProperty;var i=(t,r)=>p(t,"name",{value:r,configurable:!0});import{readFileSync as a,existsSync as g,statSync as f}from"node:fs";import u from"node:path";import{SkillError as n}from"../http.js";const c={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".webp":"image/webp",".gif":"image/gif"};function x(t){return t===!0||t==="true"}i(x,"isTrue");function S(t,r){if(t==null||t==="")return;const e=Number(t);if(!Number.isFinite(e))throw new n(`❌ ${r} must be a number, received: ${t}`);return e}i(S,"toNumber");function y(t){return t==null?[]:Array.isArray(t)?t.map(String):[String(t)]}i(y,"toStringArray");function j(t){const r=t.trim();if(!r)return"";const e=r.toLowerCase();if(e.startsWith("http://")||e.startsWith("https://")||e.startsWith("data:"))return r;if(!g(r)||!f(r).isFile())throw new n(`❌ image path does not exist or is not a file: ${r}`);const o=u.extname(r).toLowerCase(),s=c[o]??"image/jpeg",m=a(r).toString("base64");return`data:${s};base64,${m}`}i(j,"resolveImageInput");export{x as isTrue,j as resolveImageInput,S as toNumber,y as toStringArray};
@@ -1,21 +1 @@
1
- /**
2
- * template-authoring skill → `remixmate template init`.
3
- *
4
- * Agent hosts call skills as `remixmate template-authoring --dir X [--force]`;
5
- * this handler is a thin adapter so both entry points share one implementation.
6
- * Purely local (auth: none): fetches the starter via npm, no ab-api calls.
7
- */
8
- import { EXIT, SkillError } from '../errors.js';
9
- import { runInit } from '../template/init.js';
10
- export async function templateInit(input, _ctx) {
11
- const dir = typeof input.dir === 'string' ? input.dir.trim() : '';
12
- if (!dir)
13
- throw new SkillError('❌ 缺少 --dir:模板工程的目标目录', EXIT.USAGE);
14
- await runInit({
15
- dir,
16
- force: input.force === true || input.force === 'true',
17
- json: input.json_output === true || input.json === true,
18
- skipInstall: input.skip_install === true,
19
- });
20
- }
21
- export default templateInit;
1
+ var i=Object.defineProperty;var e=(r,t)=>i(r,"name",{value:t,configurable:!0});import{EXIT as s,SkillError as f}from"../errors.js";import{runInit as l}from"../template/init.js";async function n(r,t){const o=typeof r.dir=="string"?r.dir.trim():"";if(!o)throw new f("❌ 缺少 --dir:模板工程的目标目录",s.USAGE);await l({dir:o,force:r.force===!0||r.force==="true",json:r.json_output===!0||r.json===!0,skipInstall:r.skip_install===!0})}e(n,"templateInit");var d=n;export{d as default,n as templateInit};
package/dist/http.js CHANGED
@@ -1,155 +1,3 @@
1
- /**
2
- * Shared HTTP helpers for ab-api endpoints.
3
- *
4
- * Centralizes the auth contract (X-Priv-Token + x-invoke-skill / x-invoke-agent /
5
- * x-conversation-id), business-code error handling (result.code != 0), and the
6
- * env var resolution order that every Python skill used to duplicate.
7
- *
8
- * Token resolution order (high → low), shared with the CLI auth commands via
9
- * src/auth/resolve.ts:
10
- * 1. opts.privateToken — caller override (maps to --token/--priv-token)
11
- * 2. process.env.PRIV_TOKEN — process env (CI / agent-embedded hosts)
12
- * 3. Credential_Store[apiBaseUrl] — written by `remixmate login`
13
- * (none) → throws SkillError — non-interactive failure
14
- *
15
- * Base URL resolution:
16
- * 1. opts.apiBaseUrl — caller override
17
- * 2. process.env.MM_API_BASE_URL — process env (point at local/staging here)
18
- * 3. process.env.MM_BACKEND_API_URL — ab-agent's convention (back-compat)
19
- * 4. https://api.remixmate.ai/api — production default (zero-config)
20
- */
21
- import { traceHeaders, cleanDiagnosticText } from './diagnostics.js';
22
- import { recordBilling } from './billing.js';
23
- import { resolvePrivToken, NOT_AUTHENTICATED_HINT } from './auth/resolve.js';
24
- import { attemptAutoLogin } from './auth/auto-login.js';
25
- import { EXIT, SkillError } from './errors.js';
26
- const DEFAULT_API_BASE_URL = 'https://api.remixmate.ai/api';
27
- // Re-exported so the many `import { SkillError } from '../http.js'` call sites
28
- // stay valid; the definition moved to errors.ts to break an import cycle with
29
- // the auth gateway (see errors.ts).
30
- export { SkillError, EXIT } from './errors.js';
31
- /**
32
- * Resolve the ab-api base URL the same way for authenticated and device-flow calls.
33
- *
34
- * `MM_BACKEND_API_URL` is honored because several Python skills already fall back
35
- * to it (registry_loader, render_job_client, _vod_polling). Since runner.ts now
36
- * injects the resolved base URL into those child processes, leaving it out here
37
- * would let a prod default silently override an ab-agent-injected backend.
38
- */
39
- export function resolveApiBaseUrl(flag) {
40
- const fromEnv = (process.env.MM_API_BASE_URL ?? '').trim() || (process.env.MM_BACKEND_API_URL ?? '').trim();
41
- return ((flag ?? '').trim() || fromEnv || DEFAULT_API_BASE_URL).replace(/\/+$/, '');
42
- }
43
- export async function resolveHttpContext(skillName, opts = {}) {
44
- const apiBaseUrl = resolveApiBaseUrl(opts.apiBaseUrl);
45
- // An explicit --token flag still wins over the preflight result.
46
- let resolved = await resolvePrivToken({
47
- flag: opts.privateToken ?? opts.preflightToken,
48
- apiBaseUrl,
49
- });
50
- // Only when flag/env/store are all empty do we try automatic browser auth.
51
- // attemptAutoLogin self-gates (cloud env / headless / disabled → returns null
52
- // immediately), so the env-injected path never reaches it (zero regression).
53
- if (!resolved) {
54
- resolved = await attemptAutoLogin(apiBaseUrl);
55
- }
56
- if (!resolved) {
57
- throw new SkillError(NOT_AUTHENTICATED_HINT, EXIT.NOT_AUTHENTICATED);
58
- }
59
- return {
60
- apiBaseUrl,
61
- privateToken: resolved.privToken,
62
- skillName,
63
- agentName: process.env.AGENT_NAME || undefined,
64
- conversationId: process.env.CONVERSATION_ID || undefined,
65
- };
66
- }
67
- function buildHeaders(ctx, extra) {
68
- const headers = {
69
- 'Content-Type': 'application/json',
70
- 'X-Priv-Token': ctx.privateToken,
71
- 'x-invoke-skill': ctx.skillName,
72
- };
73
- if (ctx.agentName)
74
- headers['x-invoke-agent'] = ctx.agentName;
75
- if (ctx.conversationId)
76
- headers['x-conversation-id'] = ctx.conversationId;
77
- return { ...headers, ...extra, ...traceHeaders() };
78
- }
79
- /**
80
- * POST JSON to ab-api and return the parsed business payload.
81
- * Throws SkillError on HTTP errors or business code != 0.
82
- */
83
- export async function mmPost(ctx, pathOrUrl, body, opts = {}) {
84
- const url = pathOrUrl.startsWith('http') ? pathOrUrl : `${ctx.apiBaseUrl}${pathOrUrl}`;
85
- const controller = new AbortController();
86
- const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? 120_000);
87
- let resp;
88
- try {
89
- resp = await fetch(url, {
90
- method: 'POST',
91
- headers: buildHeaders(ctx),
92
- body: JSON.stringify(body),
93
- signal: controller.signal,
94
- });
95
- }
96
- catch (err) {
97
- if (err instanceof Error && err.name === 'AbortError') {
98
- throw new SkillError(`❌ request timed out: ${url}`, EXIT.BACKEND_UNREACHABLE);
99
- }
100
- throw new SkillError(`❌ network error: ${err.message}`, EXIT.BACKEND_UNREACHABLE);
101
- }
102
- finally {
103
- clearTimeout(timeout);
104
- }
105
- const text = await resp.text();
106
- if (!resp.ok) {
107
- // 401/403 is not a generic API failure — it means the credential is missing,
108
- // expired or revoked. Surfacing it as such (with exit code 4) is what lets a
109
- // host offer to re-authorize instead of reporting an opaque backend error.
110
- if (resp.status === 401 || resp.status === 403) {
111
- throw new SkillError(`${NOT_AUTHENTICATED_HINT}\n (后端返回 HTTP ${resp.status})`, EXIT.NOT_AUTHENTICATED);
112
- }
113
- if (resp.status >= 500) {
114
- throw new SkillError(`❌ API request failed (HTTP ${resp.status}): [response omitted]`, EXIT.BACKEND_UNREACHABLE);
115
- }
116
- throw new SkillError(`❌ API request failed (HTTP ${resp.status}): [response omitted]`);
117
- }
118
- let parsed;
119
- try {
120
- parsed = JSON.parse(text);
121
- }
122
- catch {
123
- throw new SkillError(`❌ failed to parse response, body is not JSON: [response omitted]`);
124
- }
125
- // Before the code check: a charged response is always code=0 today, but a
126
- // partial-failure envelope that still billed must not lose its billing line.
127
- recordBilling(parsed.billing);
128
- if (parsed.code !== 0) {
129
- // ab-api reports auth failures in the envelope (HTTP 200 + code=401), so the
130
- // business-code path needs the same 401 → "re-authorize" mapping as above.
131
- if (parsed.code === 401 || parsed.code === 403) {
132
- throw new SkillError(`${NOT_AUTHENTICATED_HINT}\n (后端返回 code=${parsed.code}${parsed.msg ? `: ${parsed.msg}` : ''})`, EXIT.NOT_AUTHENTICATED);
133
- }
134
- throw new SkillError(`❌ API returned a business error: ${cleanDiagnosticText(parsed.msg ?? 'unknown error')} (code=${parsed.code})`);
135
- }
136
- return parsed.data ?? undefined;
137
- }
138
- /**
139
- * Generic polling helper. Invokes `probe()` immediately, then every
140
- * `intervalMs`, until it returns a non-null value or `timeoutMs` elapses.
141
- * Probing first (rather than sleeping first) avoids a wasted initial interval
142
- * when the backend job is already complete.
143
- */
144
- export async function pollUntil(probe, opts = {}) {
145
- const intervalMs = opts.intervalMs ?? 5_000;
146
- const timeoutMs = opts.timeoutMs ?? 120_000;
147
- const start = Date.now();
148
- while (Date.now() - start < timeoutMs) {
149
- const result = await probe();
150
- if (result !== null)
151
- return result;
152
- await new Promise((r) => setTimeout(r, intervalMs));
153
- }
154
- throw new SkillError(`❌ polling timed out (${Math.round(timeoutMs / 1000)}s)`);
155
- }
1
+ var f=Object.defineProperty;var a=(e,t)=>f(e,"name",{value:t,configurable:!0});import{traceHeaders as A,cleanDiagnosticText as w}from"./diagnostics.js";import{recordBilling as E}from"./billing.js";import{resolvePrivToken as N,NOT_AUTHENTICATED_HINT as d}from"./auth/resolve.js";import{attemptAutoLogin as v}from"./auth/auto-login.js";import{EXIT as l,SkillError as n}from"./errors.js";const _="https://api.remixmate.ai/api";import{SkillError as S,EXIT as b}from"./errors.js";function h(e){const t=(process.env.MM_API_BASE_URL??"").trim()||(process.env.MM_BACKEND_API_URL??"").trim();return((e??"").trim()||t||_).replace(/\/+$/,"")}a(h,"resolveApiBaseUrl");async function P(e,t={}){const o=h(t.apiBaseUrl);let i=await N({flag:t.privateToken??t.preflightToken,apiBaseUrl:o});if(i||(i=await v(o)),!i)throw new n(d,l.NOT_AUTHENTICATED);return{apiBaseUrl:o,privateToken:i.privToken,skillName:e,agentName:process.env.AGENT_NAME||void 0,conversationId:process.env.CONVERSATION_ID||void 0}}a(P,"resolveHttpContext");function g(e,t){const o={"Content-Type":"application/json","X-Priv-Token":e.privateToken,"x-invoke-skill":e.skillName};return e.agentName&&(o["x-invoke-agent"]=e.agentName),e.conversationId&&(o["x-conversation-id"]=e.conversationId),{...o,...t,...A()}}a(g,"buildHeaders");async function U(e,t,o,i={}){const m=t.startsWith("http")?t:`${e.apiBaseUrl}${t}`,c=new AbortController,T=setTimeout(()=>c.abort(),i.timeoutMs??12e4);let s;try{s=await fetch(m,{method:"POST",headers:g(e),body:JSON.stringify(o),signal:c.signal})}catch(u){throw u instanceof Error&&u.name==="AbortError"?new n(`❌ request timed out: ${m}`,l.BACKEND_UNREACHABLE):new n(`❌ network error: ${u.message}`,l.BACKEND_UNREACHABLE)}finally{clearTimeout(T)}const p=await s.text();if(!s.ok)throw s.status===401||s.status===403?new n(`${d}
2
+ (后端返回 HTTP ${s.status})`,l.NOT_AUTHENTICATED):s.status>=500?new n(`❌ API request failed (HTTP ${s.status}): [response omitted]`,l.BACKEND_UNREACHABLE):new n(`❌ API request failed (HTTP ${s.status}): [response omitted]`);let r;try{r=JSON.parse(p)}catch{throw new n("❌ failed to parse response, body is not JSON: [response omitted]")}if(E(r.billing),r.code!==0)throw r.code===401||r.code===403?new n(`${d}
3
+ (后端返回 code=${r.code}${r.msg?`: ${r.msg}`:""})`,l.NOT_AUTHENTICATED):new n(`❌ API returned a business error: ${w(r.msg??"unknown error")} (code=${r.code})`);return r.data??void 0}a(U,"mmPost");async function D(e,t={}){const o=t.intervalMs??5e3,i=t.timeoutMs??12e4,m=Date.now();for(;Date.now()-m<i;){const c=await e();if(c!==null)return c;await new Promise(T=>setTimeout(T,o))}throw new n(`❌ polling timed out (${Math.round(i/1e3)}s)`)}a(D,"pollUntil");export{b as EXIT,S as SkillError,U as mmPost,D as pollUntil,h as resolveApiBaseUrl,P as resolveHttpContext};
package/dist/index.js CHANGED
@@ -1,8 +1 @@
1
- /**
2
- * remixmate — public library exports.
3
- *
4
- * The CLI entrypoint lives in `cli.ts` (`bin: remixmate`). This file is the
5
- * library surface for type-sharing consumers; the published npm package
6
- * exposes it via `main: dist/index.js` / `types: dist/index.d.ts`.
7
- */
8
- export { loadSkills, findSkill, SKILLS_DIR } from './registry.js';
1
+ import{loadSkills as i,findSkill as o,SKILLS_DIR as d}from"./registry.js";export{d as SKILLS_DIR,o as findSkill,i as loadSkills};