bilisum 1.20.0-alpha.3 → 1.20.0-beta.1

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/README.md CHANGED
@@ -43,6 +43,8 @@ bilisum summarize --help
43
43
  ```bash
44
44
  bilisum env # 查看环境状态
45
45
  bilisum env use cli # 切换到独立环境
46
+ bilisum desktop # 快捷切换到桌面端环境
47
+ bilisum auto # 恢复自动选择
46
48
  bilisum env setup # 初始化独立环境(内置运行时,需 Python 3.12)
47
49
  bilisum --setting # 打开当前环境的网页设置(cli 环境无需桌面端)
48
50
  bilisum summarize <url> --environment cli # 单次使用某环境
@@ -60,14 +62,14 @@ bilisum summarize <url> --environment cli # 单次使用某环境
60
62
 
61
63
  ## 它如何"控制现有程序"
62
64
 
63
- - 默认连接桌面端服务 `http://127.0.0.1:3838`,任务、结果、知识库都与桌面端**同一个数据库**。
65
+ - `desktop` 环境连接桌面端服务 `http://127.0.0.1:3838`,任务、结果、知识库都与桌面端**同一个数据库**;默认 `auto` 会优先探测 desktop,不可达时使用 cli 独立环境。
64
66
  - 访问令牌自动获取:`--token` → `VIDEO_SUM_ACCESS_TOKEN` → 桌面端自己的 token 文件(`%APPDATA%\BiliSum\access-token.json` 等)→ 服务侧 `{数据根}\data\auth.json`。桌面端正在运行时通常无需任何配置。
65
- - 数据根默认与桌面端一致(Windows `%LOCALAPPDATA%\bilisum`,macOS `~/Library/Application Support/bilisum`),可用 `--data` / `BILISUM_DATA_ROOT` 覆盖。
67
+ - desktop 数据根默认与桌面端一致(Windows `%LOCALAPPDATA%\bilisum`,macOS `~/Library/Application Support/bilisum`);cli 使用独立的 `CLI_HOME`。
66
68
  - 配置(LLM / ASR Key 等)跟随服务端设置,桌面端里配好了,CLI 直接用。
67
69
 
68
70
  ## 服务不在线时
69
71
 
70
- `summarize / brief / transcribe / status / tasks` 会先探活;如果 `127.0.0.1:3838` 没有服务,就**用桌面端数据目录自动后台拉起一个**(优先用桌面端自带的托管 Python 运行时,其次系统 Python 3.12),等 `/health` 就绪后继续。CLI 拉起的服务进程记录在 `{数据根}\cli-runtime-<port>.json`:
72
+ `summarize / brief / transcribe / status / tasks` 会先探活当前环境。desktop 不可达时用桌面端数据目录拉起服务,cli 不可达时用 CLI 独立数据目录和 venv 拉起服务;custom 只连接指定地址,不会在本机拉起替代服务。CLI 拉起的服务进程记录在 `{数据根}\cli-runtime-<port>.json`:
71
73
 
72
74
  ```bash
73
75
  bilisum start --no-open # 前台启动(可选,一般不用)
@@ -84,13 +86,30 @@ bilisum stop # 停止 CLI 拉起的服务
84
86
  npm install -g bilisum # 或 npx bilisum ...
85
87
  ```
86
88
 
87
- 包内**不包含 Python 运行时**(CLI 是纯客户端),首次使用要求机器上已有桌面版 BiliSum 或 Python 3.12。
89
+ 包内包含构建独立环境所需的 Python 源码;首次执行 `bilisum env setup` 仍要求系统已有 Python 3.12。
90
+ ## Agent 视频理解 Skill
88
91
 
