bilisum 1.20.0-alpha.4 → 1.20.0

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 (26) hide show
  1. package/README.md +18 -0
  2. package/bin/bilisum.js +9 -2
  3. package/lib/args.js +2 -1
  4. package/lib/runtime.js +42 -3
  5. package/lib/skill.js +223 -0
  6. package/package.json +2 -1
  7. package/runtime/VERSION +1 -1
  8. package/runtime/apps/service/pyproject.toml +1 -1
  9. package/runtime/apps/service/src/video_sum_service/app.py +5 -0
  10. package/runtime/apps/service/src/video_sum_service/knowledge/conversation_service.py +408 -0
  11. package/runtime/apps/service/src/video_sum_service/knowledge/index_service.py +116 -28
  12. package/runtime/apps/service/src/video_sum_service/knowledge/job_coordinator.py +247 -0
  13. package/runtime/apps/service/src/video_sum_service/knowledge/local_llm.py +115 -2
  14. package/runtime/apps/service/src/video_sum_service/knowledge/rag_service.py +262 -14
  15. package/runtime/apps/service/src/video_sum_service/repository.py +524 -8
  16. package/runtime/apps/service/src/video_sum_service/routers/knowledge.py +169 -0
  17. package/runtime/apps/service/src/video_sum_service/schemas.py +116 -0
  18. package/runtime/apps/web/static/assets/{index-S9QooflZ.css → index-KvLZ2oI3.css} +1 -1
  19. package/runtime/apps/web/static/assets/index-opES5zaV.js +357 -0
  20. package/runtime/apps/web/static/index.html +2 -2
  21. package/runtime/packages/core/pyproject.toml +1 -1
  22. package/runtime/packages/infra/pyproject.toml +1 -1
  23. package/runtime/packages/infra/src/video_sum_infra/llm.py +47 -0
  24. package/skill/bilisum-video-understanding/SKILL.md +81 -0
  25. package/skill/bilisum-video-understanding/agents/openai.yaml +4 -0
  26. package/runtime/apps/web/static/assets/index-DHuJPoLi.js +0 -357
package/README.md CHANGED
@@ -87,7 +87,24 @@ npm install -g bilisum # 或 npx bilisum ...
87
87
  ```
88
88
 
89
89
  包内包含构建独立环境所需的 Python 源码;首次执行 `bilisum env setup` 仍要求系统已有 Python 3.12。
90
+ ## Agent 视频理解 Skill
90
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` 理解视频。
91
108
  ## 常用选项
92
109
 
93
110
  | 选项 | 说明 |
@@ -122,4 +139,5 @@ node "<安装目录>\resources\cli\bin\bilisum.js" summarize "https://..."
122
139
  ```bash
123
140
  npm test --prefix packages/npx # node:test 单元测试
124
141
  npm run npx:test # 版本 + 单测 + npm pack 校验(根目录)
142
+ node packages/npx/bin/bilisum.js --setting # 可直接从仓库运行;自动使用仓库内 runtime/web 资源
125
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,
@@ -56,6 +57,7 @@ function printHelp() {
56
57
  console.log(" npx bilisum start [options] Start the service");
57
58
  console.log(" npx bilisum stop Stop the CLI-started BiliSum service");
58
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");
59
61
  console.log(" npx bilisum --version Print package version");
60
62
  console.log(" npx bilisum release Open the latest GitHub release");
61
63
  console.log("");
@@ -205,7 +207,8 @@ async function ensureAuthenticatedService(options, log) {
205
207
  if (!token) {
206
208
  throw new UsageError(
207
209
  "无法获取访问令牌:请用 --token 指定,或设置 VIDEO_SUM_ACCESS_TOKEN," +
208
- "或先启动一次桌面端 BiliSum 生成令牌。",
210
+ "或先启动一次桌面端 BiliSum 生成令牌;没有桌面端时可使用 --environment cli。",
211
+ { showHelp: false },
209
212
  );
210
213
  }
211
214
  return token;
@@ -647,6 +650,10 @@ async function main() {
647
650
  await doctor(args);
648
651
  return;
649
652
  }
653
+ if (command === "skill") {
654
+ await runSkillCommand(args);
655
+ return;
656
+ }
650
657
  if (command === "help" || command === "-h" || command === "--help") {
651
658
  printHelp();
652
659
  return;
@@ -667,7 +674,7 @@ async function main() {
667
674
  } catch (error) {
668
675
  const message = error instanceof Error ? error.message : String(error);
669
676
  console.error(message);
670
- if (error instanceof UsageError) {
677
+ if (error instanceof UsageError && error.showHelp) {
671
678
  console.error("");
672
679
  printHelp();
673
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
 
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/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.4",
3
+ "version": "1.20.0",
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.4
1
+ 1.20.0
@@ -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.4"
7
+ version = "1.20.0"
8
8
  description = "Local backend service for the video summarizer."
9
9
  requires-python = ">=3.12"
10
10
  dependencies = [
@@ -27,6 +27,7 @@ from video_sum_service import cli_idle
27
27
  from video_sum_service.context import CACHE_STATIC_DIR, WEB_STATIC_DIR, access_token_manager, app_info, logger, settings_manager
28
28
  from video_sum_service.integrations import probe_asr_connection, probe_llm_connection
29
29
  from video_sum_service.repository import SqliteTaskRepository
30
+ from video_sum_service.knowledge.job_coordinator import KnowledgeJobCoordinator
30
31
  from video_sum_service.routers.system import router as system_router
31
32
  from video_sum_service.routers.knowledge import router as knowledge_router
32
33
  from video_sum_service.routers.tasks import router as tasks_router
@@ -113,6 +114,9 @@ async def lifespan(app: FastAPI):
113
114
  app.state.task_repository = repository
114
115
  app.state.db_connection = connection
115
116
  app.state.settings_manager = settings_manager
117
+ knowledge_job_coordinator = KnowledgeJobCoordinator(repository, current_settings)
118
+ knowledge_job_coordinator.start()
119
+ app.state.knowledge_job_coordinator = knowledge_job_coordinator
116
120
  initialize_runtime_startup_state(app.state, current_settings)
117
121
  cli_idle.start_watchdog(repository)
118
122
  start_runtime_startup(app.state, repository, current_settings, recover_incomplete_tasks)
@@ -121,6 +125,7 @@ async def lifespan(app: FastAPI):
121
125
  yield
122
126
  finally:
123
127
  request_runtime_startup_shutdown(app.state)
128
+ knowledge_job_coordinator.stop()
124
129
  startup_thread = getattr(app.state, "runtime_startup_thread", None)
125
130
  if startup_thread is not None and startup_thread.is_alive():
126
131
  startup_thread.join(timeout=10)