@9000ai/cli 0.4.0 → 0.5.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/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ ## What's new in v0.5.1
2
+
3
+ - `9000ai init --path <dir>` — one command to setup workspace
4
+ - `9000ai search keyword --wait` — auto-poll, returns results directly
5
+ - `9000ai transcribe submit --wait` — same for transcription
6
+ - `9000ai monitor fetch --sec-user <id>` — fetch user homepage videos instantly
7
+ - Streamlined skills (removed legacy Python scripts)
8
+ - Hub SKILL.md: cleaner structure with call principles
package/dist/client.d.ts CHANGED
@@ -7,6 +7,12 @@ export interface RequestOptions {
7
7
  timeout?: number;
8
8
  }
9
9
  export declare function request(opts: RequestOptions): Promise<unknown>;
10
+ /** Poll a task/batch until terminal status. Returns final response data. */
11
+ export declare function pollUntilDone(path: string, opts?: {
12
+ interval?: number;
13
+ timeout?: number;
14
+ quiet?: boolean;
15
+ }): Promise<unknown>;
10
16
  export declare function printJson(data: unknown, opts?: {
11
17
  fields?: string;
12
18
  compact?: boolean;
package/dist/client.js CHANGED
@@ -40,6 +40,28 @@ export async function request(opts) {
40
40
  clearTimeout(timer);
41
41
  }
42
42
  }
43
+ /** Poll a task/batch until terminal status. Returns final response data. */
44
+ export async function pollUntilDone(path, opts) {
45
+ const interval = opts?.interval ?? 3_000;
46
+ const timeout = opts?.timeout ?? 600_000;
47
+ const start = Date.now();
48
+ while (Date.now() - start < timeout) {
49
+ const data = await request({ method: "GET", path });
50
+ const resp = data;
51
+ const inner = (resp.data ?? resp);
52
+ const status = (inner.status ?? "").toUpperCase();
53
+ if (["SUCCESS", "COMPLETED", "FAILED", "ERROR"].includes(status)) {
54
+ return data;
55
+ }
56
+ if (!opts?.quiet) {
57
+ const elapsed = Math.round((Date.now() - start) / 1000);
58
+ console.error(`[${elapsed}s] status: ${status || "PENDING"} — polling...`);
59
+ }
60
+ await new Promise((r) => setTimeout(r, interval));
61
+ }
62
+ console.error("Error: poll timed out");
63
+ process.exit(1);
64
+ }
43
65
  export function printJson(data, opts) {
44
66
  let output = data;
45
67
  // --fields: extract specified fields from items/data
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerInitCommand(program: Command): void;
@@ -0,0 +1,83 @@
1
+ import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join, relative } from "path";
4
+ const TEMPLATES_DIR = join(homedir(), ".9000ai", "skills", "9000AI-hub", "init", "templates");
5
+ /** Files under profile/ are never overwritten — user may have edited them. */
6
+ const NEVER_OVERWRITE_DIRS = ["profile"];
7
+ function copyTemplates(src, dest, relBase) {
8
+ const created = [];
9
+ const skipped = [];
10
+ if (!existsSync(src))
11
+ return { created, skipped };
12
+ const entries = readdirSync(src);
13
+ for (const entry of entries) {
14
+ const srcPath = join(src, entry);
15
+ const destPath = join(dest, entry);
16
+ const rel = relative(relBase, destPath);
17
+ const stat = statSync(srcPath);
18
+ if (stat.isDirectory()) {
19
+ if (!existsSync(destPath)) {
20
+ mkdirSync(destPath, { recursive: true });
21
+ }
22
+ const sub = copyTemplates(srcPath, destPath, relBase);
23
+ created.push(...sub.created);
24
+ skipped.push(...sub.skipped);
25
+ }
26
+ else {
27
+ if (existsSync(destPath)) {
28
+ // Never overwrite profile files
29
+ const topDir = rel.split(/[/\\]/)[0];
30
+ if (NEVER_OVERWRITE_DIRS.includes(topDir)) {
31
+ skipped.push(rel);
32
+ continue;
33
+ }
34
+ // Other existing files: also skip (idempotent)
35
+ skipped.push(rel);
36
+ }
37
+ else {
38
+ mkdirSync(join(destPath, ".."), { recursive: true });
39
+ copyFileSync(srcPath, destPath);
40
+ created.push(rel);
41
+ }
42
+ }
43
+ }
44
+ return { created, skipped };
45
+ }
46
+ export function registerInitCommand(program) {
47
+ program
48
+ .command("init")
49
+ .description("Initialize content workspace — copy templates to project directory")
50
+ .requiredOption("--path <dir>", "Project directory to initialize")
51
+ .action((opts) => {
52
+ const dest = opts.path;
53
+ // Check templates exist
54
+ if (!existsSync(TEMPLATES_DIR)) {
55
+ console.error(`Error: Templates not found at ${TEMPLATES_DIR}\n` +
56
+ `Run: npm install -g @9000ai/cli`);
57
+ process.exit(1);
58
+ }
59
+ // Create dest if needed
60
+ if (!existsSync(dest)) {
61
+ mkdirSync(dest, { recursive: true });
62
+ }
63
+ const { created, skipped } = copyTemplates(TEMPLATES_DIR, dest, dest);
64
+ // Output summary
65
+ if (created.length > 0) {
66
+ console.log(`Created ${created.length} files:`);
67
+ created.forEach((f) => console.log(` + ${f}`));
68
+ }
69
+ if (skipped.length > 0) {
70
+ console.log(`Skipped ${skipped.length} existing files:`);
71
+ skipped.forEach((f) => console.log(` - ${f}`));
72
+ }
73
+ if (created.length === 0 && skipped.length === 0) {
74
+ console.log("No template files found.");
75
+ return;
76
+ }
77
+ console.log(`\nWorkspace ready: ${dest}`);
78
+ console.log(`\nNext steps:`);
79
+ console.log(` 1. Fill in profile/ (identity.md, voice.md, topics.md, product.md)`);
80
+ console.log(` 2. cd ${dest}`);
81
+ console.log(` 3. Open Claude Code and start working`);
82
+ });
83
+ }
@@ -104,4 +104,21 @@ export function registerMonitorCommands(parent) {
104
104
  const data = await request({ method: "GET", path: `/api/v1/douyin/monitor/runs/${opts.runId}` });
105
105
  printJson(data, { fields: opts.fields, compact: opts.compact });
106
106
  });