92
+ 安装 CLI 后,可以把随包提供的 `bilisum-video-understanding` skill 安装给 Agent:
93
+
94
+ ```bash
95
+ # 从 GitHub 安装到当前项目或指定 Agent
96
+ npx skills add https://github.com/lycohana/BiliSum --skill bilisum-video-understanding
97
+
98
+ # 使用 BiliSum CLI 自带的交互式安装器
99
+ bilisum skill install
100
+
101
+ # 非交互环境下显式指定目标
102
+ bilisum skill install --project
103
+ bilisum skill install --global
104
+ bilisum skill install --path ./agent-skills/bilisum-video-understanding
105
+ ```
106
+
107
+ `bilisum skill install` 在终端中会让你选择当前项目、Codex 全局目录或自定义目录;被 Agent/CI 调用且未指定目标时只打印路径,不会写入文件。已有 skill 需要更新时加 `--force`。安装后,Agent 可以根据用户请求调用 `summarize`、`brief`、`transcribe` 和 `status` 理解视频。
89
108
  ## 常用选项
90
109
 
91
110
  | 选项 | 说明 |
92
111
  |------|------|
93
- | `--host / --port` | 服务地址(默认 `127.0.0.1:3838`) |
112
+ | `--host / --port` | 自定义服务地址(显式指定时进入 custom 环境) |
94
113
  | `--data <path>` | 桌面端数据根(默认 `%LOCALAPPDATA%\bilisum`) |
95
114
  | `--token <token>` | 访问令牌(默认自动读桌面端 token) |
96
115
  | `--format json\|markdown\|transcript` | 输出格式,默认 markdown |
@@ -120,4 +139,5 @@ node "<安装目录>\resources\cli\bin\bilisum.js" summarize "https://..."
120
139
  ```bash
121
140
  npm test --prefix packages/npx # node:test 单元测试
122
141
  npm run npx:test # 版本 + 单测 + npm pack 校验(根目录)
142
+ node packages/npx/bin/bilisum.js --setting # 可直接从仓库运行;自动使用仓库内 runtime/web 资源
123
143
  ```
