@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.
- package/LICENSE +45 -21
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/argv.js +1 -51
- package/dist/auth/auth-lock.js +1 -100
- package/dist/auth/auto-login.js +7 -75
- package/dist/auth/commands.js +40 -290
- package/dist/auth/credential-store.js +1 -143
- package/dist/auth/device-flow-runner.js +13 -110
- package/dist/auth/device-flow.js +1 -132
- package/dist/auth/ensure.js +1 -62
- package/dist/auth/environment.js +1 -69
- package/dist/auth/pending-store.js +1 -45
- package/dist/auth/resolve.js +1 -44
- package/dist/billing.js +3 -76
- package/dist/capabilities.js +1 -75
- package/dist/cli.js +39 -211
- package/dist/diagnostics.js +2 -70
- package/dist/doctor.js +9 -188
- package/dist/errors.js +1 -33
- package/dist/exec.js +4 -47
- package/dist/handlers/gen-digital-human.js +33 -264
- package/dist/handlers/gen-image.js +9 -113
- package/dist/handlers/gen-post.js +15 -192
- package/dist/handlers/gen-video.js +7 -185
- package/dist/handlers/gen-voice.js +19 -133
- package/dist/handlers/index.js +1 -20
- package/dist/handlers/shared.js +1 -66
- package/dist/handlers/template-init.js +1 -21
- package/dist/http.js +3 -155
- package/dist/index.js +1 -8
- package/dist/install.js +40 -448
- package/dist/manifest.json +44 -3
- package/dist/progress.js +2 -15
- package/dist/project/commands.js +34 -156
- package/dist/project/host.js +1 -151
- package/dist/project/resolve.js +1 -65
- package/dist/project/store.js +2 -97
- package/dist/project/take.js +1 -106
- package/dist/project/trace.js +5 -278
- package/dist/registry.js +3 -70
- package/dist/runner.js +7 -225
- package/dist/skill-schema.js +1 -194
- package/dist/template/args.js +1 -69
- package/dist/template/check.js +2 -30
- package/dist/template/commands.js +34 -163
- package/dist/template/constants.js +1 -46
- package/dist/template/init.js +5 -133
- package/dist/template/list.js +1 -46
- package/dist/template/preview.js +7 -318
- package/dist/template/proc.js +1 -43
- package/dist/template/project.js +6 -161
- package/dist/template/publish.js +5 -185
- package/dist/template/render-client.js +2 -79
- package/dist/template/upgrade.js +4 -123
- package/dist/text.js +1 -15
- package/package.json +2 -35
- package/skills/doc-parse/scripts/extract_via_render.py +32 -32
- package/skills/export-jianying/scripts/gen_jianying_draft.py +188 -188
- package/skills/gen-script/SKILL.md +23 -1
- package/skills/gen-script/scripts/gen_script.py +644 -551
- package/skills/gen-script/skill.json +12 -0
- package/skills/gen-script/version.json +1 -1
- package/skills/prepare-video-assets/scripts/prepare_video_assets.py +21 -21
- package/skills/render-video/scripts/_chrome_vendor.py +17 -17
- package/skills/render-video/scripts/_digital_human_segments.py +34 -34
- package/skills/render-video/scripts/_video_probe.py +33 -33
- package/skills/render-video/scripts/_vod_polling.py +13 -13
- package/skills/render-video/scripts/remote_renderer_client.py +42 -42
- package/skills/render-video/scripts/render_video.py +639 -639
- package/skills/render-video/scripts/required_props.py +37 -37
- package/skills/render-video/scripts/upload_video.py +35 -35
- package/skills/render-video/version.json +1 -1
- package/skills/template-registry/SKILL.md +16 -0
- package/skills/template-registry/scripts/countdown_spec.py +92 -92
- package/skills/template-registry/scripts/doc_extract.py +40 -40
- package/skills/template-registry/scripts/list_templates.py +303 -191
- package/skills/template-registry/scripts/log_diagnostics.py +1 -1
- package/skills/template-registry/scripts/match_template.py +87 -87
- package/skills/template-registry/scripts/payload_hydrator.py +89 -89
- package/skills/template-registry/scripts/registry_loader.py +159 -159
- package/skills/template-registry/scripts/render_job_client.py +75 -75
- package/skills/template-registry/scripts/showcase_loader.py +44 -44
- package/skills/template-registry/skill.json +4 -1
- package/skills/template-registry/version.json +1 -1
- package/skills/template-registry/video_dsl/runtime/__init__.py +8 -8
- package/skills/template-registry/video_dsl/runtime/dsl_validator.py +329 -329
- package/skills/template-registry/video_dsl/runtime/narration_budget.py +36 -36
- package/skills/template-registry/video_dsl/runtime/payload_contract.py +7 -7
- package/skills/template-registry/video_dsl/runtime/prompt_enhancer.py +98 -98
- package/skills/template-registry/video_dsl/runtime/timeline_compiler.py +127 -127
- package/skills/template-registry/video_dsl/schema/video-dsl-v1alpha1.json +14 -1
- package/skills/video-parser/scripts/analyze_video.py +173 -173
- package/skills/video-parser/scripts/deconstruct_video.py +91 -91
- package/skills/video-parser/scripts/parse_via_render.py +21 -21
- package/skills/web-screenshot/scripts/_media_screenshot/__init__.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/bootstrap.py +7 -7
- package/skills/web-screenshot/scripts/_media_screenshot/browser.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/cli_args.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/js_loader.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/overlay.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/reader.py +40 -40
- package/skills/web-screenshot/scripts/_media_screenshot/recording.py +11 -11
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/__init__.py +8 -8
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/arrow.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/caption.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/click.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/code_lines.py +5 -5
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/focus.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/highlight.py +3 -3
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/hover.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/redact.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/scroll.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/sequence.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/title_card.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/type.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/wait.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/zoom.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/screenshot.py +15 -15
- package/skills/web-screenshot/scripts/_media_screenshot/storyboard.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/template.py +11 -11
- package/skills/web-screenshot/scripts/_media_screenshot/trim.py +10 -10
- package/skills/web-screenshot/scripts/_media_screenshot/urlguard.py +22 -22
- package/skills/web-screenshot/scripts/_media_upload.py +33 -33
- package/skills/web-screenshot/scripts/read_page.py +15 -15
- package/skills/web-screenshot/scripts/record.py +26 -26
- package/skills/web-screenshot/scripts/screenshot.py +5 -5
- package/dist/argv.d.ts +0 -15
- package/dist/auth/auth-lock.d.ts +0 -26
- package/dist/auth/auto-login.d.ts +0 -20
- package/dist/auth/commands.d.ts +0 -8
- package/dist/auth/credential-store.d.ts +0 -60
- package/dist/auth/device-flow-runner.d.ts +0 -58
- package/dist/auth/device-flow.d.ts +0 -52
- package/dist/auth/ensure.d.ts +0 -55
- package/dist/auth/environment.d.ts +0 -38
- package/dist/auth/pending-store.d.ts +0 -33
- package/dist/auth/resolve.d.ts +0 -30
- package/dist/billing.d.ts +0 -44
- package/dist/capabilities.d.ts +0 -63
- package/dist/cli.d.ts +0 -13
- package/dist/diagnostics.d.ts +0 -24
- package/dist/doctor.d.ts +0 -22
- package/dist/errors.d.ts +0 -29
- package/dist/exec.d.ts +0 -18
- package/dist/handlers/gen-digital-human.d.ts +0 -17
- package/dist/handlers/gen-image.d.ts +0 -20
- package/dist/handlers/gen-post.d.ts +0 -22
- package/dist/handlers/gen-video.d.ts +0 -19
- package/dist/handlers/gen-voice.d.ts +0 -26
- package/dist/handlers/index.d.ts +0 -20
- package/dist/handlers/shared.d.ts +0 -26
- package/dist/handlers/template-init.d.ts +0 -10
- package/dist/http.d.ts +0 -72
- package/dist/index.d.ts +0 -9
- package/dist/install.d.ts +0 -71
- package/dist/progress.d.ts +0 -23
- package/dist/project/commands.d.ts +0 -19
- package/dist/project/host.d.ts +0 -91
- package/dist/project/resolve.d.ts +0 -43
- package/dist/project/store.d.ts +0 -31
- package/dist/project/take.d.ts +0 -50
- package/dist/project/trace.d.ts +0 -98
- package/dist/registry.d.ts +0 -30
- package/dist/runner.d.ts +0 -39
- package/dist/skill-schema.d.ts +0 -138
- package/dist/template/args.d.ts +0 -25
- package/dist/template/check.d.ts +0 -23
- package/dist/template/commands.d.ts +0 -25
- package/dist/template/constants.d.ts +0 -39
- package/dist/template/init.d.ts +0 -28
- package/dist/template/list.d.ts +0 -23
- package/dist/template/preview.d.ts +0 -60
- package/dist/template/proc.d.ts +0 -26
- package/dist/template/project.d.ts +0 -47
- package/dist/template/publish.d.ts +0 -43
- package/dist/template/render-client.d.ts +0 -39
- package/dist/template/upgrade.d.ts +0 -44
- package/dist/text.d.ts +0 -8
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
+
|
|
87
87
|
"role": "screen_record",
|
|
88
88
|
}
|
|
89
89
|
if title:
|
|
90
90
|
query["title"] = title
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
204
204
|
base = _base_url(base_url)
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
274
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
17
|
-
from _media_screenshot.reader import do_read
|
|
18
|
-
from _media_screenshot.urlguard import assert_public_url
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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;
|
package/dist/auth/auth-lock.d.ts
DELETED
|
@@ -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>;
|
package/dist/auth/commands.d.ts
DELETED
|
@@ -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>;
|
package/dist/auth/ensure.d.ts
DELETED
|
@@ -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>;
|