@remixmate/cli 0.9.72 → 0.9.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/LICENSE +45 -21
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/dist/argv.js +1 -51
  5. package/dist/auth/auth-lock.js +1 -100
  6. package/dist/auth/auto-login.js +7 -75
  7. package/dist/auth/commands.js +40 -290
  8. package/dist/auth/credential-store.js +1 -143
  9. package/dist/auth/device-flow-runner.js +13 -110
  10. package/dist/auth/device-flow.js +1 -132
  11. package/dist/auth/ensure.js +1 -62
  12. package/dist/auth/environment.js +1 -69
  13. package/dist/auth/pending-store.js +1 -45
  14. package/dist/auth/resolve.js +1 -44
  15. package/dist/billing.js +3 -76
  16. package/dist/capabilities.js +1 -75
  17. package/dist/cli.js +39 -211
  18. package/dist/diagnostics.js +2 -70
  19. package/dist/doctor.js +9 -188
  20. package/dist/errors.js +1 -33
  21. package/dist/exec.js +4 -47
  22. package/dist/handlers/gen-digital-human.js +33 -264
  23. package/dist/handlers/gen-image.js +9 -113
  24. package/dist/handlers/gen-post.js +15 -192
  25. package/dist/handlers/gen-video.js +7 -185
  26. package/dist/handlers/gen-voice.js +19 -133
  27. package/dist/handlers/index.js +1 -20
  28. package/dist/handlers/shared.js +1 -66
  29. package/dist/handlers/template-init.js +1 -21
  30. package/dist/http.js +3 -155
  31. package/dist/index.js +1 -8
  32. package/dist/install.js +40 -448
  33. package/dist/manifest.json +44 -3
  34. package/dist/progress.js +2 -15
  35. package/dist/project/commands.js +34 -156
  36. package/dist/project/host.js +1 -151
  37. package/dist/project/resolve.js +1 -65
  38. package/dist/project/store.js +2 -97
  39. package/dist/project/take.js +1 -106
  40. package/dist/project/trace.js +5 -278
  41. package/dist/registry.js +3 -70
  42. package/dist/runner.js +7 -225
  43. package/dist/skill-schema.js +1 -194
  44. package/dist/template/args.js +1 -69
  45. package/dist/template/check.js +2 -30
  46. package/dist/template/commands.js +34 -163
  47. package/dist/template/constants.js +1 -46
  48. package/dist/template/init.js +5 -133
  49. package/dist/template/list.js +1 -46
  50. package/dist/template/preview.js +7 -318
  51. package/dist/template/proc.js +1 -43
  52. package/dist/template/project.js +6 -161
  53. package/dist/template/publish.js +5 -185
  54. package/dist/template/render-client.js +2 -79
  55. package/dist/template/upgrade.js +4 -123
  56. package/dist/text.js +1 -15
  57. package/package.json +2 -35
  58. package/skills/doc-parse/scripts/extract_via_render.py +32 -32
  59. package/skills/export-jianying/scripts/gen_jianying_draft.py +188 -188
  60. package/skills/gen-script/SKILL.md +23 -1
  61. package/skills/gen-script/scripts/gen_script.py +644 -551
  62. package/skills/gen-script/skill.json +12 -0
  63. package/skills/gen-script/version.json +1 -1
  64. package/skills/prepare-video-assets/scripts/prepare_video_assets.py +21 -21
  65. package/skills/render-video/scripts/_chrome_vendor.py +17 -17
  66. package/skills/render-video/scripts/_digital_human_segments.py +34 -34
  67. package/skills/render-video/scripts/_video_probe.py +33 -33
  68. package/skills/render-video/scripts/_vod_polling.py +13 -13
  69. package/skills/render-video/scripts/remote_renderer_client.py +42 -42
  70. package/skills/render-video/scripts/render_video.py +639 -639
  71. package/skills/render-video/scripts/required_props.py +37 -37
  72. package/skills/render-video/scripts/upload_video.py +35 -35
  73. package/skills/render-video/version.json +1 -1
  74. package/skills/template-registry/SKILL.md +16 -0
  75. package/skills/template-registry/scripts/countdown_spec.py +92 -92
  76. package/skills/template-registry/scripts/doc_extract.py +40 -40
  77. package/skills/template-registry/scripts/list_templates.py +303 -191
  78. package/skills/template-registry/scripts/log_diagnostics.py +1 -1
  79. package/skills/template-registry/scripts/match_template.py +87 -87
  80. package/skills/template-registry/scripts/payload_hydrator.py +89 -89
  81. package/skills/template-registry/scripts/registry_loader.py +159 -159
  82. package/skills/template-registry/scripts/render_job_client.py +75 -75
  83. package/skills/template-registry/scripts/showcase_loader.py +44 -44
  84. package/skills/template-registry/skill.json +4 -1
  85. package/skills/template-registry/version.json +1 -1
  86. package/skills/template-registry/video_dsl/runtime/__init__.py +8 -8
  87. package/skills/template-registry/video_dsl/runtime/dsl_validator.py +329 -329
  88. package/skills/template-registry/video_dsl/runtime/narration_budget.py +36 -36
  89. package/skills/template-registry/video_dsl/runtime/payload_contract.py +7 -7
  90. package/skills/template-registry/video_dsl/runtime/prompt_enhancer.py +98 -98
  91. package/skills/template-registry/video_dsl/runtime/timeline_compiler.py +127 -127
  92. package/skills/template-registry/video_dsl/schema/video-dsl-v1alpha1.json +14 -1
  93. package/skills/video-parser/scripts/analyze_video.py +173 -173
  94. package/skills/video-parser/scripts/deconstruct_video.py +91 -91
  95. package/skills/video-parser/scripts/parse_via_render.py +21 -21
  96. package/skills/web-screenshot/scripts/_media_screenshot/__init__.py +4 -4
  97. package/skills/web-screenshot/scripts/_media_screenshot/bootstrap.py +7 -7
  98. package/skills/web-screenshot/scripts/_media_screenshot/browser.py +4 -4
  99. package/skills/web-screenshot/scripts/_media_screenshot/cli_args.py +4 -4
  100. package/skills/web-screenshot/scripts/_media_screenshot/js_loader.py +2 -2
  101. package/skills/web-screenshot/scripts/_media_screenshot/overlay.py +1 -1
  102. package/skills/web-screenshot/scripts/_media_screenshot/reader.py +40 -40
  103. package/skills/web-screenshot/scripts/_media_screenshot/recording.py +11 -11
  104. package/skills/web-screenshot/scripts/_media_screenshot/scenes/__init__.py +8 -8
  105. package/skills/web-screenshot/scripts/_media_screenshot/scenes/arrow.py +2 -2
  106. package/skills/web-screenshot/scripts/_media_screenshot/scenes/caption.py +1 -1
  107. package/skills/web-screenshot/scripts/_media_screenshot/scenes/click.py +1 -1
  108. package/skills/web-screenshot/scripts/_media_screenshot/scenes/code_lines.py +5 -5
  109. package/skills/web-screenshot/scripts/_media_screenshot/scenes/focus.py +1 -1
  110. package/skills/web-screenshot/scripts/_media_screenshot/scenes/highlight.py +3 -3
  111. package/skills/web-screenshot/scripts/_media_screenshot/scenes/hover.py +1 -1
  112. package/skills/web-screenshot/scripts/_media_screenshot/scenes/redact.py +1 -1
  113. package/skills/web-screenshot/scripts/_media_screenshot/scenes/scroll.py +1 -1
  114. package/skills/web-screenshot/scripts/_media_screenshot/scenes/sequence.py +1 -1
  115. package/skills/web-screenshot/scripts/_media_screenshot/scenes/title_card.py +1 -1
  116. package/skills/web-screenshot/scripts/_media_screenshot/scenes/type.py +1 -1
  117. package/skills/web-screenshot/scripts/_media_screenshot/scenes/wait.py +1 -1
  118. package/skills/web-screenshot/scripts/_media_screenshot/scenes/zoom.py +2 -2
  119. package/skills/web-screenshot/scripts/_media_screenshot/screenshot.py +15 -15
  120. package/skills/web-screenshot/scripts/_media_screenshot/storyboard.py +2 -2
  121. package/skills/web-screenshot/scripts/_media_screenshot/template.py +11 -11
  122. package/skills/web-screenshot/scripts/_media_screenshot/trim.py +10 -10
  123. package/skills/web-screenshot/scripts/_media_screenshot/urlguard.py +22 -22
  124. package/skills/web-screenshot/scripts/_media_upload.py +33 -33
  125. package/skills/web-screenshot/scripts/read_page.py +15 -15
  126. package/skills/web-screenshot/scripts/record.py +26 -26
  127. package/skills/web-screenshot/scripts/screenshot.py +5 -5
  128. package/dist/argv.d.ts +0 -15
  129. package/dist/auth/auth-lock.d.ts +0 -26
  130. package/dist/auth/auto-login.d.ts +0 -20
  131. package/dist/auth/commands.d.ts +0 -8
  132. package/dist/auth/credential-store.d.ts +0 -60
  133. package/dist/auth/device-flow-runner.d.ts +0 -58
  134. package/dist/auth/device-flow.d.ts +0 -52
  135. package/dist/auth/ensure.d.ts +0 -55
  136. package/dist/auth/environment.d.ts +0 -38
  137. package/dist/auth/pending-store.d.ts +0 -33
  138. package/dist/auth/resolve.d.ts +0 -30
  139. package/dist/billing.d.ts +0 -44
  140. package/dist/capabilities.d.ts +0 -63
  141. package/dist/cli.d.ts +0 -13
  142. package/dist/diagnostics.d.ts +0 -24
  143. package/dist/doctor.d.ts +0 -22
  144. package/dist/errors.d.ts +0 -29
  145. package/dist/exec.d.ts +0 -18
  146. package/dist/handlers/gen-digital-human.d.ts +0 -17
  147. package/dist/handlers/gen-image.d.ts +0 -20
  148. package/dist/handlers/gen-post.d.ts +0 -22
  149. package/dist/handlers/gen-video.d.ts +0 -19
  150. package/dist/handlers/gen-voice.d.ts +0 -26
  151. package/dist/handlers/index.d.ts +0 -20
  152. package/dist/handlers/shared.d.ts +0 -26
  153. package/dist/handlers/template-init.d.ts +0 -10
  154. package/dist/http.d.ts +0 -72
  155. package/dist/index.d.ts +0 -9
  156. package/dist/install.d.ts +0 -71
  157. package/dist/progress.d.ts +0 -23
  158. package/dist/project/commands.d.ts +0 -19
  159. package/dist/project/host.d.ts +0 -91
  160. package/dist/project/resolve.d.ts +0 -43
  161. package/dist/project/store.d.ts +0 -31
  162. package/dist/project/take.d.ts +0 -50
  163. package/dist/project/trace.d.ts +0 -98
  164. package/dist/registry.d.ts +0 -30
  165. package/dist/runner.d.ts +0 -39
  166. package/dist/skill-schema.d.ts +0 -138
  167. package/dist/template/args.d.ts +0 -25
  168. package/dist/template/check.d.ts +0 -23
  169. package/dist/template/commands.d.ts +0 -25
  170. package/dist/template/constants.d.ts +0 -39
  171. package/dist/template/init.d.ts +0 -28
  172. package/dist/template/list.d.ts +0 -23
  173. package/dist/template/preview.d.ts +0 -60
  174. package/dist/template/proc.d.ts +0 -26
  175. package/dist/template/project.d.ts +0 -47
  176. package/dist/template/publish.d.ts +0 -43
  177. package/dist/template/render-client.d.ts +0 -39
  178. package/dist/template/upgrade.d.ts +0 -44
  179. package/dist/text.d.ts +0 -8