package/bin/bilisum.js CHANGED
@@ -18,6 +18,7 @@ const {
18
18
  const { UsageError, parseTaskCommandArgs } = require("../lib/args");
19
19
  const { formatTasks, emit } = require("../lib/output");
20
20
  const { readVersion, locateBilisumDataRoot, findPython, cliHomeDir, ensureCliVenv, cliVenvPython, findSystemPython } = require("../lib/runtime");
21
+ const { runSkillCommand } = require("../lib/skill");
21
22
  const { readToken } = require("../lib/token");
22
23
  const {
23
24
  ensureService,
@@ -51,16 +52,18 @@ function printHelp() {
51
52
  console.log(" npx bilisum status <task-id> [--json] Show task status");
52
53
  console.log(" npx bilisum tasks [--limit N] [--json] List tasks");
53
54
  console.log(" npx bilisum env [use <name>|setup] Environment status / switch / setup");
55
+ console.log(" npx bilisum desktop|cli|custom|auto Switch environment (shortcut)");
54
56
  console.log(" npx bilisum --setting Open the web settings page");
55
57
  console.log(" npx bilisum start [options] Start the service");
56
58
  console.log(" npx bilisum stop Stop the CLI-started BiliSum service");
57
59
  console.log(" npx bilisum doctor Check environment/service/Python setup");
60
+ console.log(" npx bilisum skill [path|install] Install the Agent video-understanding skill");
58
61
  console.log(" npx bilisum --version Print package version");
59
62
  console.log(" npx bilisum release Open the latest GitHub release");
60
63
  console.log("");
61
64
  console.log("环境(bilisum env):desktop 连桌面端服务 / cli 独立环境(自带运行时)/ custom 自定义 / auto 自动");
62
65
  console.log("");
63
- console.log("默认连接桌面端服务 http://127.0.0.1:3838;未运行时自动用桌面端数据目录后台拉起。");
66
+ console.log("默认按已保存的环境连接;auto 模式优先桌面端,否则使用 CLI 独立环境。");
64
67
  console.log("");
65
68
  console.log("Options (all commands):");
66
69
  console.log(" --host <host> Host, default 127.0.0.1");
@@ -132,7 +135,7 @@ function parseConnectionOptions(args, { allowTaskId = false } = {}) {
132
135
  } else if (arg === "--token") {
133
136
  options.token = readValue(++index, arg);
134
137
  } else if (arg === "--environment" || arg === "--env-mode") {
135
- const name = readValue(++index, arg);
138
+ const name = readValue(++index, arg).toLowerCase();
136
139
  if (!ENV_NAMES.includes(name)) {
137
140
  throw new UsageError(`--environment 仅支持 ${ENV_NAMES.join(" / ")},收到:${name}`);
138
141
  }
@@ -204,7 +207,8 @@ async function ensureAuthenticatedService(options, log) {
204
207
  if (!token) {
205
208
  throw new UsageError(
206
209
  "无法获取访问令牌:请用 --token 指定,或设置 VIDEO_SUM_ACCESS_TOKEN," +
207
- "或先启动一次桌面端 BiliSum 生成令牌。",
210
+ "或先启动一次桌面端 BiliSum 生成令牌;没有桌面端时可使用 --environment cli。",
211
+ { showHelp: false },
208
212
  );
209
213
  }
210
214
  return token;
@@ -514,11 +518,12 @@ async function envCommand(args) {
514
518
 
515
519
  if (sub === "use") {
516
520
  const name = String(args[1] || "").toLowerCase();
517
- if (!ENV_NAMES.includes(name) || name === "auto") {
518
- throw new UsageError(`用法:bilisum env use ${ENV_NAMES.filter((item) => item !== "auto").join("|")}`);
521
+ if (!ENV_NAMES.includes(name) || args.length !== 2) {
522
+ throw new UsageError(`用法:bilisum env use ${ENV_NAMES.join("|")}`);
519
523
  }
520
524
  writeConfig({ ...config, env: name });
521
- console.log(`已切换到 ${name} 环境。`);
525
+ const suffix = name === "auto" ? "(桌面端可用时优先 desktop,否则 cli)" : "";
526
+ console.log(`已切换到 ${name} 环境${suffix}。`);
522
527
  return;
523
528
  }
524
529
 
@@ -534,6 +539,10 @@ async function envCommand(args) {
534
539
  return;
535
540
  }
536
541
 
542
+ if (sub) {
543
+ throw new UsageError(`未知 env 子命令:${sub}`);
544
+ }
545
+
537
546
  // default: status
538
547
  const current = await resolveEnvironment({ environment: config.env || "auto" });
539
548
  const desktopUp = await isDesktopReachable();
@@ -548,7 +557,7 @@ async function envCommand(args) {
548
557
  console.log(` custom ${config.custom ? "● 已配置" : "○ 未配置(--host/--port/--token)"}`);
549
558
  console.log(` auto desktop 优先,否则 cli`);
550
559
  console.log("");
551
- console.log("切换:bilisum env use desktop|cli|custom");
560
+ console.log("切换:bilisum env use desktop|cli|custom|auto(也可直接 bilisum desktop|cli|custom|auto)");
552
561
  if (config.env && config.env !== "auto") {
553
562
  console.log(`已持久化选择:${config.env}`);
554
563
  }
@@ -557,7 +566,8 @@ async function envCommand(args) {
557
566
  function printEnvHelp() {
558
567
  console.log("Usage:");
559
568
  console.log(" bilisum env Show environment status");
560
- console.log(" bilisum env use <name> Switch environment (desktop|cli|custom)");
569
+ console.log(" bilisum env use <name> Switch environment (desktop|cli|custom|auto)");
570
+ console.log(" bilisum <name> Shortcut for `bilisum env use <name>`");
561
571
  console.log(" bilisum env setup Initialize the standalone cli environment");
562
572
  console.log("");
563
573
  console.log("Environments:");
@@ -619,6 +629,13 @@ async function main() {
619
629
  await envCommand(args);
620
630
  return;
621
631
  }
632
+ if (ENV_NAMES.includes(command)) {
633
+ if (args.length > 0) {
634
+ throw new UsageError(`用法:bilisum ${command}`);
635
+ }
636
+ await envCommand(["use", command]);
637
+ return;
638
+ }
622
639
  if (command === "settings" || command === "--setting" || command === "config") {
623
640
  await settingsCommand(args);
624
641
  return;
@@ -633,6 +650,10 @@ async function main() {
633
650
  await doctor(args);
634
651
  return;
635
652
  }
653
+ if (command === "skill") {
654
+ await runSkillCommand(args);
655
+ return;
656
+ }
636
657
  if (command === "help" || command === "-h" || command === "--help") {
637
658
  printHelp();
638
659
  return;
@@ -653,7 +674,7 @@ async function main() {
653
674
  } catch (error) {
654
675
  const message = error instanceof Error ? error.message : String(error);
655
676
  console.error(message);
656
- if (error instanceof UsageError) {
677
+ if (error instanceof UsageError && error.showHelp) {
657
678
  console.error("");
658
679
  printHelp();
659
680
  }
package/lib/args.js CHANGED
@@ -8,9 +8,10 @@ const { locateBilisumDataRoot } = require("./runtime");
8
8
  const { ENV_NAMES } = require("./env");
9
9
 
10
10
  class UsageError extends Error {
11
- constructor(message) {
11
+ constructor(message, { showHelp = true } = {}) {
12
12
  super(message);
13
13
  this.name = "UsageError";
14
+ this.showHelp = showHelp;
14
15
  }
15
16
  }
16
17
 
@@ -74,7 +75,7 @@ function parseTaskCommandArgs(args, { defaultFormat = "markdown" } = {}) {
74
75
  } else if (arg === "--python") {
75
76
  options.python = readValue(args, ++index, arg);
76
77
  } else if (arg === "--environment" || arg === "--env-mode") {
77
- const name = readValue(args, ++index, arg);
78
+ const name = readValue(args, ++index, arg).toLowerCase();
78
79
  if (!ENV_NAMES.includes(name)) {
79
80
  throw new UsageError(`--environment 仅支持 ${ENV_NAMES.join(" / ")},收到:${name}`);
80
81
  }
package/lib/env.js CHANGED
@@ -13,8 +13,8 @@
13
13
  * - auto (default): probe 127.0.0.1:3838 — reachable => desktop, else cli.
14
14
  *
15
15
  * Selection precedence:
16
- * 1. --environment desktop|cli|custom (one-shot override)
17
- * 2. explicit --host/--port (treated as a custom target)
16
+ * 1. explicit --host/--port (treated as a custom target)
17
+ * 2. --environment desktop|cli|custom|auto (one-shot override)
18
18
  * 3. config.env persisted by `bilisum env use <name>`
19
19
  * 4. auto
20
20
  */
@@ -74,20 +74,29 @@ async function resolveEnvironment(options = {}) {
74
74
  const config = readConfig();
75
75
  const explicitHost = Boolean(options._hostSet);
76
76
  const explicitPort = Boolean(options._portSet);
77
- const flag = normalizeEnvName(options.environment);
77
+ const rawFlag = String(options.environment || "").trim();
78
+ const flag = normalizeEnvName(rawFlag);
78
79
 
79
- let env;
80
+ if (rawFlag && !flag) {
81
+ throw new Error(`未知环境:${rawFlag}。可选值:${ENV_NAMES.join(" / ")}`);
82
+ }
83
+
84
+ let requestedEnv;
80
85
  if (explicitHost || explicitPort) {
81
86
  // Explicit connection target always wins (custom).
82
- env = "custom";
83
- } else if (flag && flag !== "auto") {
84
- env = flag;
87
+ requestedEnv = "custom";
88
+ } else if (flag) {
89
+ // An explicit `auto` is a real one-shot override. It must not fall back to
90
+ // a persisted desktop/cli/custom selection.
91
+ requestedEnv = flag;
85
92
  } else {
86
- env = currentEnvName(config);
87
- if (env === "auto") {
88
- const probe = options._desktopProbe || isDesktopReachable;
89
- env = (await probe()) ? "desktop" : "cli";
90
- }
93
+ requestedEnv = currentEnvName(config);
94
+ }
95
+
96
+ let env = requestedEnv;
97
+ if (env === "auto") {
98
+ const probe = options._desktopProbe || isDesktopReachable;
99
+ env = (await probe()) ? "desktop" : "cli";
91
100
  }
92
101
 
93
102
  let host;
package/lib/runtime.js CHANGED
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  const { existsSync, mkdirSync, readFileSync, writeFileSync } = require("node:fs");
15
- const { join } = require("node:path");
15
+ const { join, resolve } = require("node:path");
16
16
  const { spawnSync } = require("node:child_process");
17
17
 
18
18
  const PACKAGE_ROOT = join(__dirname, "..");
@@ -139,9 +139,47 @@ function findPython(dataRoot) {
139
139
  return findSystemPython();
140
140
  }
141
141
 
142
- /** Bundled Python sources inside the npm package (prepack copies these). */
142
+ function isRuntimeSourceRoot(path) {
143
+ return existsSync(join(path, "apps", "service", "pyproject.toml"));
144
+ }
145
+
146
+ /**
147
+ * Python/runtime sources for both supported layouts:
148
+ * - published package: packages/npx/runtime (created by prepack)
149
+ * - repository checkout: repository root (runtime is removed by postpack)
150
+ */
143
151
  function runtimeSourceDir() {
144
- return join(PACKAGE_ROOT, "runtime");
152
+ const packagedRoot = join(PACKAGE_ROOT, "runtime");
153
+ if (isRuntimeSourceRoot(packagedRoot)) {
154
+ return packagedRoot;
155
+ }
156
+
157
+ const repositoryRoot = resolve(PACKAGE_ROOT, "..", "..");
158
+ if (isRuntimeSourceRoot(repositoryRoot)) {
159
+ return repositoryRoot;
160
+ }
161
+
162
+ // Keep the packaged path in error messages for an incomplete npm install.
163
+ return packagedRoot;
164
+ }
165
+
166
+ /**
167
+ * Resolve the installable Agent Skill from the published package or the
168
+ * repository checkout used during development.
169
+ */
170
+ function skillSourceDir() {
171
+ const packagedSkill = join(PACKAGE_ROOT, "skill", "bilisum-video-understanding");
172
+ if (existsSync(join(packagedSkill, "SKILL.md"))) {
173
+ return packagedSkill;
174
+ }
175
+
176
+ const repositoryRoot = resolve(PACKAGE_ROOT, "..", "..");
177
+ const repositorySkill = join(repositoryRoot, ".agents", "skills", "bilisum-video-understanding");
178
+ if (existsSync(join(repositorySkill, "SKILL.md"))) {
179
+ return repositorySkill;
180
+ }
181
+
182
+ return packagedSkill;
145
183
  }
146
184
 
147
185
  /** CLI venv lives under CLI_HOME/venv. */
@@ -225,6 +263,7 @@ module.exports = {
225
263
  findSystemPython,
226
264
  findPython,
227
265
  runtimeSourceDir,
266
+ skillSourceDir,
228
267
  cliVenvDir,
229
268
  cliVenvPython,
230
269
  ensureRuntime,
package/lib/server.js CHANGED
@@ -275,10 +275,17 @@ function buildStartupFailureHint(logPath) {
275
275
  return `详细日志:${logPath}`;
276
276
  }
277
277
 
278
+ function buildAutoStartMessage(options) {
279
+ const url = baseUrlFor(options.host, options.port);
280
+ const dataRootLabel = options.environment === "cli" ? "CLI 独立数据目录" : "桌面端数据目录";
281
+ return `BiliSum 服务未在 ${url} 运行,正在用 ${dataRootLabel} 后台启动...`;
282
+ }
283
+
278
284
  /**
279
- * Make sure a BiliSum service is reachable. If none is running, start one
280
- * headlessly sharing the desktop data root. When `accessToken` is known it is
281
- * injected into the spawned service so CLI and service always agree.
285
+ * Make sure a BiliSum service is reachable. If none is running, start a local
286
+ * service for the desktop/cli environments. A custom target is connection-only
287
+ * and is never replaced with an unrelated local process. When `accessToken` is
288
+ * known it is injected into the spawned service so CLI and service always agree.
282
289
  * Returns { started, payload }.
283
290
  */
284
291
  async function ensureService(options, { startupTimeoutMs = DEFAULT_STARTUP_TIMEOUT_MS, log = console.error, accessToken = null } = {}) {
@@ -287,7 +294,13 @@ async function ensureService(options, { startupTimeoutMs = DEFAULT_STARTUP_TIMEO
287
294
  return { started: false, payload: await health(url) };
288
295
  }
289
296
 
290
- log(`BiliSum 服务未在 ${url} 运行,正在用桌面端数据目录后台启动...`);
297
+ if (options.environment === "custom") {
298
+ throw new Error(
299
+ `无法连接自定义 BiliSum 服务(${url})。请确认服务已启动,并检查 --host/--port 配置。`,
300
+ );
301
+ }
302
+
303
+ log(buildAutoStartMessage(options));
291
304
  const { child, logPath } = spawnService(options, { background: true, accessToken });
292
305
  const controller = new AbortController();
293
306
  const earlyExit = new Promise((resolve) => {
@@ -396,4 +409,5 @@ module.exports = {
396
409
  cliServiceLogPath,
397
410
  readServiceSettings,
398
411
  assertSpawnablePort,
412
+ buildAutoStartMessage,
399
413
  };
package/lib/skill.js ADDED
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+
3
+ const { copyFileSync, existsSync, mkdirSync, statSync } = require("node:fs");
4
+ const { homedir } = require("node:os");
5
+ const { createInterface } = require("node:readline/promises");
6
+ const { dirname, join, resolve } = require("node:path");
7
+
8
+ const { UsageError } = require("./args");
9
+ const { skillSourceDir } = require("./runtime");
10
+
11
+ const SKILL_NAME = "bilisum-video-understanding";
12
+ const MANAGED_FILES = ["SKILL.md", join("agents", "openai.yaml")];
13
+
14
+ function projectSkillDir(cwd = process.cwd()) {
15
+ return resolve(cwd, ".agents", "skills", SKILL_NAME);
16
+ }
17
+
18
+ function globalSkillDir() {
19
+ const codexHome = String(process.env.CODEX_HOME || "").trim();
20
+ const codexRoot = codexHome || join(homedir(), ".codex");
21
+ return resolve(codexRoot, "skills", SKILL_NAME);
22
+ }
23
+
24
+ function destinationForMode(mode, cwd = process.cwd()) {
25
+ if (mode === "project") {
26
+ return projectSkillDir(cwd);
27
+ }
28
+ if (mode === "global") {
29
+ return globalSkillDir();
30
+ }
31
+ throw new UsageError(`未知 skill 安装范围:${mode}`);
32
+ }
33
+
34
+ function parseInstallArgs(args) {
35
+ const options = {
36
+ mode: "",
37
+ path: "",
38
+ force: false,
39
+ help: false,
40
+ };
41
+
42
+ for (let index = 0; index < args.length; index += 1) {
43
+ const arg = args[index];
44
+ if (arg === "--project") {
45
+ if (options.mode || options.path) {
46
+ throw new UsageError("--project、--global 与 --path 只能选择一个。");
47
+ }
48
+ options.mode = "project";
49
+ } else if (arg === "--global") {
50
+ if (options.mode || options.path) {
51
+ throw new UsageError("--project、--global 与 --path 只能选择一个。");
52
+ }
53
+ options.mode = "global";
54
+ } else if (arg === "--path") {
55
+ const value = args[++index];
56
+ if (!value || value.startsWith("--")) {
57
+ throw new UsageError("--path requires a value");
58
+ }
59
+ if (options.mode || options.path) {
60
+ throw new UsageError("--project、--global 与 --path 只能选择一个。");
61
+ }
62
+ options.path = value;
63
+ } else if (arg === "--force") {
64
+ options.force = true;
65
+ } else if (arg === "--help" || arg === "-h") {
66
+ options.help = true;
67
+ } else {
68
+ throw new UsageError(`未知 skill 安装选项:${arg}`);
69
+ }
70
+ }
71
+
72
+ return options;
73
+ }
74
+
75
+ function printSkillHelp() {
76
+ console.log("Usage:");
77
+ console.log(" bilisum skill path");
78
+ console.log(" bilisum skill install [--project | --global | --path <dir>] [--force]");
79
+ console.log("");
80
+ console.log("Commands:");
81
+ console.log(" path Print the bundled video-understanding skill directory");
82
+ console.log(" install Install the skill interactively or copy to an explicit target");
83
+ console.log("");
84
+ console.log("Install behavior:");
85
+ console.log(" TTY Choose project, Codex global, or a custom path interactively");
86
+ console.log(" non-TTY Preview source and destinations without writing files");
87
+ console.log(" --project Install to ./.agents/skills/ in the current project");
88
+ console.log(" --global Install to $CODEX_HOME/skills/ or ~/.codex/skills/");
89
+ console.log(" --path Install to the exact directory supplied");
90
+ console.log(" --force Overwrite managed files in an existing skill directory");
91
+ }
92
+
93
+ function assertSkillSource(source) {
94
+ if (!existsSync(join(source, "SKILL.md"))) {
95
+ throw new Error(`Bundled BiliSum skill is missing: ${source}`);
96
+ }
97
+ for (const relativePath of MANAGED_FILES) {
98
+ if (!existsSync(join(source, relativePath))) {
99
+ throw new Error(`Bundled BiliSum skill file is missing: ${join(source, relativePath)}`);
100
+ }
101
+ }
102
+ }
103
+
104
+ function installSkill(target, { source = skillSourceDir(), force = false } = {}) {
105
+ assertSkillSource(source);
106
+ const destination = resolve(target);
107
+ if (existsSync(destination)) {
108
+ if (!statSync(destination).isDirectory()) {
109
+ throw new UsageError(`skill 安装目标不是目录:${destination}`);
110
+ }
111
+ if (!force) {
112
+ throw new UsageError(`skill 安装目标已存在:${destination}。如需更新请加 --force。`);
113
+ }
114
+ }
115
+
116
+ for (const relativePath of MANAGED_FILES) {
117
+ const targetPath = join(destination, relativePath);
118
+ mkdirSync(dirname(targetPath), { recursive: true });
119
+ copyFileSync(join(source, relativePath), targetPath);
120
+ }
121
+ return destination;
122
+ }
123
+
124
+ function printInstallPreview(source) {
125
+ console.log(`Bundled skill: ${source}`);
126
+ console.log(`Project target: ${projectSkillDir()}`);
127
+ console.log(`Global target: ${globalSkillDir()}`);
128
+ console.log("");
129
+ console.log("Non-interactive mode: no files were written.");
130
+ console.log("Use --project, --global, or --path <dir> to install explicitly.");
131
+ }
132
+
133
+ async function chooseInteractiveTarget() {
134
+ const readline = createInterface({ input: process.stdin, output: process.stdout });
135
+ try {
136
+ console.log("选择 BiliSum 视频理解 skill 的安装位置:");
137
+ console.log(` 1) 当前项目 ${projectSkillDir()}`);
138
+ console.log(` 2) Codex 全局 ${globalSkillDir()}`);
139
+ console.log(" 3) 自定义目录");
140
+ console.log(" q) 取消");
141
+ const choice = (await readline.question("请输入选项 [1/2/3/q]: ")).trim().toLowerCase();
142
+ if (choice === "1") {
143
+ return projectSkillDir();
144
+ }
145
+ if (choice === "2") {
146
+ return globalSkillDir();
147
+ }
148
+ if (choice === "3") {
149
+ const customPath = (await readline.question("请输入 skill 目标目录: ")).trim();
150
+ if (!customPath) {
151
+ throw new UsageError("自定义 skill 目录不能为空。");
152
+ }
153
+ return resolve(customPath);
154
+ }
155
+ if (choice === "q" || choice === "quit" || choice === "cancel") {
156
+ return null;
157
+ }
158
+ throw new UsageError("无效的 skill 安装选项。");
159
+ } finally {
160
+ readline.close();
161
+ }
162
+ }
163
+
164
+ async function installCommand(args) {
165
+ const options = parseInstallArgs(args);
166
+ if (options.help) {
167
+ printSkillHelp();
168
+ return;
169
+ }
170
+
171
+ const source = resolve(skillSourceDir());
172
+ let destination = "";
173
+ if (options.mode) {
174
+ destination = destinationForMode(options.mode);
175
+ } else if (options.path) {
176
+ destination = resolve(options.path);
177
+ } else if (process.stdin.isTTY && process.stdout.isTTY) {
178
+ destination = await chooseInteractiveTarget();
179
+ } else {
180
+ printInstallPreview(source);
181
+ return;
182
+ }
183
+
184
+ if (!destination) {
185
+ console.log("已取消 skill 安装。");
186
+ return;
187
+ }
188
+
189
+ const installed = installSkill(destination, { source, force: options.force });
190
+ console.log(`BiliSum 视频理解 skill 已安装到:${installed}`);
191
+ }
192
+
193
+ async function runSkillCommand(args) {
194
+ const [subcommand, ...rest] = args;
195
+ if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
196
+ printSkillHelp();
197
+ return;
198
+ }
199
+ if (subcommand === "path") {
200
+ if (rest.length > 0) {
201
+ throw new UsageError("用法:bilisum skill path");
202
+ }
203
+ console.log(resolve(skillSourceDir()));
204
+ return;
205
+ }
206
+ if (subcommand === "install") {
207
+ await installCommand(rest);
208
+ return;
209
+ }
210
+ throw new UsageError(`未知 skill 子命令:${subcommand}`);
211
+ }
212
+
213
+ module.exports = {
214
+ SKILL_NAME,
215
+ MANAGED_FILES,
216
+ projectSkillDir,
217
+ globalSkillDir,
218
+ destinationForMode,
219
+ parseInstallArgs,
220
+ installSkill,
221
+ printSkillHelp,
222
+ runSkillCommand,
223
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bilisum",
3
- "version": "1.20.0-alpha.3",
3
+ "version": "1.20.0-beta.1",
4
4
  "description": "BiliSum CLI: control the installed BiliSum app, or run standalone with its own runtime.",
5
5
  "bin": {
6
6
  "bilisum": "bin/bilisum.js"
@@ -9,6 +9,7 @@
9
9
  "bin",
10
10
  "lib",
11
11
  "runtime",
12
+ "skill",
12
13
  "README.md"
13
14
  ],
14
15
  "scripts": {
package/runtime/VERSION CHANGED
@@ -1 +1 @@
1
- 1.20.0-alpha.3
1
+ 1.20.0-beta.1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "video-sum-service"
7
- version = "1.20.0-alpha.3"
7
+ version = "1.20.0-beta.1"
8
8
  description = "Local backend service for the video summarizer."
9
9
  requires-python = ">=3.12"
10
10
  dependencies = [