107
+ cmd
108
+ .command("fetch")
109
+ .description("Fetch user homepage videos directly (sync, no queue)")
110
+ .requiredOption("--sec-user <id>", "User sec_user_id or homepage URL")
111
+ .option("--count <n>", "Number of videos to fetch", "20")
112
+ .option("--sort <n>", "Sort: 0=newest 1=hottest", "0")
113
+ .option("--fields <list>", "Comma-separated fields to extract")
114
+ .option("--compact", "One JSON object per line")
115
+ .action(async (opts) => {
116
+ const params = new URLSearchParams({
117
+ sec_user_id: opts.secUser,
118
+ count: opts.count,
119
+ sort_type: opts.sort,
120
+ });
121
+ const data = await request({ method: "GET", path: `/api/v1/douyin/monitor/user-posts?${params}` });
122
+ printJson(data, { fields: opts.fields, compact: opts.compact });
123
+ });
107
124
  }
@@ -1,4 +1,4 @@
1
- import { request, printJson } from "../client.js";
1
+ import { request, printJson, pollUntilDone } from "../client.js";
2
2
  import { writeJson, writeTsv, listOutputFiles, readOutputJson, timestampSlug } from "../output.js";
3
3
  export function registerSearchCommands(parent) {
4
4
  const cmd = parent.command("search").description("Douyin topic discovery — trending boards and keyword search");
@@ -36,6 +36,9 @@ export function registerSearchCommands(parent) {
36
36
  .option("--filter-duration <v>", "Duration filter", "")
37
37
  .option("--min-likes <n>", "Minimum likes", "0")
38
38
  .option("--min-comments <n>", "Minimum comments", "0")
39
+ .option("--wait", "Poll until batch completes then print results")
40
+ .option("--fields <list>", "Comma-separated fields to extract (used with --wait)")
41
+ .option("--compact", "One JSON object per line (used with --wait)")
39
42
  .action(async (keywords, opts) => {
40
43
  const payload = {
41
44
  keywords,
@@ -55,9 +58,32 @@ export function registerSearchCommands(parent) {
55
58
  });
56
59
  const resp = data;
57
60
  const inner = resp.data;
58
- console.log(`Batch submitted batch_id: ${inner?.batch_id}`);
59
- console.log(`Use: 9000ai search batch-result --batch-id ${inner?.batch_id}`);
60
- printJson(inner);
61
+ const batchId = inner?.batch_id;
62
+ if (!opts.wait || !batchId) {
63
+ console.log(`Batch submitted → batch_id: ${batchId}`);
64
+ console.log(`Use: 9000ai search batch-result --batch-id ${batchId}`);
65
+ printJson(inner);
66
+ return;
67
+ }
68
+ // --wait: poll then fetch results
69
+ console.error(`Batch submitted → ${batchId}, waiting...`);
70
+ await pollUntilDone(`/api/v1/tasks/batch/${batchId}`);
71
+ const resultData = await request({
72
+ method: "GET",
73
+ path: `/api/v1/douyin/discovery/search-videos/batch/${batchId}/results`,
74
+ });
75
+ const resultResp = resultData;
76
+ const resultInner = resultResp.data;
77
+ const items = (resultInner?.items ?? []);
78
+ if (items.length > 0) {
79
+ const slug = timestampSlug();
80
+ writeJson("latest_search.json", resultInner);
81
+ writeJson(`latest_search_${slug}.json`, resultInner);
82
+ writeTsv("latest_search.tsv", items);
83
+ writeTsv(`latest_search_${slug}.tsv`, items);
84
+ console.error(`${items.length} items → output/latest_search.json`);
85
+ }
86
+ printJson(resultInner, { fields: opts.fields, compact: opts.compact });
61
87
  });
62
88
  cmd
63
89
  .command("batch-result")
@@ -1,4 +1,4 @@
1
- import { request, printJson } from "../client.js";
1
+ import { request, printJson, pollUntilDone } from "../client.js";
2
2
  import { loadJsonFile } from "../utils/format.js";
3
3
  export function registerTranscribeCommands(parent) {
4
4
  const cmd = parent.command("transcribe").description("Video / audio transcription");
@@ -6,6 +6,9 @@ export function registerTranscribeCommands(parent) {
6
6
  .command("submit")
7
7
  .description("Submit batch video-to-text task")
8
8
  .requiredOption("--json-file <path>", "JSON file with video task details")
9
+ .option("--wait", "Poll until task completes then print results")
10
+ .option("--fields <list>", "Comma-separated fields to extract (used with --wait)")
11
+ .option("--compact", "One JSON object per line (used with --wait)")
9
12
  .action(async (opts) => {
10
13
  const payload = loadJsonFile(opts.jsonFile);
11
14
  const data = await request({
@@ -13,7 +16,21 @@ export function registerTranscribeCommands(parent) {
13
16
  path: "/api/v1/media/batch-video-to-text",
14
17
  payload,
15
18
  });
16
- printJson(data);
19
+ const resp = data;
20
+ const inner = resp.data;
21
+ const batchId = inner?.batch_id;
22
+ if (!opts.wait || !batchId) {
23
+ printJson(data);
24
+ return;
25
+ }
26
+ // --wait: poll then fetch results
27
+ console.error(`Batch submitted → ${batchId}, waiting...`);
28
+ await pollUntilDone(`/api/v1/tasks/batch/${batchId}`);
29
+ const resultData = await request({
30
+ method: "GET",
31
+ path: `/api/v1/tasks/batch/${batchId}`,
32
+ });
33
+ printJson(resultData, { fields: opts.fields, compact: opts.compact });
17
34
  });
18
35
  cmd
19
36
  .command("text")
package/dist/index.js CHANGED
@@ -8,11 +8,12 @@ import { registerTranscribeCommands } from "./commands/transcribe.js";
8
8
  import { registerTaskCommands } from "./commands/task.js";
9
9
  import { registerFeedbackCommands } from "./commands/feedback.js";
10
10
  import { registerSkillCommands } from "./commands/skill.js";
11
+ import { registerInitCommand } from "./commands/init.js";
11
12
  const program = new Command();
12
13
  program
13
14
  .name("9000ai")
14
15
  .description("9000AI Toolbox CLI — unified interface for 9000AI platform")
15
- .version("0.4.0");
16
+ .version("0.5.0");
16
17
  registerConfigCommands(program);
17
18
  registerAuthCommands(program);
18
19
  registerSearchCommands(program);
@@ -21,6 +22,7 @@ registerTranscribeCommands(program);
21
22
  registerTaskCommands(program);
22
23
  registerFeedbackCommands(program);
23
24
  registerSkillCommands(program);
25
+ registerInitCommand(program);
24
26
  program.parseAsync(process.argv).catch((err) => {
25
27
  console.error(err);
26
28
  process.exit(1);
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * postinstall: copy bundled skills to ~/.9000ai/skills/
3
+ * postinstall: copy bundled skills to ~/.9000ai/skills/ and show changelog
4
4
  */
5
5
  export {};
@@ -1,17 +1,40 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * postinstall: copy bundled skills to ~/.9000ai/skills/
3
+ * postinstall: copy bundled skills to ~/.9000ai/skills/ and show changelog
4
4
  */
5
- import { existsSync, mkdirSync, cpSync } from "fs";
5
+ import { existsSync, mkdirSync, cpSync, readFileSync } from "fs";
6
6
  import { dirname, join } from "path";
7
7
  import { homedir } from "os";
8
8
  import { fileURLToPath } from "url";
9
9
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
10
  const src = join(__dirname, "..", "skills");
11
11
  const dest = join(homedir(), ".9000ai", "skills");
12
+ // Read version
13
+ let version = "unknown";
14
+ try {
15
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
16
+ version = pkg.version;
17
+ }
18
+ catch { }
12
19
  if (!existsSync(src)) {
13
20
  process.exit(0);
14
21
  }
15
22
  mkdirSync(dest, { recursive: true });
16
23
  cpSync(src, dest, { recursive: true, force: true });
17
- console.log(`9000AI skills installed → ${dest}`);
24
+ // Read changelog
25
+ let changelog = "";
26
+ try {
27
+ changelog = readFileSync(join(__dirname, "..", "CHANGELOG.md"), "utf-8");
28
+ }
29
+ catch { }
30
+ console.log(`\n@9000ai/cli v${version} installed\n`);
31
+ console.log(`Skills updated: ${dest}\n`);
32
+ if (changelog) {
33
+ console.log(changelog);
34
+ console.log("");
35
+ }
36
+ console.log(`Get started:`);
37
+ console.log(` 1. 9000ai config set --base-url <server-url> --api-key <key>`);
38
+ console.log(` 2. 9000ai init --path <your-project-dir>`);
39
+ console.log(` 3. cd <your-project-dir> → start working`);
40
+ console.log(`\nUpdate: npm update -g @9000ai/cli\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@9000ai/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "9000AI Toolbox CLI — unified command-line interface for 9000AI platform",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,8 @@
14
14
  },
15
15
  "files": [
16
16
  "dist",
17
- "skills"
17
+ "skills",
18
+ "CHANGELOG.md"
18
19
  ],
19
20
  "dependencies": {
20
21
  "commander": "^12.1.0"
@@ -1,10 +1,13 @@
1
1
  ---
2
- name: 9000AI-hub-9000AI
3
- description: 9000AI 中台总入口。用于识别用户属于哪个业务模块,并路由到对应 skill。中台只负责目录、主调用命令、路由规则和统一使用规范,不直接执行业务。
2
+ name: 9000AI-hub
3
+ description: 9000AI 中台总入口。识别用户意图,路由到对应模块。首次使用时引导初始化。
4
4
  triggers:
5
5
  - /9000AI
6
6
  - /9000AI中台
7
- - 9000AI中台
7
+ - /content-init
8
+ - /初始化工作空间
9
+ - 初始化
10
+ - 第一次用
8
11
  role: router
9
12
  scope: routing
10
13
  output-format: routing-only
@@ -12,227 +15,109 @@ output-format: routing-only
12
15
 
13
16
  # 9000AI 中台
14
17
 
15
- ## 作用
18
+ 9000AI 是一个内容创作者中台。它把抖音热榜、关键词搜索、主页监控、视频转写这些能力整合在一起,通过 `9000ai` CLI 统一调用,后端异步执行。
16
19
 
17
- 你是 9000AI 的总入口。
18
- 你的职责不是直接执行业务,而是:
19
- 1. 识别用户当前属于哪个模块
20
- 2. 告诉系统应该进入哪个 skill
21
- 3. 在必要时提示下一步怎么继续
20
+ 你作为 AI 助手,通过这个中台帮用户完成选题发现、竞品分析、素材采集等内容创作工作流。中台只是工具层——你负责理解用户意图、编排调用、输出结果。
22
21
 
23
- 中台不重复解释子 skill 的全部细节。具体参数、脚本命令、输出文件格式,以对应子 skill 的 `SKILL.md` 为准。
22
+ ## 调用原则
24
23
 
25
- ## 总原则
24
+ 1. **无依赖的操作必须并行。** 热榜+搜索、多个关键词——没有数据依赖就同时发出
25
+ 2. **异步任务用 `--wait`。** 搜索和转写加 `--wait`,一条命令拿到最终结果
26
+ 3. **有依赖的操作串行。** 上一步输出是下一步输入时,必须等
27
+ 4. **用 `--fields` 只取需要的字段。** 永远不要全量读取
26
28
 
27
- 1. 优先选择最贴近用户意图的单个模块
28
- 2. 不要同时展开多个模块,除非用户明确要求串联
29
- 3. 中台只提供主调用命令,不展开全部子命令
30
- 4. 统一环境变量、统一 runner 规范,以中台为准
31
- 5. 如果用户只是泛泛提问,先帮他判断该进入哪个模块
32
-
33
- ## 初始化(首次使用必读)
34
-
35
- ### 这一步在干什么?
36
-
37
- 9000AI 的 skill(热榜、监控、转写……)本身不直接干活,它们只是"遥控器"。
38
- 真正干活的是 **9000AI 中台服务**——一个跑在某台电脑上的后端程序。
29
+ ```bash
30
+ # 单任务
31
+ 9000ai search keyword "美食探店" --wait --fields desc,likes,author_name
32
+
33
+ # 多数据源并行
34
+ 并行 {
35
+ 9000ai search hot
36
+ 9000ai search keyword "关键词A" --wait --fields desc,likes,author_name
37
+ 9000ai search keyword "关键词B" --wait --fields desc,likes,author_name
38
+ }
39
+
40
+ # 有依赖,串行
41
+ 9000ai search keyword "xxx" --wait --fields desc,video_url,likes
42
+ → 拿 video_url → 9000ai transcribe submit --json-file ... --wait
43
+ ```
39
44
 
40
- 初始化就是告诉这些遥控器:**中台服务跑在哪、用什么钥匙才能连上去。**
45
+ ## 模块目录
41
46
 
42
- 你需要两样东西:
47
+ ### 抖音选题发现
48
+ - 命令: `9000ai search`
49
+ - 场景: 热榜、关键词搜索、候选视频发现
43
50
 
44
- | 要填的 | 是什么 | 从哪来 |
45
- |--------|--------|--------|
46
- | `--base-url` | 中台服务的网络地址 | 跑服务的那台电脑的 IP + 端口,比如 `http://192.168.1.100:8025` |
47
- | `--api-key` | 你的身份钥匙 | 在中台后端的 `.env` 文件里配置,找管理员要 |
51
+ ### 抖音主页监控
52
+ - 命令: `9000ai monitor`
53
+ - 场景: 直接获取主页视频(`monitor fetch`)、加入监控队列(`monitor submit`)
48
54
 
49
- > **怎么找 base-url?**
50
- > 在跑中台服务的那台电脑上,打开终端输入 `ipconfig`(Windows)或 `ifconfig`(Mac/Linux),
51
- > 找到局域网 IP(通常是 `192.168.x.x`),加上端口 `8025`,就是你的地址。
52
- > 如果你就在跑服务的这台电脑上用,填 `http://127.0.0.1:8025` 就行。
55
+ ### 视频转写
56
+ - 命令: `9000ai transcribe`
57
+ - 场景: 批量视频转文字、查看转写结果
53
58
 
54
- ### 怎么初始化?
59
+ ### 反馈提交
60
+ - 命令: `9000ai feedback`
61
+ - 场景: 提交反馈、查看记录。接口报错或用户不满时主动提议反馈
55
62
 
56
- **只需要跑一次**,之后所有命令都自动共享这个配置:
63
+ ## 路由规则
57
64
 
58
- ```bash
59
- 9000ai config set --base-url http://192.168.x.x:8025 --api-key sk-xxx
60
- ```
65
+ | 用户意图 | 路由 |
66
+ |---------|------|
67
+ | 初始化 / 第一次用 / 搭建工作空间 | 执行下方"首次使用流程" |
68
+ | 查热榜 / 搜抖音 / 找选题 | `9000ai search` |
69
+ | 看某人主页 / 监控抖音号 | `9000ai monitor` |
70
+ | 视频转文字 / 转写 | `9000ai transcribe` |
71
+ | 反馈 / 建议 / 报 bug | `9000ai feedback` |
61
72
 
62
- 成功后会看到确认信息。如果以后中台换了地址或换了 key,再跑一次就行。
73
+ ## 首次使用流程
63
74
 
64
- ### 怎么检查当前配置?
75
+ ### 1. 确认中台连接
65
76
 
66
77
  ```bash
67
78
  9000ai config show
68
79
  ```
69
80
 
70
- ### 没初始化会怎样?
81
+ 未配置 → 问用户要地址和 key:
71
82
 
72
- 任何 skill 在调用中台时都会报错:`缺少 API key`,并提示你先运行上面的初始化命令。
73
-
74
- ### 配置存在哪?
75
-
76
- 保存在 `9000AI-hub-9000AI/local/config.json`(这个文件不入 git,不用担心泄露)。
77
- 各 skill 不需要单独配置,都从这里读。
78
-
79
- ## PowerShell 编码准则
80
-
81
- 在 Windows PowerShell 里直接跑脚本时,默认编码容易把中文参数和输出搞乱码。
82
- 进入任何 9000AI skill 之前,先按 UTF-8 设好控制台:
83
-
84
- ```powershell
85
- $env:PYTHONIOENCODING = 'utf-8'
86
- [Console]::InputEncoding = [System.Text.Encoding]::UTF8
87
- [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
83
+ ```bash
84
+ 9000ai config set --base-url <地址> --api-key <key>
88
85
  ```
89
86
 
90
- 如果不先设 UTF-8,常见问题是:
91
- - 中文关键词被打坏
92
- - 返回 JSON 中文发花
93
- - 复制到后续命令里的参数失真
94
-
95
- ## 模块目录
96
-
97
- ### 0. 内容工作空间初始化
98
-
99
- - skill: `content-init`(位于 `9000AI-hub-9000AI/init/SKILL.md`)
100
- - 主调用命令: `/content-init`
101
- - 适用场景:
102
- - 首次使用 9000AI 内容中台
103
- - 初始化项目工作空间目录结构
104
- - 创建 agent 人格、主张库模板、用户画像模板
105
- - 查看使用指南
106
-
107
- ### 1. 抖音主页监控
108
-
109
- - skill: `douyin-monitor-9000AI`
110
- - 主调用命令: `/douyin-monitor-9000AI`
111
- - 适用场景:
112
- - 添加监控账号
113
- - 发起主页监控
114
- - 查看监控运行状态
115
- - 回看监控结果
116
-
117
- ### 2. 抖音选题发现
118
-
119
- - skill: `douyin-topic-discovery-9000AI`
120
- - 主调用命令: `/douyin-topic-discovery-9000AI`
121
- - 适用场景:
122
- - 抖音热榜
123
- - 抖音搜索流
124
- - 候选视频发现
125
- - 搜索结果落本地 output 文件
126
-
127
- ### 3. 视频转写
128
-
129
- - skill: `video-transcription`
130
- - 主调用命令: `/video-transcription`
131
- - 适用场景:
132
- - 批量视频转文字
133
- - 查看转写任务状态
134
- - 回看转写结果
135
-
136
- ### 4. 反馈提交
137
-
138
- - skill: `feedback-9000AI`
139
- - 主调用命令: `/feedback-9000AI`
140
- - 适用场景:
141
- - 提交使用反馈(工作流串联建议、Bug、功能请求等)
142
- - 查看自己的反馈记录
143
-
144
- ## 路由规则
145
-
146
- - 用户要”初始化 / 开始使用 / 搭建工作空间 / 第一次用 / 初始化内容工作空间”
147
- - 路由到 `content-init`(`9000AI-hub-9000AI/init/SKILL.md`)
148
-
149
- - 用户要”监控抖音号 / 看主页更新 / 查主页监控结果”
150
- - 路由到 `douyin-monitor-9000AI`
151
-
152
- - 用户要“查热榜 / 搜抖音内容 / 找选题 / 找候选视频”
153
- - 路由到 `douyin-topic-discovery-9000AI`
154
-
155
- - 用户要”把视频转成文字 / 提交转写 / 看转写结果”
156
- - 路由到 `video-transcription`
157
-
158
- - 用户要”提交反馈 / 有建议 / 报 bug / 反馈意见”
159
- - 路由到 `feedback-9000AI`
160
-
161
- ## 串联规则
162
-
163
- 只有在用户明确要求多步串联时,才跨模块:
164
-
165
- - 先搜索,再转写
166
- - 先监控,再分析结果
167
-
168
- 如果需要串联:
169
- 1. 先进入第一个模块完成提交
170
- 2. 拿到任务结果或结果引用
171
- 3. 再进入下一个模块继续执行
172
-
173
- ## 统一使用规范
174
-
175
- 1. 不把大结果直接塞进上下文
176
- 2. 优先使用子 skill 的 `output` 文件、`task_id`、`batch_id`、`row_no` 继续操作
177
- 3. 不在中台里展开子 skill 的全部脚本命令
178
- 4. 如果用户问”该用哪个模块”,中台负责判断
179
- 5. 如果用户已经明确说了模块名,直接进入对应 skill
180
- 6. 共享执行底座以 `9000AI-hub-9000AI/shared/runner.py` 为准
181
- 7. 统一 runner 规范以 `9000AI-hub-9000AI/references/runner-spec-v1.md` 为准
182
- 8. 以下情况主动提议进入 `feedback-9000AI` 提交反馈,不要等用户说:
183
- - 任何 skill 出现接口报错、任务失败、结果异常
184
- - 发现某个工作流可以改进或串联自动化
185
- - 用户表达了不满、困惑或明确的改进意见
186
-
187
- ## 性能准则(必须遵守)
188
-
189
- ### 数据精简
190
-
191
- 所有查询命令都支持 `--fields` 和 `--compact` 参数。**默认必须用 `--fields` 只取需要的字段,禁止全量读取。**
87
+ ### 2. 初始化工作空间
192
88
 
193
89
  ```bash
194
- # 错误 返回 83KB 全量数据,浪费上下文
195
- 9000ai task results --task-id <id>
196
-
197
- # 正确 — 只取需要的 4 个字段
198
- 9000ai task results --task-id <id> --fields desc,video_url,likes,author_name
199
-
200
- # 更紧凑 — 每条一行,适合批量处理
201
- 9000ai task results --task-id <id> --fields desc,video_url,likes --compact
90
+ 9000ai init --path <项目目录>
202
91
  ```
203
92
 
204
- 常用字段组合:
205
- - 搜索结果概览:`--fields desc,author_name,likes,duration`
206
- - 提取转写链接:`--fields video_url,download_url,play_url,desc`
207
- - 监控结果摘要:`--fields desc,author_name,likes,comments,create_time`
93
+ ### 3. 载入角色
208
94
 
209
- ### 并行执行
95
+ 初始化完成后,读取项目目录下的 `agents/content-agent.md` 作为你的角色设定。这个文件定义了你的工作方式、可用能力、inbox 处理规则等。
210
96
 
211
- 独立的操作必须并行,不要串行等待。用 subagent 或并行工具调用。
97
+ **如果用户还没初始化**,你只有中台调度能力。**初始化后**,你才是完整的内容创作执行助手。
212
98
 
213
- 可以并行的场景:
214
- - 写 inbox 文件 + 提交转写任务
215
- - 多个关键词的搜索结果查询
216
- - 查多个 task_id 的状态
99
+ ### 4. 了解用户
217
100
 
218
- 不能并行的场景:
219
- - 搜索 → 等结果 → 筛选(有依赖关系)
220
- - 创建监控对象 → 提交监控任务(需要 creator_id)
101
+ 主动发起对话:
221
102
 
222
- ### 减少中间文件
103
+ > 工作空间搭好了。我想先了解一下你,这样后面做选题会更精准。
104
+ > 我们可以聊几个问题,也可以你把资料丢进 inbox/(支持 .txt),我来提取。
223
105
 
224
- CLI 支持 `--json-file` 也支持直接传参。简单请求直接传参,不要写中间 JSON 文件。
106
+ **对话模式** 依次引导:
107
+ 1. **identity.md** — 你是谁?做什么业务?目标受众?
108
+ 2. **voice.md** — 内容风格?参考博主?什么话不说?
109
+ 3. **topics.md** — 主攻方向?不碰的领域?
110
+ 4. **product.md** — 产品/服务?定价?核心卖点?
225
111
 
226
- ### 不暴露中间过程
112
+ **inbox 模式** — 读取 inbox/ 的 .txt,提取信息填充 profile/。
227
113
 
228
- 用户要的是结果,不是过程。拿到数据后直接输出摘要或执行下一步,不要把原始 JSON 展示给用户。
114
+ 完成后:
229
115
 
230
- ## 一句话总结
116
+ > 画像建好了。现在可以开始——比如"帮我做选题调研"、"看看最近热榜"。
231
117
 
232
- 中台只负责:
118
+ ## 参考文档
233
119
 
234
- - 看用户要做什么
235
- - 告诉系统该去哪个 skill
236
- - 给出主调用命令
120
+ 需要更详细的使用规范时查阅:
237
121
 
238
- 中台不直接执行业务。
122
+ - `references/usage-guidelines.md` — 性能准则、字段组合、串联规则、PowerShell 编码
123
+ - `references/env.example` — 环境变量示例