@remixmate/cli 0.9.72 → 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 (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,19 +1,19 @@
1
1
  #!/usr/bin/env python3
2
- """录屏产物收尾客户端 —— 把本地视频推给 ab-render 的 /finalize 端点。
3
2
 
4
- ab-render 侧(apps/ab-render/server/finalize.ts)负责:webm→mp4 转码 + 抽首帧封面
5
- + 上传阿里云 VOD + 轮询拿到 CDN 播放地址。本模块只做 HTTP 层:POST 原始字节 +
6
- 轮询 /finalizeStatus。纯标准库,不依赖 ffmpeg / OSS / 阿里云 SDK。
7
3
 
8
- 与 render-video 的 remote_renderer_client 一样直连 ab-render:
9
- Base URL 优先级:显式传入 > 环境变量 REMOTION_RENDER_API_URL > 默认生产地址。
10
4
 
11
- 认证头(复用 X-Priv-Token 规范):
12
- X-Priv-Token: <PRIV_TOKEN>
13
- x-invoke-skill: web-record
14
- x-invoke-agent: <AGENT_NAME> # optional
15
- x-conversation-id: <CONVERSATION_ID> # optional
16
- """
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
17
  from __future__ import annotations
18
18
 
19
19
  import json
@@ -34,7 +34,7 @@ TERMINAL_FAILURE = {"failed", "cancelled"}
34
34
 
35
35
 
36
36
  class FinalizeError(RuntimeError):
37
- """上传/收尾失败,交由调用方决定是否降级为「仅本地文件」。"""
37
+ pass
38
38
 
39
39
 
40
40
  def _base_url(explicit: Optional[str]) -> str:
@@ -75,7 +75,7 @@ def _submit(
75
75
  conversation_id: Optional[str],
76
76
  request_timeout: float,
77
77
  ) -> str:
78
- """POST /finalize(原始字节 + query options)→ 返回 taskId。"""
78
+
79
79
  ext = Path(local_path).suffix.lstrip(".").lower() or "webm"
80
80
  query = {
81
81
  "transcode": "1" if transcode else "0",
@@ -83,12 +83,12 @@ def _submit(
83
83
  "coverAtSec": str(cover_at_sec),
84
84
  "resolveCdn": "1" if resolve_cdn else "0",
85
85
  "ext": ext,
86
- # 素材角色:ab-api 按它落 file.role(录屏贡献 video_clip 能力、按成品保留)
86
+
87
87
  "role": "screen_record",
88
88
  }
89
89
  if title:
90
90
  query["title"] = title
91
- # 端锚裁剪窗口:让 ab-render 在转码同一 pass 里切掉开头加载段(skill 侧不再裁)。
91
+
92
92
  if keep_tail_sec and keep_tail_sec > 0:
93
93
  query["keepTailSec"] = f"{keep_tail_sec:.3f}"
94
94
  url = f"{base_url}/finalize?{urllib.parse.urlencode(query)}"
@@ -133,7 +133,7 @@ def _poll(
133
133
  request_timeout: float,
134
134
  on_progress: Optional[Callable[[dict], None]],
135
135
  ) -> dict:
136
- """POST /finalizeStatus 轮询直到 succeeded/failed/超时。成功返回 data dict。"""
136
+
137
137
  start = time.monotonic()
138
138
  consecutive_errors = 0
139
139
  while True:
@@ -192,17 +192,17 @@ def finalize_recording(
192
192
  poll_interval: float = 3.0,
193
193
  request_timeout: float = 300.0,
194
194
  ) -> dict:
195
- """把本地视频推给 ab-render 收尾并轮询到终态。
196
195
 
197
- keep_tail_sec:端锚裁剪窗口(保留视频尾部这么多秒,切掉开头加载段);由 skill 测量的
198
- 内容窗口得出,None 表示不裁。裁剪与转码在 ab-render 合并成一次 pass。
199
196
 
200
- 返回 finalizeStatus 的 data dict,关键字段:
201
- fileUrl(CDN 或 vod://)、vodRef、fileId、coverUrl、durationSec。
202
- 失败抛 FinalizeError,由调用方决定降级。
203
- """
197
+
198
+
199
+
200
+
201
+
202
+
203
+
204
204
  base = _base_url(base_url)
205
- # 诊断:明确打出实际请求的 ab-render 地址 + 裁剪窗口,便于排查「发去哪/有没有裁」。
205
+
206
206
  print(
207
207
  f"[record] finalize target: {base} keepTailSec={keep_tail_sec}",
208
208
  file=sys.stderr,
@@ -234,15 +234,15 @@ def finalize_recording(
234
234
 
235
235
  DEFAULT_ABAPI_BASE_URL = "https://api-agent.remixmate.ai/api"
236
236
 
237
- # 分镜里承载「画面在讲什么」的文本字段(caption / title-card / type 等场景)
237
+
238
238
  _STORY_TEXT_KEYS = ("title", "subtitle", "text", "caption", "label", "content")
239
239
 
240
240
 
241
241
  def storyboard_text(story: Optional[dict]) -> str:
242
- """把分镜里的可读文本收成一段:标题卡、字幕条、打字内容等,按场景顺序拼接。
243
242
 
244
- 录屏本身没有旁白,这段文本就是它最便宜的语义——生产者手上现成有,不需要 ASR。
245
- """
243
+
244
+
245
+
246
246
  if not isinstance(story, dict):
247
247
  return ""
248
248
  parts: list[str] = []
@@ -268,11 +268,11 @@ def annotate_asset(
268
268
  duration_sec: Optional[float],
269
269
  request_timeout: float = 30.0,
270
270
  ) -> None:
271
- """把录屏的语义写进 ab-api 素材库(POST /asset/annotate):分镜文本 + 来源页面。
272
271
 
273
- 分镜里没有逐场景时间戳,整条录屏作为一个片段。失败抛 FinalizeError,调用方只记日志——
274
- 语义是附加信息,不能让「录屏已完成」失败。见 ab-platform/docs/product/creation/asset-semantic-library-design.md §6.1。
275
- """
272
+
273
+
274
+
275
+
276
276
  if not private_token or not (file_id or file_url):
277
277
  return
278
278
  base = (os.environ.get("MM_API_BASE_URL") or DEFAULT_ABAPI_BASE_URL).rstrip("/")
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env python3
2
- """Playwright 正文抽取入口(薄壳,逻辑在 _media_screenshot.reader)。
3
2
 
4
- 与 screenshot.py / record.py 同住一个 scripts/ 目录,因为三者共用
5
- `_media_screenshot/` 里的浏览器启动、等待、设备模拟与 cookie 处理。
6
- 对外它是独立的 skill:`skills/web-read/skill.json` 指到这里。
7
- """
3
+
4
+
5
+
6
+
7
+
8
8
  from __future__ import annotations
9
9
 
10
10
  import argparse
@@ -13,9 +13,9 @@ import sys
13
13
  from pathlib import Path
14
14
 
15
15
  sys.path.insert(0, str(Path(__file__).resolve().parent))
16
- from _media_screenshot import cli_args # noqa: E402
17
- from _media_screenshot.reader import do_read # noqa: E402
18
- from _media_screenshot.urlguard import assert_public_url # noqa: E402
16
+ from _media_screenshot import cli_args
17
+ from _media_screenshot.reader import do_read
18
+ from _media_screenshot.urlguard import assert_public_url
19
19
 
20
20
  DEFAULT_MAX_CHARS = 20000
21
21
 
@@ -61,7 +61,7 @@ def main() -> None:
61
61
 
62
62
  args = ap.parse_args()
63
63
 
64
- # 内网地址守卫(见 urlguard.py:web_read 会把正文交给模型)。
64
+
65
65
  assert_public_url(args.url)
66
66
 
67
67
  storage_path, is_temp = cli_args.build_storage(args)
@@ -93,17 +93,17 @@ def main() -> None:
93
93
  except OSError:
94
94
  pass
95
95
 
96
- # 只在「基本没抽到东西」时报警。一篇文章页的整页文本本来就是正文的好几倍
97
- # (导航、推荐、页脚),拿这个比例当告警条件会让警告天天响、从而没人再看。
96
+
97
+
98
98
  chars = result["charCount"]
99
99
  body_chars = result["bodyCharCount"]
100
100
  status = result.get("status")
101
- # A targeted read of a counter/button can legitimately be one character.
102
- # Empty scopes still deserve a warning; HTTP warnings remain independent.
101
+
102
+
103
103
  unexpectedly_short = chars < 100 and (not args.selector or chars == 0)
104
104
  if isinstance(status, int) and status >= 400:
105
- # 错误页也有正文("404 Not Found"),照读不误——但不说一声的话,调用方
106
- # 会把一张错误页当成文章内容去写脚本。
105
+
106
+
107
107
  print(
108
108
  f"⚠️ 服务器返回 HTTP {status},下面读到的很可能是错误页而不是目标内容。",
109
109
  file=sys.stderr,
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env python3
2
- """Playwright 录屏入口(薄壳,逻辑在 _media_screenshot 包)。
3
-
4
- 模式:
5
- - 固定时长 / 条件停止(--duration / --stop-when-*)
6
- - 自动滚动(--scroll-through)
7
- - 自定义分镜(--storyboard <json>)
8
- - 模板分镜(--template <name> --param k=v ...)
9
- """
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
10
  from __future__ import annotations
11
11
 
12
12
  import argparse
@@ -16,14 +16,14 @@ import sys
16
16
  from pathlib import Path
17
17
 
18
18
  sys.path.insert(0, str(Path(__file__).resolve().parent))
19
- from _media_screenshot import ( # noqa: E402
19
+ from _media_screenshot import (
20
20
  cli_args,
21
21
  do_record,
22
22
  do_storyboard,
23
23
  template,
24
24
  trim,
25
25
  )
26
- from _media_upload import ( # noqa: E402
26
+ from _media_upload import (
27
27
  FinalizeError,
28
28
  annotate_asset,
29
29
  finalize_recording,
@@ -44,7 +44,7 @@ def main() -> None:
44
44
  help="本地输出 .webm 路径(默认 recording.webm)",
45
45
  )
46
46
 
47
- # plain recording stop conditions
47
+
48
48
  ap.add_argument("--duration", type=int, help="固定录制时长(毫秒)")
49
49
  ap.add_argument("--stop-when-selector", help="选择器出现就停")
50
50
  ap.add_argument("--stop-when-hidden", help="选择器消失就停")
@@ -53,7 +53,7 @@ def main() -> None:
53
53
  help="安全上限(ms),用 stop-when-* 时防挂死",
54
54
  )
55
55
 
56
- # scroll-through
56
+
57
57
  ap.add_argument(
58
58
  "--scroll-through", action="store_true",
59
59
  help="录制时自动从顶部平滑滚到底部",
@@ -63,7 +63,7 @@ def main() -> None:
63
63
  ap.add_argument("--scroll-pause-top", type=int, default=800)
64
64
  ap.add_argument("--scroll-pause-bottom", type=int, default=1200)
65
65
 
66
- # storyboard / template
66
+
67
67
  ap.add_argument("--storyboard", help="分镜 JSON 文件路径")
68
68
  ap.add_argument(
69
69
  "--template",
@@ -78,7 +78,7 @@ def main() -> None:
78
78
  help="列出可用模板并退出",
79
79
  )
80
80
 
81
- # 收尾(transcode + 封面 + VOD 上传,默认开;委托 ab-render /finalize)
81
+
82
82
  ap.add_argument(
83
83
  "--no-upload", action="store_true",
84
84
  help="录完不上传 VOD,只保留本地 webm(默认会转码+封面+上传并返回 CDN 地址)",
@@ -202,15 +202,15 @@ def main() -> None:
202
202
 
203
203
  local_path = str(out)
204
204
 
205
- # 端锚裁剪窗口:保留内容窗口 + 边距(与 _media_screenshot/trim.py 的常量一致)。
206
- # 上传路径把它交给 ab-render 在转码同一 pass 里裁;--no-upload 单机路径才本地裁。
205
+
206
+
207
207
  keep_tail_sec = None
208
208
  if end_ms > start_ms:
209
209
  keep_tail_sec = ((end_ms - start_ms) + 80 + 200) / 1000.0
210
210
 
211
- # ── 收尾:转码 + 封面 + 上传 VOD(默认开)───────────────────────────────
212
- # ffmpeg / OSS 全部在 ab-render 侧完成,本 skill 只推文件 + 轮询状态。
213
- # 环境缺失(如裸跑 CLI 无 PRIV_TOKEN)或收尾失败 → 优雅降级为「仅本地文件」。
211
+
212
+
213
+
214
214
  upload_enabled = not args.no_upload
215
215
  priv_token = os.environ.get("PRIV_TOKEN", "")
216
216
  if upload_enabled and not priv_token:
@@ -218,14 +218,14 @@ def main() -> None:
218
218
  upload_enabled = False
219
219
 
220
220
  if not upload_enabled:
221
- # 单机 / 不上传:没有 ab-render 帮忙裁,就在本地尽力裁一刀(需系统 ffmpeg,
222
- # 缺失则 trim.trim_video 保留原片并提示)。
221
+
222
+
223
223
  trim.trim_video(out, start_ms, end_ms)
224
224
  print(local_path)
225
225
  return
226
226
 
227
227
  def _on_progress(data: dict) -> None:
228
- # finalizeStatus 的 phase/progress → __progress__,喂 ab-agent SSE 时间线
228
+
229
229
  line: dict = {"__progress__": True, "phase": data.get("phase")}
230
230
  prog = data.get("progress")
231
231
  if isinstance(prog, (int, float)):
@@ -247,7 +247,7 @@ def main() -> None:
247
247
  conversation_id=conversation_id,
248
248
  on_progress=_on_progress,
249
249
  )
250
- except Exception as e: # noqa: BLE001 — 收尾失败不应丢掉已录好的本地文件
250
+ except Exception as e:
251
251
  print(f"[record] VOD 上传收尾失败,降级为仅本地文件:{e}", file=sys.stderr)
252
252
  print(local_path)
253
253
  return
@@ -258,7 +258,7 @@ def main() -> None:
258
258
  vod_ref = result.get("vodRef") or ""
259
259
  file_id = result.get("fileId")
260
260
 
261
- # 语义入库:分镜文本 + 来源页面。失败只提示,不影响录屏结果。
261
+
262
262
  try:
263
263
  annotate_asset(
264
264
  private_token=priv_token,
@@ -271,7 +271,7 @@ def main() -> None:
271
271
  except FinalizeError as e:
272
272
  print(f"[record] 录屏语义写入素材库失败(不影响录屏):{e}", file=sys.stderr)
273
273
 
274
- # 上传成功,默认删本地 webm(VOD 已持有),除非 --keep-webm
274
+
275
275
  if not args.keep_webm:
276
276
  try:
277
277
  os.unlink(local_path)
@@ -285,7 +285,7 @@ def main() -> None:
285
285
  print(f"🖼️ 封面:{cover_url}")
286
286
  if isinstance(duration, (int, float)):
287
287
  print(f"🕐 时长:{duration:.1f}s")
288
- # 结构化 asset 标记:ab-agent postcall 解析这一行下发前端播放器
288
+
289
289
  asset = {
290
290
  "url": cdn_url,
291
291
  "coverUrl": cover_url,
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """Playwright 截屏入口(薄壳,逻辑在 _media_screenshot 包)。"""
2
+
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
@@ -8,7 +8,7 @@ import sys
8
8
  from pathlib import Path
9
9
 
10
10
  sys.path.insert(0, str(Path(__file__).resolve().parent))
11
- from _media_screenshot import cli_args, do_screenshot # noqa: E402
11
+ from _media_screenshot import cli_args, do_screenshot
12
12
 
13
13
 
14
14
  def main() -> None:
@@ -51,9 +51,9 @@ def main() -> None:
51
51
 
52
52
  args = ap.parse_args()
53
53
 
54
- # 截屏只出图片。若 --output 是视频后缀,Playwright 的 page.screenshot() 会按扩展名
55
- # 推断出 video/webm 并抛一串晦涩的 mime 错误。这里提前拦截,返回面向模型的清晰指引,
56
- # 把请求导向 web_record(录屏入口)。
54
+
55
+
56
+
57
57
  video_exts = (".webm", ".mp4", ".mov", ".mkv", ".avi", ".gif")
58
58
  if args.output.lower().endswith(video_exts):
59
59
  ext = Path(args.output).suffix or "(video)"
package/dist/argv.d.ts DELETED
@@ -1,15 +0,0 @@
1
- /**
2
- * Argv parser shared by cli.ts and python passthrough.
3
- *
4
- * Convention (matches ab-agent's buildCliArgs):
5
- * --foo-bar value → foo_bar: 'value'
6
- * --foo-bar → foo_bar: true
7
- * --foo-bar a --foo-bar b → foo_bar: ['a', 'b']
8
- * -p value → short flags also supported as-is (kebab → snake)
9
- *
10
- * Booleans are detected when the next token starts with '--' or is absent.
11
- * Negative number values (-1.5) are NOT supported as flag values in this
12
- * parser — Python skills handle them via argparse downstream when needed.
13
- */
14
- export type ParsedArgs = Record<string, string | boolean | string[]>;
15
- export declare function parseArgv(argv: string[]): ParsedArgs;
@@ -1,26 +0,0 @@
1
- /**
2
- * Cross-process file lock for the auto device-login flow.
3
- *
4
- * When several skill processes start without a token at the same time, only one
5
- * should drive the Device Flow (= open one browser). The rest wait for the
6
- * winner to write the credential, then read it. This lock coordinates that.
7
- *
8
- * The lock file lives next to the credential store, contains only non-secret
9
- * metadata, and self-heals: a lock left behind by a crashed process is reclaimed
10
- * once it is older than `staleMs` (Requirement 3.1/3.4/3.5).
11
- */
12
- export interface LockHandle {
13
- release(): Promise<void>;
14
- }
15
- /**
16
- * Try to acquire the auth lock for `apiBaseUrl`.
17
- * - Returns a handle when acquired.
18
- * - Returns null when another live process holds it.
19
- * - Reclaims a stale lock (older than `staleMs`) and retries once.
20
- */
21
- export declare function acquireLock(apiBaseUrl: string, staleMs: number): Promise<LockHandle | null>;
22
- /**
23
- * Poll the credential store until a usable credential for `apiBaseUrl` appears
24
- * or `timeoutMs` elapses. Used by processes that did NOT win the lock.
25
- */
26
- export declare function waitForCredential(apiBaseUrl: string, timeoutMs: number): Promise<boolean>;
@@ -1,20 +0,0 @@
1
- /**
2
- * Automatic Device Flow orchestration triggered when a skill call finds no
3
- * token. Only ever invoked AFTER resolvePrivToken() has confirmed all three
4
- * sources (flag / env / credential store) are empty, so the env-injected
5
- * (cloud) path never reaches this module — guaranteeing zero regression there.
6
- *
7
- * Flow (see design.md):
8
- * canAutoAuth gate → acquire lock
9
- * ├─ won : runDeviceFlow(short wait) → on approval re-read the store
10
- * └─ lost : wait for whoever holds the lock to write a credential
11
- *
12
- * Always bounded by `maxWaitMs` so it can never block a host tool call forever.
13
- */
14
- import { type ResolvedToken } from './resolve.js';
15
- /**
16
- * Attempt to obtain a token via automatic browser authorization. Returns the
17
- * resolved token on success, or null when auto auth is not applicable / did not
18
- * complete in time (callers then fail fast with NOT_AUTHENTICATED_HINT).
19
- */
20
- export declare function attemptAutoLogin(apiBaseUrl: string): Promise<ResolvedToken | null>;
@@ -1,8 +0,0 @@
1
- /**
2
- * `remixmate login | logout | whoami` command implementations.
3
- *
4
- * Security: the PrivToken value is never printed. Identity is shown by label only.
5
- * Env/CI behavior: when PRIV_TOKEN is set, `login` does not start the device flow.
6
- */
7
- /** Dispatch an auth verb. Returns a process exit code. */
8
- export declare function runAuthCommand(verb: string, rest: string[]): Promise<number>;
@@ -1,60 +0,0 @@
1
- /**
2
- * Local credential store for the remixmate CLI.
3
- *
4
- * Design (see .kiro/specs/cli-device-login/design.md):
5
- * - File store at ~/.config/remixmate/credentials.json (mode 0600) is the
6
- * REQUIRED, always-present implementation. Entries are keyed by API base
7
- * URL so multiple accounts / backends coexist without overwriting.
8
- * - The OS keychain is an OPTIONAL enhancement loaded via dynamic import().
9
- * When a keychain module is available we keep the secret PrivToken there
10
- * and store only non-secret metadata (+ a location marker) in the file.
11
- * When it is absent or fails at runtime, the PrivToken is stored in the
12
- * file instead. Either way the PrivToken value is never logged.
13
- *
14
- * No new runtime dependency is added: keychain support activates only if a
15
- * compatible module (`keytar`) is already importable in the host environment.
16
- */
17
- export declare const CRED_DIR: string;
18
- export declare const CRED_FILE: string;
19
- /** A resolved credential as seen by callers (token always populated). */
20
- export interface StoredCredential {
21
- privToken: string;
22
- userLabel?: string;
23
- createdAt: number;
24
- }
25
- /** Result of a remove, so callers (logout) can report partial failures. */
26
- export interface RemoveResult {
27
- removedFile: boolean;
28
- removedKeychain: boolean;
29
- keychainError?: string;
30
- fileError?: string;
31
- }
32
- /**
33
- * Persist a credential for `apiBaseUrl`. Prefers the keychain for the secret;
34
- * falls back to the file on absence or runtime failure. Returns whether the
35
- * keychain was used so callers can surface "keychain unavailable, used file".
36
- */
37
- export declare function setCredential(apiBaseUrl: string, cred: StoredCredential): Promise<{
38
- usedKeychain: boolean;
39
- keychainError?: string;
40
- }>;
41
- /** Read a credential for `apiBaseUrl`, or null when none is stored. */
42
- export declare function getCredential(apiBaseUrl: string): Promise<StoredCredential | null>;
43
- /** Non-secret summary of one stored entry, for diagnostics (`whoami`, `doctor`). */
44
- export interface CredentialSummary {
45
- apiBaseUrl: string;
46
- userLabel?: string;
47
- createdAt: number;
48
- secret: 'file' | 'keychain';
49
- }
50
- /**
51
- * List which backends have a stored credential, WITHOUT reading any secret.
52
- *
53
- * Credentials are keyed by API base URL, so "logged in" is per-backend. Without
54
- * this, a credential stored for a local dev backend while the CLI defaults to
55
- * production produced a bare "not logged in" — technically true, actively
56
- * misleading, and the exact confusion that motivated this work.
57
- */
58
- export declare function listCredentials(): Promise<CredentialSummary[]>;
59
- /** Remove a credential from both stores; reports per-store outcome. */
60
- export declare function removeCredential(apiBaseUrl: string): Promise<RemoveResult>;
@@ -1,58 +0,0 @@
1
- /**
2
- * Shared Device Flow orchestration used by BOTH `remixmate login` (interactive,
3
- * full device-code lifetime) and the automatic browser auth that kicks in when
4
- * a skill call finds no token (bounded by a short wait window).
5
- *
6
- * Centralizing the request → open browser → poll → persist sequence keeps the
7
- * two entry points byte-for-byte consistent (Requirement 4.1/4.2) and means the
8
- * credential written by auto-login is indistinguishable from `login`'s.
9
- *
10
- * Security: the PrivToken value is NEVER written to the provided sink.
11
- */
12
- import { type DeviceCodeResponse } from './device-flow.js';
13
- export declare function renderAuthBlock(apiBaseUrl: string, code: DeviceCodeResponse): string;
14
- /**
15
- * Emit the authorization prompt: a one-click link (device code pre-filled),
16
- * the manual fallback, and — for agent hosts — the machine-readable block.
17
- * Shared by `runDeviceFlow` and `login --start` so both entry points present
18
- * authorization identically.
19
- */
20
- export declare function writeAuthPrompt(write: (msg: string) => void, apiBaseUrl: string, code: DeviceCodeResponse): void;
21
- export interface DeviceFlowOptions {
22
- /**
23
- * Upper bound on how long to poll for approval. When omitted the device
24
- * code's own `expiresIn` is the only bound (used by interactive `login`).
25
- * When set (auto-login), a stop caused by this window is reported as
26
- * `'timeout'` rather than `'expired'`.
27
- */
28
- maxWaitMs?: number;
29
- /** Where human-facing progress text goes. Defaults to stderr. */
30
- write?: (msg: string) => void;
31
- /** Client label sent with the device-code request. */
32
- clientLabel?: string;
33
- }
34
- export type DeviceFlowOutcome = {
35
- status: 'approved';
36
- userLabel?: string;
37
- } | {
38
- status: 'timeout';
39
- } | {
40
- status: 'denied';
41
- } | {
42
- status: 'expired';
43
- } | {
44
- status: 'error';
45
- message: string;
46
- };
47
- /**
48
- * Persist an approved PrivToken. Shared by `runDeviceFlow` and `login --wait`
49
- * so a credential written by either is indistinguishable. The token value is
50
- * never written to `write`.
51
- */
52
- export declare function persistApproval(apiBaseUrl: string, privToken: string, userLabel: string | undefined, write: (msg: string) => void): Promise<void>;
53
- /**
54
- * Run one full Device Flow against `apiBaseUrl`. On approval the PrivToken is
55
- * persisted via setCredential (keychain or file, same as `login`) and only the
56
- * non-secret outcome is returned.
57
- */
58
- export declare function runDeviceFlow(apiBaseUrl: string, opts?: DeviceFlowOptions): Promise<DeviceFlowOutcome>;
@@ -1,52 +0,0 @@
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
- export interface DeviceCodeResponse {
9
- deviceCode: string;
10
- userCode: string;
11
- verificationUri: string;
12
- verificationUriComplete?: string;
13
- interval: number;
14
- expiresIn: number;
15
- }
16
- /** Device-token poll sub-states (mirrors backend model.DeviceTokenStatus*). */
17
- export type TokenStatus = 'authorization_pending' | 'slow_down' | 'approved' | 'access_denied' | 'expired_token' | 'invalid_grant';
18
- export interface TokenResult {
19
- status: TokenStatus;
20
- privToken?: string;
21
- userLabel?: string;
22
- /**
23
- * Set when polling stopped because the caller-supplied `maxWaitMs` window
24
- * elapsed BEFORE the device code's own expiry. Lets callers distinguish a
25
- * short-wait timeout ("authorize then re-run") from a real expired code.
26
- */
27
- timedOut?: boolean;
28
- }
29
- export declare class DeviceFlowError extends Error {
30
- }
31
- /** Request a fresh device_code/user_code. 30s timeout, no retries here. */
32
- export declare function requestDeviceCode(apiBaseUrl: string, clientLabel: string): Promise<DeviceCodeResponse>;
33
- /**
34
- * Try to open `url` in the default browser. Returns false when no opener is
35
- * available (caller then prints the URL for manual entry).
36
- */
37
- export declare function openBrowser(url: string): boolean;
38
- /**
39
- * Poll device/token until a terminal outcome. Honors `interval` (+5s on
40
- * slow_down), retries transient network failures up to 3 consecutive times, and
41
- * bounds total time by the device code's `expiresIn`.
42
- *
43
- * When `opts.maxWaitMs` is supplied, polling also stops once that window
44
- * elapses (whichever comes first). A stop caused by `maxWaitMs` returns
45
- * `{ status: 'expired_token', timedOut: true }` so callers can tell a
46
- * short-wait timeout apart from a genuinely expired device code.
47
- *
48
- * Resolves with the final TokenResult (status 'approved' carries the token).
49
- */
50
- export declare function pollForToken(apiBaseUrl: string, code: DeviceCodeResponse, opts?: {
51
- maxWaitMs?: number;
52
- }): Promise<TokenResult>;
@@ -1,55 +0,0 @@
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 { type CredentialSource } from './resolve.js';
16
- /**
17
- * Per-skill authorization requirement, declared as `auth` in skill.json.
18
- *
19
- * - `required`: no token → run the device flow; still no token → abort (exit 4).
20
- * - `optional`: inject a token when one is already available, otherwise carry
21
- * on silently. Never opens a browser — these skills degrade to a
22
- * reduced mode (e.g. web-record keeps the file locally instead of
23
- * uploading), and popping an auth window for that would be rude.
24
- * - `none`: purely local, never touches ab-api (e.g. web-screenshot).
25
- */
26
- export type AuthMode = 'required' | 'optional' | 'none';
27
- export declare const AUTH_MODE_VALUES: readonly ["required", "optional", "none"];
28
- export interface AuthPreflight {
29
- /** Backend the invocation is bound to; injected so Python derives the same one. */
30
- apiBaseUrl: string;
31
- /** Absent only for `none`, or for `optional` with no credential available. */
32
- token?: string;
33
- source?: CredentialSource;
34
- userLabel?: string;
35
- }
36
- /**
37
- * Resolve (and if necessary acquire) a PrivToken for one skill invocation.
38
- * Throws SkillError(exit 4) when `mode === 'required'` and no token could be
39
- * obtained; the message is the shared NOT_AUTHENTICATED_HINT.
40
- */
41
- export declare function ensureAuth(opts: {
42
- mode: AuthMode;
43
- apiBaseUrl?: string;
44
- flagToken?: string;
45
- }): Promise<AuthPreflight>;
46
- /**
47
- * Environment overrides handed to a spawned Python skill.
48
- *
49
- * `MM_API_BASE_URL` is always set so the child resolves the same backend the
50
- * preflight authenticated against — otherwise a `--api-base-url` flag or a
51
- * credential bound to a staging backend would silently disagree with the URL
52
- * the Python side derives. `PRIV_TOKEN` is only set when non-empty, so an
53
- * `optional` skill without a credential keeps inheriting whatever the host set.
54
- */
55
- export declare function authChildEnv(pre: AuthPreflight): Record<string, string>;