@@ -1,132 +1 @@
1
- /**
2
- * OAuth 2.0 Device Authorization Grant (RFC 8628) client for `remixmate login`.
3
- *
4
- * The device/code and device/token endpoints are PUBLIC (no auth), so this uses
5
- * plain fetch against the ab-api base URL rather than the authenticated mmPost.
6
- * On approval the backend returns the user's existing PrivToken (Route B).
7
- */
8
- import { spawn } from 'node:child_process';
9
- export class DeviceFlowError extends Error {
10
- }
11
- async function postJson(url, body, timeoutMs) {
12
- const controller = new AbortController();
13
- const timer = setTimeout(() => controller.abort(), timeoutMs);
14
- try {
15
- let resp;
16
- try {
17
- resp = await fetch(url, {
18
- method: 'POST',
19
- headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
20
- body: JSON.stringify(body),
21
- signal: controller.signal,
22
- });
23
- }
24
- catch (err) {
25
- // A DNS/TCP/TLS failure or an abort rejects with a plain Error. Left
26
- // unwrapped it escapes runDeviceFlow's DeviceFlowError handler and
27
- // surfaces to the user as a raw stack trace.
28
- const reason = err instanceof Error && err.name === 'AbortError'
29
- ? `请求超时(${Math.round(timeoutMs / 1000)}s)`
30
- : err.message;
31
- throw new DeviceFlowError(`无法连接授权服务 ${url}: ${reason}`);
32
- }
33
- const text = await resp.text();
34
- if (!resp.ok) {
35
- throw new DeviceFlowError(`HTTP ${resp.status}: ${text.slice(0, 200)}`);
36
- }
37
- try {
38
- return JSON.parse(text);
39
- }
40
- catch {
41
- throw new DeviceFlowError(`授权服务返回了非 JSON 响应: ${text.slice(0, 200)}`);
42
- }
43
- }
44
- finally {
45
- clearTimeout(timer);
46
- }
47
- }
48
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
49
- /** Request a fresh device_code/user_code. 30s timeout, no retries here. */
50
- export async function requestDeviceCode(apiBaseUrl, clientLabel) {
51
- const env = await postJson(`${apiBaseUrl}/auth/device/code`, { clientLabel }, 30_000);
52
- if (env.code !== 0 || !env.data) {
53
- throw new DeviceFlowError(env.msg || 'failed to obtain device code');
54
- }
55
- return env.data;
56
- }
57
- /**
58
- * Try to open `url` in the default browser. Returns false when no opener is
59
- * available (caller then prints the URL for manual entry).
60
- */
61
- export function openBrowser(url) {
62
- const cmd = process.platform === 'darwin' ? 'open' :
63
- process.platform === 'win32' ? 'cmd' :
64
- 'xdg-open';
65
- const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
66
- try {
67
- const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
68
- child.on('error', () => { });
69
- child.unref();
70
- return true;
71
- }
72
- catch {
73
- return false;
74
- }
75
- }
76
- /**
77
- * Poll device/token until a terminal outcome. Honors `interval` (+5s on
78
- * slow_down), retries transient network failures up to 3 consecutive times, and
79
- * bounds total time by the device code's `expiresIn`.
80
- *
81
- * When `opts.maxWaitMs` is supplied, polling also stops once that window
82
- * elapses (whichever comes first). A stop caused by `maxWaitMs` returns
83
- * `{ status: 'expired_token', timedOut: true }` so callers can tell a
84
- * short-wait timeout apart from a genuinely expired device code.
85
- *
86
- * Resolves with the final TokenResult (status 'approved' carries the token).
87
- */
88
- export async function pollForToken(apiBaseUrl, code, opts = {}) {
89
- const expiryDeadline = Date.now() + code.expiresIn * 1000;
90
- const waitDeadline = opts.maxWaitMs != null ? Date.now() + opts.maxWaitMs : Number.POSITIVE_INFINITY;
91
- const deadline = Math.min(expiryDeadline, waitDeadline);
92
- let intervalMs = Math.max(code.interval, 1) * 1000;
93
- let consecutiveErrors = 0;
94
- // Wait one interval before the first poll (approval can't have happened yet).
95
- await sleep(intervalMs);
96
- while (Date.now() < deadline) {
97
- let env;
98
- try {
99
- env = await postJson(`${apiBaseUrl}/auth/device/token`, { deviceCode: code.deviceCode }, 15_000);
100
- consecutiveErrors = 0;
101
- }
102
- catch (err) {
103
- consecutiveErrors += 1;
104
- if (consecutiveErrors >= 3) {
105
- throw new DeviceFlowError(`polling failed: ${err.message}`);
106
- }
107
- await sleep(intervalMs);
108
- continue;
109
- }
110
- if (env.code !== 0 || !env.data) {
111
- throw new DeviceFlowError(env.msg || 'unexpected token response');
112
- }
113
- const result = env.data;
114
- switch (result.status) {
115
- case 'approved':
116
- case 'access_denied':
117
- case 'expired_token':
118
- case 'invalid_grant':
119
- return result;
120
- case 'slow_down':
121
- intervalMs += 5_000;
122
- break;
123
- case 'authorization_pending':
124
- default:
125
- break;
126
- }
127
- await sleep(intervalMs);
128
- }
129
- // Stopped on the wait window rather than the code's own expiry.
130
- const timedOut = waitDeadline <= expiryDeadline;
131
- return { status: 'expired_token', timedOut };
132
- }
1
+ var f=Object.defineProperty;var s=(e,t)=>f(e,"name",{value:t,configurable:!0});import{spawn as x}from"node:child_process";class c extends Error{static{s(this,"DeviceFlowError")}}async function m(e,t,n){const o=new AbortController,l=setTimeout(()=>o.abort(),n);try{let i;try{i=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(t),signal:o.signal})}catch(a){const w=a instanceof Error&&a.name==="AbortError"?`请求超时(${Math.round(n/1e3)}s)`:a.message;throw new c(`无法连接授权服务 ${e}: ${w}`)}const r=await i.text();if(!i.ok)throw new c(`HTTP ${i.status}: ${r.slice(0,200)}`);try{return JSON.parse(r)}catch{throw new c(`授权服务返回了非 JSON 响应: ${r.slice(0,200)}`)}}finally{clearTimeout(l)}}s(m,"postJson");const p=s(e=>new Promise(t=>setTimeout(t,e)),"sleep");async function y(e,t){const n=await m(`${e}/auth/device/code`,{clientLabel:t},3e4);if(n.code!==0||!n.data)throw new c(n.msg||"failed to obtain device code");return n.data}s(y,"requestDeviceCode");function T(e){const t=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",n=process.platform==="win32"?["/c","start","",e]:[e];try{const o=x(t,n,{stdio:"ignore",detached:!0});return o.on("error",()=>{}),o.unref(),!0}catch{return!1}}s(T,"openBrowser");async function _(e,t,n={}){const o=Date.now()+t.expiresIn*1e3,l=n.maxWaitMs!=null?Date.now()+n.maxWaitMs:Number.POSITIVE_INFINITY,i=Math.min(o,l);let r=Math.max(t.interval,1)*1e3,a=0;for(await p(r);Date.now()<i;){let d;try{d=await m(`${e}/auth/device/token`,{deviceCode:t.deviceCode},15e3),a=0}catch(h){if(a+=1,a>=3)throw new c(`polling failed: ${h.message}`);await p(r);continue}if(d.code!==0||!d.data)throw new c(d.msg||"unexpected token response");const u=d.data;switch(u.status){case"approved":case"access_denied":case"expired_token":case"invalid_grant":return u;case"slow_down":r+=5e3;break;default:break}await p(r)}return{status:"expired_token",timedOut:l<=o}}s(_,"pollForToken");export{c as DeviceFlowError,T as openBrowser,_ as pollForToken,y as requestDeviceCode};
@@ -1,62 +1 @@
1
- /**
2
- * The single authorization entry point for every skill invocation.
3
- *
4
- * Before this module the CLI resolved PrivTokens in three unrelated places:
5
- * `resolveHttpContext` (4 TS handlers, with credential store + auto device
6
- * login), `registry_loader.py` (1 Python skill, with its own credential-file
7
- * reader), and plain `os.environ["PRIV_TOKEN"]` (the other 7 Python skills,
8
- * which therefore ignored `remixmate login` entirely). Auto browser auth only
9
- * ever covered the first group.
10
- *
11
- * Now `runner.ts` calls `ensureAuth()` once, before dispatch, for every skill —
12
- * so the credential store, the OS keychain and the device flow exist in exactly
13
- * one place (Node), and Python scripts only ever read an injected `PRIV_TOKEN`.
14
- */
15
- import { resolveApiBaseUrl } from '../http.js';
16
- import { EXIT, SkillError } from '../errors.js';
17
- import { resolvePrivToken, NOT_AUTHENTICATED_HINT } from './resolve.js';
18
- import { attemptAutoLogin } from './auto-login.js';
19
- export const AUTH_MODE_VALUES = ['required', 'optional', 'none'];
20
- /**
21
- * Resolve (and if necessary acquire) a PrivToken for one skill invocation.
22
- * Throws SkillError(exit 4) when `mode === 'required'` and no token could be
23
- * obtained; the message is the shared NOT_AUTHENTICATED_HINT.
24
- */
25
- export async function ensureAuth(opts) {
26
- const apiBaseUrl = resolveApiBaseUrl(opts.apiBaseUrl);
27
- if (opts.mode === 'none')
28
- return { apiBaseUrl };
29
- let resolved = await resolvePrivToken({ flag: opts.flagToken, apiBaseUrl });
30
- // Browser authorization is only ever attempted for `required`. attemptAutoLogin
31
- // self-gates on CI / headless / kill-switch, so the env-injected cloud path
32
- // returns here untouched.
33
- if (!resolved && opts.mode === 'required') {
34
- resolved = await attemptAutoLogin(apiBaseUrl);
35
- }
36
- if (!resolved) {
37
- if (opts.mode === 'optional')
38
- return { apiBaseUrl };
39
- throw new SkillError(NOT_AUTHENTICATED_HINT, EXIT.NOT_AUTHENTICATED);
40
- }
41
- return {
42
- apiBaseUrl,
43
- token: resolved.privToken,
44
- source: resolved.source,
45
- userLabel: resolved.userLabel,
46
- };
47
- }
48
- /**
49
- * Environment overrides handed to a spawned Python skill.
50
- *
51
- * `MM_API_BASE_URL` is always set so the child resolves the same backend the
52
- * preflight authenticated against — otherwise a `--api-base-url` flag or a
53
- * credential bound to a staging backend would silently disagree with the URL
54
- * the Python side derives. `PRIV_TOKEN` is only set when non-empty, so an
55
- * `optional` skill without a credential keeps inheriting whatever the host set.
56
- */
57
- export function authChildEnv(pre) {
58
- const env = { MM_API_BASE_URL: pre.apiBaseUrl };
59
- if (pre.token)
60
- env.PRIV_TOKEN = pre.token;
61
- return env;
62
- }
1
+ var t=Object.defineProperty;var n=(e,r)=>t(e,"name",{value:r,configurable:!0});import{resolveApiBaseUrl as i}from"../http.js";import{EXIT as a,SkillError as l}from"../errors.js";import{resolvePrivToken as u,NOT_AUTHENTICATED_HINT as T}from"./resolve.js";import{attemptAutoLogin as f}from"./auto-login.js";const U=["required","optional","none"];async function _(e){const r=i(e.apiBaseUrl);if(e.mode==="none")return{apiBaseUrl:r};let o=await u({flag:e.flagToken,apiBaseUrl:r});if(!o&&e.mode==="required"&&(o=await f(r)),!o){if(e.mode==="optional")return{apiBaseUrl:r};throw new l(T,a.NOT_AUTHENTICATED)}return{apiBaseUrl:r,token:o.privToken,source:o.source,userLabel:o.userLabel}}n(_,"ensureAuth");function c(e){const r={MM_API_BASE_URL:e.apiBaseUrl};return e.token&&(r.PRIV_TOKEN=e.token),r}n(c,"authChildEnv");export{U as AUTH_MODE_VALUES,c as authChildEnv,_ as ensureAuth};
@@ -1,69 +1 @@
1
- /**
2
- * Pure environment predicates that gate the auto device-login feature.
3
- *
4
- * Everything here is synchronous, side-effect free, never touches the network
5
- * and never reads a PrivToken. The conservative bias is: when in doubt, treat
6
- * the environment as headless so we fail fast rather than pop a useless browser.
7
- *
8
- * See .kiro/specs/cli-auto-device-login/design.md (Requirement 1.4–1.7).
9
- */
10
- /** Total kill switch: hosts can force-disable auto auth and fall back to fail-fast. */
11
- export function autoAuthDisabled() {
12
- return (process.env.REMIXMATE_NO_BROWSER_AUTH ?? '').trim() === '1';
13
- }
14
- /** Heuristic detection of common CI environments (no desktop → headless). */
15
- function isCI() {
16
- const e = process.env;
17
- return !!(e.CI ||
18
- e.CONTINUOUS_INTEGRATION ||
19
- e.GITHUB_ACTIONS ||
20
- e.GITLAB_CI ||
21
- e.BUILDKITE ||
22
- e.CIRCLECI ||
23
- e.TRAVIS ||
24
- e.JENKINS_URL);
25
- }
26
- /**
27
- * Whether the process is likely running without a desktop session capable of
28
- * opening a browser.
29
- *
30
- * - CI environments are always treated as headless.
31
- * - On Linux a browser needs an X11 / Wayland display; absence → headless.
32
- * - macOS / Windows are assumed to have a GUI session available.
33
- */
34
- export function isHeadless() {
35
- if (isCI())
36
- return true;
37
- if (process.platform === 'linux') {
38
- return !((process.env.DISPLAY ?? '').trim() || (process.env.WAYLAND_DISPLAY ?? '').trim());
39
- }
40
- return false;
41
- }
42
- /**
43
- * Master gate for auto authorization. Only true when auto auth is not disabled
44
- * AND the environment can plausibly open a browser for the user.
45
- */
46
- export function canAutoAuth() {
47
- return !autoAuthDisabled() && !isHeadless();
48
- }
49
- /**
50
- * Whether the CLI is being driven by an agent host (Claude Code / Codex /
51
- * ab-agent) rather than typed by a human at a terminal.
52
- *
53
- * Used to decide (a) whether to emit the machine-readable AUTH_REQUIRED block
54
- * alongside the human text, and (b) whether an interactive `login` may block
55
- * for the device code's full lifetime — an agent's tool call is time-bounded,
56
- * so it must get a bounded, resumable wait instead.
57
- *
58
- * Detection is deliberately loose: a piped stdout is the reliable signal, the
59
- * env markers just make the intent explicit for hosts that allocate a pty.
60
- */
61
- export function isAgentHost() {
62
- const e = process.env;
63
- if ((e.CLAUDECODE ?? '').trim() || (e.CLAUDE_CODE ?? '').trim() || (e.CODEX_SANDBOX ?? '').trim()) {
64
- return true;
65
- }
66
- if ((e.AGENT_NAME ?? '').trim())
67
- return true;
68
- return !process.stdout.isTTY;
69
- }
1
+ var n=Object.defineProperty;var t=(r,e)=>n(r,"name",{value:e,configurable:!0});function s(){return(process.env.REMIXMATE_NO_BROWSER_AUTH??"").trim()==="1"}t(s,"autoAuthDisabled");function o(){const r=process.env;return!!(r.CI||r.CONTINUOUS_INTEGRATION||r.GITHUB_ACTIONS||r.GITLAB_CI||r.BUILDKITE||r.CIRCLECI||r.TRAVIS||r.JENKINS_URL)}t(o,"isCI");function u(){return o()?!0:process.platform==="linux"?!((process.env.DISPLAY??"").trim()||(process.env.WAYLAND_DISPLAY??"").trim()):!1}t(u,"isHeadless");function A(){return!s()&&!u()}t(A,"canAutoAuth");function I(){const r=process.env;return(r.CLAUDECODE??"").trim()||(r.CLAUDE_CODE??"").trim()||(r.CODEX_SANDBOX??"").trim()||(r.AGENT_NAME??"").trim()?!0:!process.stdout.isTTY}t(I,"isAgentHost");export{s as autoAuthDisabled,A as canAutoAuth,I as isAgentHost,u as isHeadless};
@@ -1,45 +1 @@
1
- /**
2
- * Pending device-authorization state, at ~/.config/remixmate/pending.json.
3
- *
4
- * Exists so authorization can outlive a single process. `login --start` requests
5
- * a device code, persists it and returns immediately; `login --wait` picks it up
6
- * and polls with a bounded window, as many times as needed. That split is what
7
- * makes authorization workable from an agent host, whose tool calls are
8
- * time-bounded and cannot sit on a 10-minute blocking poll.
9
- *
10
- * The device_code IS a short-lived bearer secret (it can be exchanged for the
11
- * PrivToken until it expires or is used), so the file is written 0600 like the
12
- * credential store, and is deleted as soon as it is redeemed or found expired.
13
- */
14
- import { homedir } from 'node:os';
15
- import { join } from 'node:path';
16
- import { promises as fs } from 'node:fs';
17
- const PENDING_DIR = join(homedir(), '.config', 'remixmate');
18
- export const PENDING_FILE = join(PENDING_DIR, 'pending.json');
19
- export async function setPending(pending) {
20
- await fs.mkdir(PENDING_DIR, { recursive: true, mode: 0o700 });
21
- await fs.writeFile(PENDING_FILE, JSON.stringify(pending, null, 2), { mode: 0o600 });
22
- await fs.chmod(PENDING_FILE, 0o600);
23
- }
24
- /**
25
- * Read the pending authorization for `apiBaseUrl`. Returns null when absent,
26
- * unreadable, bound to a different backend, or already expired — callers then
27
- * tell the user to run `login --start` again.
28
- */
29
- export async function getPending(apiBaseUrl) {
30
- let parsed;
31
- try {
32
- parsed = JSON.parse(await fs.readFile(PENDING_FILE, 'utf8'));
33
- }
34
- catch {
35
- return null;
36
- }
37
- if (!parsed?.deviceCode || parsed.apiBaseUrl !== apiBaseUrl)
38
- return null;
39
- if (!(parsed.expiresAt > Date.now()))
40
- return null;
41
- return parsed;
42
- }
43
- export async function clearPending() {
44
- await fs.rm(PENDING_FILE, { force: true });
45
- }
1
+ var s=Object.defineProperty;var i=(r,e)=>s(r,"name",{value:e,configurable:!0});import{homedir as c}from"node:os";import{join as o}from"node:path";import{promises as t}from"node:fs";const a=o(c(),".config","remixmate"),n=o(a,"pending.json");async function l(r){await t.mkdir(a,{recursive:!0,mode:448}),await t.writeFile(n,JSON.stringify(r,null,2),{mode:384}),await t.chmod(n,384)}i(l,"setPending");async function p(r){let e;try{e=JSON.parse(await t.readFile(n,"utf8"))}catch{return null}return!e?.deviceCode||e.apiBaseUrl!==r||!(e.expiresAt>Date.now())?null:e}i(p,"getPending");async function g(){await t.rm(n,{force:!0})}i(g,"clearPending");export{n as PENDING_FILE,g as clearPending,p as getPending,l as setPending};
@@ -1,44 +1 @@
1
- /**
2
- * Single source of truth for PrivToken resolution precedence, shared by the
3
- * CLI commands and the HTTP layer (src/http.ts wires this in — Wave 2 task 4.3).
4
- *
5
- * Precedence (high → low), per requirements 7.1–7.4:
6
- * 1. explicit --token flag (caller override)
7
- * 2. PRIV_TOKEN environment variable (CI / agent-embedded hosts)
8
- * 3. Credential_Store entry for the active API base URL (written by `login`)
9
- *
10
- * A value that is empty after trimming whitespace is treated as ABSENT, so an
11
- * exported-but-empty `PRIV_TOKEN=""` falls through to the credential store
12
- * rather than masking it.
13
- */
14
- import { getCredential } from './credential-store.js';
15
- /** Trim and return undefined when empty, so empty == absent everywhere. */
16
- function clean(value) {
17
- if (value == null)
18
- return undefined;
19
- const trimmed = value.trim();
20
- return trimmed.length > 0 ? trimmed : undefined;
21
- }
22
- /**
23
- * Resolve the PrivToken following the documented precedence. Returns null when
24
- * no source yields a token (callers render the not-authenticated message).
25
- */
26
- export async function resolvePrivToken(opts) {
27
- const flag = clean(opts.flag);
28
- if (flag)
29
- return { privToken: flag, source: 'flag' };
30
- const env = clean(process.env.PRIV_TOKEN);
31
- if (env)
32
- return { privToken: env, source: 'env' };
33
- const stored = await getCredential(opts.apiBaseUrl);
34
- if (stored && clean(stored.privToken)) {
35
- return {
36
- privToken: stored.privToken,
37
- source: 'credential-store',
38
- userLabel: stored.userLabel,
39
- };
40
- }
41
- return null;
42
- }
43
- /** Human-readable hint shown when resolution fails (no token echoed). */
44
- export const NOT_AUTHENTICATED_HINT = '❌ not authenticated — run `remixmate login`, set PRIV_TOKEN, or pass --token';
1
+ var s=Object.defineProperty;var t=(e,n)=>s(e,"name",{value:n,configurable:!0});import{getCredential as u}from"./credential-store.js";function o(e){if(e==null)return;const n=e.trim();return n.length>0?n:void 0}t(o,"clean");async function l(e){const n=o(e.flag);if(n)return{privToken:n,source:"flag"};const i=o(process.env.PRIV_TOKEN);if(i)return{privToken:i,source:"env"};const r=await u(e.apiBaseUrl);return r&&o(r.privToken)?{privToken:r.privToken,source:"credential-store",userLabel:r.userLabel}:null}t(l,"resolvePrivToken");const T="❌ not authenticated — run `remixmate login`, set PRIV_TOKEN, or pass --token";export{T as NOT_AUTHENTICATED_HINT,l as resolvePrivToken};
package/dist/billing.js CHANGED
@@ -1,76 +1,3 @@
1
- /**
2
- * Billing capture — makes credit consumption visible to whoever ran the skill.
3
- *
4
- * Credits are deducted server-side and used to be invisible here: a run printed
5
- * its image/video URL and nothing else, so the first time a user noticed the
6
- * credit system at all was the `insufficient_credits` error after the balance
7
- * had already run out. ab-api now attaches a `billing` object to the envelope of
8
- * every response that charged something (see core.Success / credits_billing.go);
9
- * this module accumulates those across all the calls a single skill run makes —
10
- * a gen-image run polls, a gen-video run polls, and each may charge once — and
11
- * renders one footer at the end.
12
- *
13
- * Two outputs, two audiences, both on stdout:
14
- * - a human-readable footer, which is what the agent relays to the user.
15
- * - one `__progress__` line (phase `billing`) — the existing machine protocol,
16
- * already parsed by ab-agent's executor and skipped by the line-scanning
17
- * parsers in render_video.py, so a nested pipeline caller can aggregate a
18
- * whole run's spend without a new stdout contract.
19
- *
20
- * Both go to stdout rather than stderr (where the auth footer lives) because
21
- * ab-agent hands the LLM `result.stdout || result.stderr` (mcp-tools.ts) — a
22
- * stderr-only footer would be invisible in the hosted agent, visible only when
23
- * a local host like codex shows both streams. Adding stdout lines is safe:
24
- * emitProgress already writes NDJSON there, so no consumer can be treating
25
- * stdout as a single JSON document.
26
- */
27
- import { emitProgress } from './progress.js';
28
- let creditsCharged = 0;
29
- let latestBalance = null;
30
- const chargedItems = [];
31
- /** Accumulate one response's billing object; no-ops on responses that charged nothing. */
32
- export function recordBilling(billing) {
33
- if (!billing || typeof billing.credits !== 'number' || billing.credits <= 0)
34
- return;
35
- creditsCharged += billing.credits;
36
- if (typeof billing.balance === 'number')
37
- latestBalance = billing.balance;
38
- for (const item of billing.items ?? []) {
39
- if (item && typeof item.credits === 'number' && item.credits > 0)
40
- chargedItems.push(item);
41
- }
42
- }
43
- /** Everything charged so far in this process, or null when nothing was. */
44
- export function billingSummary() {
45
- if (creditsCharged <= 0)
46
- return null;
47
- return { credits: creditsCharged, balance: latestBalance ?? 0, items: [...chargedItems] };
48
- }
49
- /**
50
- * Emit the footer for what this process charged. Safe to call on the error path
51
- * too: a run can fail after a successful (already billed) generation step, and
52
- * that spend still needs to be reported.
53
- *
54
- * Idempotent — only the first call prints, so a handler and the dispatcher can
55
- * both reach for it without doubling the number the user sees.
56
- */
57
- let emitted = false;
58
- export function emitBillingFooter() {
59
- if (emitted)
60
- return;
61
- const summary = billingSummary();
62
- if (!summary)
63
- return;
64
- emitted = true;
65
- emitProgress({ phase: 'billing', credits: summary.credits, balance: summary.balance, items: summary.items });
66
- const balance = summary.balance.toLocaleString('en-US');
67
- const credits = summary.credits.toLocaleString('en-US');
68
- process.stdout.write(`\n💳 Charged ${credits} credits · balance ${balance}\n`);
69
- }
70
- /** Test seam — resets the accumulator between runs in-process. */
71
- export function resetBilling() {
72
- creditsCharged = 0;
73
- latestBalance = null;
74
- chargedItems.length = 0;
75
- emitted = false;
76
- }
1
+ var a=Object.defineProperty;var r=(e,t)=>a(e,"name",{value:t,configurable:!0});import{emitProgress as l}from"./progress.js";let n=0,c=null;const s=[];function m(e){if(!(!e||typeof e.credits!="number"||e.credits<=0)){n+=e.credits,typeof e.balance=="number"&&(c=e.balance);for(const t of e.items??[])t&&typeof t.credits=="number"&&t.credits>0&&s.push(t)}}r(m,"recordBilling");function u(){return n<=0?null:{credits:n,balance:c??0,items:[...s]}}r(u,"billingSummary");let i=!1;function p(){if(i)return;const e=u();if(!e)return;i=!0,l({phase:"billing",credits:e.credits,balance:e.balance,items:e.items});const t=e.balance.toLocaleString("en-US"),o=e.credits.toLocaleString("en-US");process.stdout.write(`
2
+ 💳 Charged ${o} credits · balance ${t}
3
+ `)}r(p,"emitBillingFooter");function b(){n=0,c=null,s.length=0,i=!1}r(b,"resetBilling");export{u as billingSummary,p as emitBillingFooter,m as recordBilling,b as resetBilling};
@@ -1,75 +1 @@
1
- /**
2
- * Model capabilities client.
3
- *
4
- * The model roster — internal model ids, friendly aliases, defaults, and each
5
- * model's parameter constraints (aspect ratios / resolutions / durations /
6
- * reference-image caps / Seedream size presets) — is owned by ab-api and served
7
- * from POST /model/capabilities (public, no auth). The CLI fetches it at runtime
8
- * instead of hardcoding it, so this npm package never ships the internal model
9
- * matrix or provider details, and validation can't drift from the backend.
10
- *
11
- * Fetched once per process and memoized; the catalog is static server-side.
12
- */
13
- import { resolveApiBaseUrl, SkillError } from './http.js';
14
- let cached = null;
15
- /** Fetch (and memoize) the model capabilities catalog from ab-api. */
16
- export async function getCapabilities(apiBaseUrl) {
17
- if (cached)
18
- return cached;
19
- const base = resolveApiBaseUrl(apiBaseUrl);
20
- const url = `${base}/model/capabilities`;
21
- const controller = new AbortController();
22
- const timeout = setTimeout(() => controller.abort(), 15_000);
23
- let resp;
24
- try {
25
- resp = await fetch(url, {
26
- method: 'POST',
27
- headers: { 'Content-Type': 'application/json' },
28
- body: '{}',
29
- signal: controller.signal,
30
- });
31
- }
32
- catch (err) {
33
- if (err instanceof Error && err.name === 'AbortError') {
34
- throw new SkillError(`❌ request timed out fetching model capabilities: ${url}`);
35
- }
36
- throw new SkillError(`❌ network error fetching model capabilities: ${err.message}`);
37
- }
38
- finally {
39
- clearTimeout(timeout);
40
- }
41
- const text = await resp.text();
42
- if (!resp.ok) {
43
- throw new SkillError(`❌ failed to fetch model capabilities (HTTP ${resp.status}): ${text}`);
44
- }
45
- let parsed;
46
- try {
47
- parsed = JSON.parse(text);
48
- }
49
- catch {
50
- throw new SkillError(`❌ model capabilities response is not JSON: ${text.slice(0, 200)}`);
51
- }
52
- if (parsed.code !== 0 || !parsed.data) {
53
- throw new SkillError(`❌ model capabilities request failed: ${parsed.msg ?? 'unknown error'} (code=${parsed.code})`);
54
- }
55
- cached = parsed.data;
56
- return cached;
57
- }
58
- /**
59
- * Resolve a user-supplied model (friendly alias or full id) against a model
60
- * list. Returns the matched descriptor, or null when nothing matches.
61
- */
62
- export function matchModel(list, input) {
63
- const key = input.trim().toLowerCase();
64
- for (const m of list) {
65
- if (m.id.toLowerCase() === key)
66
- return m;
67
- if ((m.aliases ?? []).some((a) => a.toLowerCase() === key))
68
- return m;
69
- }
70
- return null;
71
- }
72
- /** The descriptor flagged `default` in a list, falling back to the first entry. */
73
- export function defaultModel(list) {
74
- return list.find((m) => m.default) ?? list[0];
75
- }
1
+ var u=Object.defineProperty;var s=(e,o)=>u(e,"name",{value:o,configurable:!0});import{resolveApiBaseUrl as m,SkillError as a}from"./http.js";let l=null;async function h(e){if(l)return l;const i=`${m(e)}/model/capabilities`,t=new AbortController,c=setTimeout(()=>t.abort(),15e3);let n;try{n=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json"},body:"{}",signal:t.signal})}catch(f){throw f instanceof Error&&f.name==="AbortError"?new a(`❌ request timed out fetching model capabilities: ${i}`):new a(`❌ network error fetching model capabilities: ${f.message}`)}finally{clearTimeout(c)}const d=await n.text();if(!n.ok)throw new a(`❌ failed to fetch model capabilities (HTTP ${n.status}): ${d}`);let r;try{r=JSON.parse(d)}catch{throw new a(`❌ model capabilities response is not JSON: ${d.slice(0,200)}`)}if(r.code!==0||!r.data)throw new a(`❌ model capabilities request failed: ${r.msg??"unknown error"} (code=${r.code})`);return l=r.data,l}s(h,"getCapabilities");function b(e,o){const i=o.trim().toLowerCase();for(const t of e)if(t.id.toLowerCase()===i||(t.aliases??[]).some(c=>c.toLowerCase()===i))return t;return null}s(b,"matchModel");function $(e){return e.find(o=>o.default)??e[0]}s($,"defaultModel");export{$ as defaultModel,h as getCapabilities,b as matchModel};