@9000ai/cli 0.6.4 → 0.7.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.
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerContentCommands(parent: Command): void;
@@ -0,0 +1,66 @@
1
+ import { request, printJson, pollUntilDone } from "../client.js";
2
+ import { loadJsonFile } from "../utils/format.js";
3
+ export function registerContentCommands(parent) {
4
+ const cmd = parent.command("content").description("LLM content processing (analyze, rewrite, summarize)");
5
+ cmd
6
+ .command("analyze")
7
+ .description("Submit batch LLM processing tasks")
8
+ .option("--preset <name>", "Built-in preset: analyze, summarize, rewrite")
9
+ .option("--system-prompt <prompt>", "Custom system prompt (alternative to --preset)")
10
+ .option("--texts-file <path>", "JSON file: [{\"id\": \"...\", \"text\": \"...\"}]")
11
+ .option("--model <model>", "Override LLM model")
12
+ .option("--group-label <label>", "Batch group label")
13
+ .option("--webhook <url>", "Webhook for completion callback")
14
+ .option("--wait", "Poll until all tasks complete")
15
+ .option("--fields <list>", "Comma-separated fields to extract")
16
+ .option("--compact", "One JSON object per line")
17
+ .action(async (opts) => {
18
+ if (!opts.textsFile) {
19
+ console.error("Error: --texts-file is required");
20
+ process.exit(1);
21
+ }
22
+ if (!opts.preset && !opts.systemPrompt) {
23
+ console.error("Error: must provide --preset or --system-prompt");
24
+ process.exit(1);
25
+ }
26
+ const textsData = loadJsonFile(opts.textsFile);
27
+ const texts = Array.isArray(textsData) ? textsData : textsData.texts;
28
+ const payload = { texts };
29
+ if (opts.preset)
30
+ payload.preset = opts.preset;
31
+ if (opts.systemPrompt)
32
+ payload.system_prompt = opts.systemPrompt;
33
+ if (opts.model)
34
+ payload.model = opts.model;
35
+ if (opts.groupLabel)
36
+ payload.group_label = opts.groupLabel;
37
+ if (opts.webhook)
38
+ payload.webhook = opts.webhook;
39
+ const data = await request({
40
+ method: "POST",
41
+ path: "/api/v1/content/batch-analyze",
42
+ payload,
43
+ });
44
+ const resp = data;
45
+ const inner = resp.data;
46
+ const batchId = inner?.batch_id;
47
+ if (!opts.wait || !batchId) {
48
+ printJson(data, { fields: opts.fields, compact: opts.compact });
49
+ return;
50
+ }
51
+ console.error(`Batch submitted → ${batchId}, waiting...`);
52
+ await pollUntilDone(`/api/v1/tasks/batch/${batchId}`);
53
+ const resultData = await request({
54
+ method: "GET",
55
+ path: `/api/v1/tasks/batch/${batchId}`,
56
+ });
57
+ printJson(resultData, { fields: opts.fields, compact: opts.compact });
58
+ });
59
+ cmd
60
+ .command("presets")
61
+ .description("List available LLM presets")
62
+ .action(async () => {
63
+ const data = await request({ method: "GET", path: "/api/v1/content/presets" });
64
+ printJson(data);
65
+ });
66
+ }
@@ -38,11 +38,14 @@ export function registerFeedbackCommands(parent) {
38
38
  .description("List my submitted feedback")
39
39
  .option("--page <n>", "Page number", "1")
40
40
  .option("--page-size <n>", "Items per page", "20")
41
+ .option("--format <fmt>", "Output format: csv|json", "csv")
42
+ .option("--fields <list>", "Comma-separated fields to extract")
43
+ .option("--compact", "One JSON object per line")
41
44
  .action(async (opts) => {
42
45
  const data = await request({
43
46
  method: "GET",
44
47
  path: `/api/v1/feedback?page=${opts.page}&page_size=${opts.pageSize}`,
45
48
  });
46
- printJson(data);
49
+ printJson(data, { fields: opts.fields, compact: opts.compact, format: opts.format });
47
50
  });
48
51
  }
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, copyFileSync, readFileSync, readdirSync, statSyn
2
2
  import { join, relative } from "path";
3
3
  import { resolveSkillsPath } from "../config.js";
4
4
  /** User data directories — never overwrite even with --force */
5
- const USER_DATA_DIRS = ["profile", "claims", "inbox", "projects", "benchmarks"];
5
+ const USER_DATA_DIRS = ["profile", "claims", "inbox", "projects", "benchmarks", "assets", "workflows"];
6
6
  function filesEqual(a, b) {
7
7
  try {
8
8
  return readFileSync(a).equals(readFileSync(b));
@@ -84,6 +84,8 @@ export function registerInitCommand(program) {
84
84
  }
85
85
  const { created, updated, skipped, outdated } = copyTemplates(templatesDir, dest, dest, !!opts.force);
86
86
  // Output summary
87
+ const isFirstTime = created.length > 0 && updated.length === 0 && outdated.length === 0;
88
+ const isUpdate = updated.length > 0;
87
89
  if (created.length > 0) {
88
90
  console.log(`Created ${created.length} files:`);
89
91
  created.forEach((f) => console.log(` + ${f}`));
@@ -102,11 +104,17 @@ export function registerInitCommand(program) {
102
104
  return;
103
105
  }
104
106
  console.log(`\nWorkspace ready: ${dest}`);
105
- if (created.length > 0 && updated.length === 0) {
106
- console.log(`\nNext steps:`);
107
- console.log(` 1. Fill in profile/ (identity.md, voice.md, topics.md, product.md)`);
108
- console.log(` 2. cd ${dest}`);
109
- console.log(` 3. Start working`);
107
+ if (isFirstTime) {
108
+ console.log(`\nThis is a content creator workspace powered by 9000AI.`);
109
+ console.log(`Capabilities: trending topics, keyword search, creator monitoring, video transcription.`);
110
+ console.log(`\nRead guide.md for full walkthrough, or get started now:`);
111
+ console.log(` 1. Load 9000AI hub: 9000ai skill load 9000AI-hub`);
112
+ console.log(` 2. Or tell your AI: /9000AI`);
113
+ console.log(` The AI will guide you through profile setup and available capabilities.`);
114
+ }
115
+ else if (isUpdate) {
116
+ console.log(`\nWhat's new: 9000ai skill load 9000AI-hub to see latest capabilities.`);
117
+ console.log(`Your data (profile/, claims/, assets/, workflows/, inbox/, projects/) was not modified.`);
110
118
  }
111
119
  });
112
120
  }
@@ -5,11 +5,12 @@ export function registerMonitorCommands(parent) {
5
5
  cmd
6
6
  .command("list-creators")
7
7
  .description("List all monitoring targets")
8
+ .option("--format <fmt>", "Output format: csv|json", "csv")
8
9
  .option("--fields <list>", "Comma-separated fields to extract")
9
10
  .option("--compact", "One JSON object per line")
10
11
  .action(async (opts) => {
11
12
  const data = await request({ method: "GET", path: "/api/v1/douyin/monitor/creators" });
12
- printJson(data, { fields: opts.fields, compact: opts.compact });
13
+ printJson(data, { fields: opts.fields, compact: opts.compact, format: opts.format });
13
14
  });
14
15
  cmd
15
16
  .command("create-creator")
@@ -85,6 +86,7 @@ export function registerMonitorCommands(parent) {
85
86
  .option("--page <n>", "Page number", "1")
86
87
  .option("--page-size <n>", "Items per page", "20")
87
88
  .option("--status <status>", "Filter by status")
89
+ .option("--format <fmt>", "Output format: csv|json", "csv")
88
90
  .option("--fields <list>", "Comma-separated fields to extract")
89
91
  .option("--compact", "One JSON object per line")
90
92
  .action(async (opts) => {
@@ -92,17 +94,18 @@ export function registerMonitorCommands(parent) {
92
94
  if (opts.status)
93
95
  params.set("status", opts.status);
94
96
  const data = await request({ method: "GET", path: `/api/v1/douyin/monitor/runs?${params}` });
95
- printJson(data, { fields: opts.fields, compact: opts.compact });
97
+ printJson(data, { fields: opts.fields, compact: opts.compact, format: opts.format });
96
98
  });
97
99
  cmd
98
100
  .command("run-detail")
99
101
  .description("View single run details")
100
102
  .requiredOption("--run-id <id>", "Run ID")
103
+ .option("--format <fmt>", "Output format: csv|json", "csv")
101
104
  .option("--fields <list>", "Comma-separated fields to extract")
102
105
  .option("--compact", "One JSON object per line")
103
106
  .action(async (opts) => {
104
107
  const data = await request({ method: "GET", path: `/api/v1/douyin/monitor/runs/${opts.runId}` });
105
- printJson(data, { fields: opts.fields, compact: opts.compact });
108
+ printJson(data, { fields: opts.fields, compact: opts.compact, format: opts.format });
106
109
  });
107
110
  cmd
108
111
  .command("fetch")
@@ -110,7 +113,7 @@ export function registerMonitorCommands(parent) {
110
113
  .requiredOption("--sec-user <id>", "User sec_user_id or homepage URL")
111
114
  .option("--count <n>", "Number of videos to fetch", "20")
112
115
  .option("--sort <n>", "Sort: 0=newest 1=hottest", "0")
113
- .option("--format <fmt>", "Output format: csv|json", "json")
116
+ .option("--format <fmt>", "Output format: csv|json", "csv")
114
117
  .option("--fields <list>", "Comma-separated fields to extract")
115
118
  .option("--compact", "One JSON object per line")
116
119
  .action(async (opts) => {
@@ -68,7 +68,7 @@ export function registerSearchCommands(parent) {
68
68
  .option("--min-likes <n>", "Minimum likes", "0")
69
69
  .option("--min-comments <n>", "Minimum comments", "0")
70
70
  .option("--wait", "Poll until batch completes then print results")
71
- .option("--format <fmt>", "Output format: csv|json", "json")
71
+ .option("--format <fmt>", "Output format: csv|json", "csv")
72
72
  .option("--fields <list>", "Comma-separated fields to extract (used with --wait)")
73
73
  .option("--compact", "One JSON object per line (used with --wait)")
74
74
  .action(async (keywords, opts) => {
@@ -121,7 +121,7 @@ export function registerSearchCommands(parent) {
121
121
  .command("batch-result")
122
122
  .description("Query async search batch results")
123
123
  .requiredOption("--batch-id <id>", "Batch ID")
124
- .option("--format <fmt>", "Output format: csv|json", "json")
124
+ .option("--format <fmt>", "Output format: csv|json", "csv")
125
125
  .option("--fields <list>", "Comma-separated fields to extract (e.g. desc,author_name,likes,video_url)")
126
126
  .option("--compact", "One JSON object per line, no indentation")
127
127
  .action(async (opts) => {
@@ -6,6 +6,7 @@ export function registerTaskCommands(parent) {
6
6
  .description("Check task or batch status")
7
7
  .option("--task-id <id>", "Task ID")
8
8
  .option("--batch-id <id>", "Batch ID (shows batch progress + task list)")
9
+ .option("--format <fmt>", "Output format: csv|json", "csv")
9
10
  .option("--fields <list>", "Comma-separated fields to extract (e.g. desc,video_url,likes)")
10
11
  .option("--compact", "One JSON object per line, no indentation")
11
12
  .action(async (opts) => {
@@ -15,20 +16,21 @@ export function registerTaskCommands(parent) {
15
16
  }
16
17
  if (opts.batchId) {
17
18
  const data = await request({ method: "GET", path: `/api/v1/tasks/batch/${opts.batchId}` });
18
- printJson(data, { fields: opts.fields, compact: opts.compact });
19
+ printJson(data, { fields: opts.fields, compact: opts.compact, format: opts.format });
19
20
  return;
20
21
  }
21
22
  const data = await request({ method: "GET", path: `/api/v1/tasks/${opts.taskId}` });
22
- printJson(data, { fields: opts.fields, compact: opts.compact });
23
+ printJson(data, { fields: opts.fields, compact: opts.compact, format: opts.format });
23
24
  });
24
25
  cmd
25
26
  .command("results")
26
27
  .description("Get task results")
27
28
  .requiredOption("--task-id <id>", "Task ID")
29
+ .option("--format <fmt>", "Output format: csv|json", "csv")
28
30
  .option("--fields <list>", "Comma-separated fields to extract (e.g. desc,video_url,likes)")
29
31
  .option("--compact", "One JSON object per line, no indentation")
30
32
  .action(async (opts) => {
31
33
  const data = await request({ method: "GET", path: `/api/v1/tasks/${opts.taskId}/results` });
32
- printJson(data, { fields: opts.fields, compact: opts.compact });
34
+ printJson(data, { fields: opts.fields, compact: opts.compact, format: opts.format });
33
35
  });
34
36
  }
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import { registerFeedbackCommands } from "./commands/feedback.js";
10
10
  import { registerSkillCommands } from "./commands/skill.js";
11
11
  import { registerInitCommand } from "./commands/init.js";
12
12
  import { registerUpdateCommand } from "./commands/update.js";
13
+ import { registerContentCommands } from "./commands/content.js";
13
14
  const program = new Command();
14
15
  program
15
16
  .name("9000ai")
@@ -25,6 +26,7 @@ registerFeedbackCommands(program);
25
26
  registerSkillCommands(program);
26
27
  registerInitCommand(program);
27
28
  registerUpdateCommand(program);
29
+ registerContentCommands(program);
28
30
  program.parseAsync(process.argv).catch((err) => {
29
31
  console.error(err);
30
32
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@9000ai/cli",
3
- "version": "0.6.4",
3
+ "version": "0.7.0",
4
4
  "description": "9000AI Toolbox CLI — unified command-line interface for 9000AI platform",
5
5
  "type": "module",
6
6
  "bin": {
@@ -57,6 +57,10 @@ output-format: routing-only
57
57
  - 命令: `9000ai transcribe`
58
58
  - 场景: 批量视频转文字、查看转写结果
59
59
 
60
+ ### 内容处理
61
+ - 命令: `9000ai content`
62
+ - 场景: 批量文案拆解、改写、摘要,或自定义 prompt 处理
63
+
60
64
  ### 反馈提交
61
65
  - 命令: `9000ai feedback`
62
66
  - 场景: 提交反馈、查看记录。接口报错或用户不满时主动提议反馈
@@ -69,6 +73,7 @@ output-format: routing-only
69
73
  | 查热榜 / 搜抖音 / 找选题 | `9000ai search` |
70
74
  | 看某人主页 / 监控抖音号 | `9000ai monitor` |
71
75
  | 视频转文字 / 转写 | `9000ai transcribe` |
76
+ | 拆解这篇文案 / 分析内容 / 改写 / 摘要 | `9000ai content` |
72
77
  | 反馈 / 建议 / 报 bug | `9000ai feedback` |
73
78
 
74
79
  ## 首次使用流程
@@ -114,7 +119,15 @@ output-format: routing-only
114
119
 
115
120
  完成后:
116
121
 
117
- > 画像建好了。现在可以开始——比如"帮我做选题调研"、"看看最近热榜"。
122
+ > 画像建好了。你的工作空间包含:
123
+ > - `profile/` — 你的画像(刚填好的)
124
+ > - `claims/` — 主张库,所有内容围绕主张展开
125
+ > - `assets/` — 内容弹药库,所有素材统一存放,AI 通过三层检索找素材
126
+ > - `workflows/` — 工作流,会在使用中不断进化
127
+ > - `inbox/` — 素材投喂入口
128
+ > - `projects/` — 选题项目
129
+ >
130
+ > 现在可以开始——比如"帮我做选题调研"、"看看最近热榜"、"转写这个视频"。
118
131
 
119
132
  ## 更新系统
120
133
 
@@ -11,8 +11,10 @@
11
11
  | 目录 | 用途 | 你的权限 |
12
12
  |------|------|---------|
13
13
  | profile/ | 了解用户是谁——身份、风格、领域 | 只读(用户维护) |
14
+ | claims/ | 主张库——主张定义、角度、产出记录 | 读写(匹配、回写角度和产出) |
15
+ | assets/ | 内容弹药库——所有素材统一存放 | 读写(入库、更新索引、标记使用) |
16
+ | workflows/ | 工作流——会在使用中不断进化 | 读写(追加已验证规则) |
14
17
  | inbox/ | 接收用户投喂的语料——会议记录、视频链接、文章、随笔 | 读取 + 处理后归档 |
15
- | claims/ | 主张库——核心资产,所有内容围绕主张展开 | 读写(提取、匹配、回写) |
16
18
  | projects/ | 选题项目——一个选题一个项目 | 读写 |
17
19
 
18
20
  ## 3. 可用工具能力
@@ -24,11 +26,136 @@
24
26
  | 对标账号监控 | 监控指定博主的内容更新 | `9000ai monitor` |
25
27
  | 视频转文字 | 批量将视频转为文字稿 | `9000ai transcribe` |
26
28
 
27
- ## 4. inbox 处理规则
29
+ ## 4. 内容弹药库(assets/)
30
+
31
+ 弹药库是整个系统的核心。所有素材——对标博主的文案、拆解出来的案例、你自己的金句——全部存在这里。
32
+
33
+ ### 4.1 三层检索
34
+
35
+ AI 找素材不能一次性读全量,必须分层:
36
+
37
+ **第一层:仓库总览 `assets/catalog.json`**
38
+
39
+ AI 第一个读的文件。记录有哪些主题、每个主题多少素材、什么标签、关联哪些主张。
40
+
41
+ ```json
42
+ [
43
+ {
44
+ "topic": "知识付费",
45
+ "count": 326,
46
+ "claim_ids": ["cl001"],
47
+ "top_tags": ["割韭菜", "教育", "焦虑", "课程"],
48
+ "top_ips": ["栋哥", "曲曲大女人"]
49
+ },
50
+ {
51
+ "topic": "_自己的",
52
+ "count": 89,
53
+ "claim_ids": ["cl001", "cl002", "cl003"],
54
+ "top_tags": ["金句", "开头", "亲身经历"],
55
+ "top_ips": []
56
+ }
57
+ ]
58
+ ```
59
+
60
+ **第二层:主题索引 `assets/_index/{主题}.jsonl`**
61
+
62
+ 每个主题一个索引文件,每行一条素材摘要。AI 按标签过滤,筛出十几条相关的。
63
+
64
+ ```jsonl
65
+ {"id":"a001","type":"transcription","title":"栋哥讲知识付费真相","tags":["知识付费","割韭菜","教育"],"source_ip":"栋哥","claim_ids":["cl001"],"hook":"知识付费就是当代最大的骗局","structure":"递进","date":"2026-04-04","reuse_count":2,"file":"知识付费/栋哥_知识付费真相.md"}
66
+ ```
67
+
68
+ **第三层:具体文件 `assets/{主题}/{文件}`**
69
+
70
+ 只有被第二层命中了才读。转写原文、拆解报告、自己的片段都在这里。
71
+
72
+ ### 4.2 检索规则
73
+
74
+ 1. **必须从第一层开始**,不允许跳过总览直接扫索引
75
+ 2. 读 catalog.json → 判断该读哪几个主题的索引 → 读对应 _index/*.jsonl → 按标签筛 → 只读命中的具体文件
76
+ 3. `_自己的` 主题里的素材优先级高于对标素材——用户自己验证过的表述优先复用
77
+ 4. 每次使用某条素材后,更新该条记录的 `reuse_count`,避免过度复用同一个案例
78
+
79
+ ### 4.3 素材入库规则
80
+
81
+ 素材进来时,AI 做三件事:
82
+ 1. 具体文件存到 `assets/{主题}/`,文件名格式:`{作者}_{标题}.md`
83
+ 2. 在 `assets/_index/{主题}.jsonl` 追加一行索引记录
84
+ 3. 更新 `assets/catalog.json` 里对应主题的 count 和 top_tags
85
+
86
+ 新主题第一次出现时,自动建文件夹和索引文件,同时在 catalog.json 追加条目。
87
+
88
+ 还没想好放哪个主题的素材,先存到 `_待分类/`。
89
+
90
+ ### 4.4 拆解报告
91
+
92
+ 对标文案转写后,如果经过后端拆解,拆解报告存在原文旁边:
93
+
94
+ ```
95
+ assets/知识付费/
96
+ ├── 栋哥_知识付费真相.md ← 转写原文
97
+ └── 栋哥_知识付费真相.analysis.json ← 拆解报告
98
+ ```
99
+
100
+ 拆解报告包含:开头钩子、核心观点、案例素材、金句、结构模式、转化钩子。AI 做选题时可以直接读拆解报告,不需要重新分析原文。
101
+
102
+ ### 4.5 `_自己的` 目录
103
+
104
+ 用户自己产出的好内容——录过的文案、想到的金句、亲身经历——存在这里。
105
+
106
+ ```
107
+ assets/_自己的/
108
+ ├── 片段/
109
+ │ ├── 知识付费就是欺诈_完整文案.md
110
+ │ └── 劳动仲裁_口述观点.md
111
+ └── ...
112
+ ```
113
+
114
+ 这里的素材和对标素材走同样的索引机制,但 `source_ip` 标记为用户自己(从 profile/identity.md 读名字),AI 检索时可以区分"我自己说过的"和"别人说的"。
115
+
116
+ ## 5. 工作流进化(workflows/)
117
+
118
+ 工作流文件定义内容生产的步骤。关键特性:**工作流不是写死的,在使用中不断固化标准。**
119
+
120
+ ### 5.1 进化机制
121
+
122
+ 用户每次纠正 AI 的判断,AI 把验证过的标准追加到对应工作流文件的"已验证规则"区域。
123
+
124
+ 例子:
125
+
126
+ ```markdown
127
+ ## 已验证规则
128
+
129
+ - 案例筛选:点赞 ≥ 500,时长 > 60s 的要点赞 ≥ 1000
130
+ - 角度偏好:优先选有冲突感的,不要中性表述
131
+ - 开头风格:直接亮观点,不铺垫
132
+ - 补素材:弹药库里相关案例 < 3 条时自动搜索补充,不用问
133
+ ```
134
+
135
+ ### 5.2 规则追加原则
136
+
137
+ 1. **只记真正的规律**,单次偶发纠正不写入
138
+ 2. **记标准不记操作**,写"点赞 ≥ 500"而不是"上次你删了那条 200 赞的"
139
+ 3. **记原因**,不只是结论,要写为什么——方便判断边界情况
140
+ 4. **用户确认后才追加**,AI 先提议,用户同意了再写入
141
+
142
+ ### 5.3 工作流与主张的关系
143
+
144
+ 工作流是通用的流程模板,主张是个人化的内容资产。工作流里不硬编码具体主张,而是引用 claims/claims.json。这样同一套工作流可以服务不同主张。
145
+
146
+ ## 6. 对标 IP 管理
147
+
148
+ 不新建文件,复用 monitor 的 creator 列表。
149
+
150
+ - 用户说"盯住这几个 IP" → 调 `9000ai monitor create-creator` 添加监控
151
+ - 对标 IP 的内容转写后入库时,`source_ip` 字段自动标记博主名
152
+ - 做选题时可以按 `source_ip` 检索某个博主的所有素材
153
+
154
+ ## 7. inbox 处理规则
28
155
 
29
156
  当用户往 inbox/ 丢入新文件时:
30
157
 
31
- ### 4.1 判断文件类型
158
+ ### 7.1 判断文件类型
32
159
 
33
160
  | 类型 | 特征 |
34
161
  |------|------|
@@ -38,20 +165,20 @@
38
165
  | 观点 / 随笔 | .txt / .md,短篇,非结构化 |
39
166
  | 热点事件描述 | 含事件名称、时间、平台来源 |
40
167
 
41
- ### 4.2 内容分类分发
168
+ ### 7.2 内容分类分发
42
169
 
43
170
  inbox 里的内容不全是案例素材,提取前先判断类型,分发到正确的位置:
44
171
 
45
172
  | 内容类型 | 判断特征 | 去哪 |
46
173
  |---------|---------|------|
47
- | 主张相关的案例、观点、热点事件 | 可以服务于某个主张,适合对外输出 | `claims/cases.jsonl` |
174
+ | 对标文案、案例、观点、热点事件 | 可以作为内容素材 | `assets/` 对应主题 |
175
+ | 用户自己的好内容 | 第一人称表述、自己的经历 | `assets/_自己的/` |
48
176
  | 个人身份、背景、目标受众 | 关于"你是谁"的信息 | 提示用户更新 `profile/identity.md` |
49
177
  | 用户自己的表达风格、语气偏好 | 用户描述自己说话方式的信息 | 提示用户更新 `profile/voice.md` |
50
178
  | 关注领域、赛道、不碰的方向 | 关于"你做什么内容"的信息 | 提示用户更新 `profile/topics.md` |
51
179
  | 产品细节、交付形式、定价、卖点 | 关于"你卖什么"的信息 | 提示用户更新 `profile/product.md` |
52
- | 技术实现、系统设计、内部流程 | 关于"你怎么做"的内部信息 | 提示用户更新 `profile/product.md` |
53
180
 
54
- **原则**:没有内容会被丢弃,只是放对位置。profile 类内容不入案例库,cases.jsonl 只存适合对外创作的素材。
181
+ **原则**:没有内容会被丢弃,只是放对位置。profile 类内容不入弹药库。
55
182
 
56
183
  #### profile/voice.md 写入规则
57
184
 
@@ -69,8 +196,8 @@ inbox 里的内容不全是案例素材,提取前先判断类型,分发到
69
196
 
70
197
  | 情况 | 处理 |
71
198
  |------|------|
72
- | 提到对标博主(曲曲、良策文化等) | 存 `cases.jsonl` 作为选题参考,不碰 voice.md |
73
- | 投喂的是别人的文案(用于分析) | 只提取案例素材,不碰 voice.md |
199
+ | 提到对标博主(曲曲、良策文化等) | 存 assets/ 作为素材,不碰 voice.md |
200
+ | 投喂的是别人的文案(用于分析) | 只入弹药库,不碰 voice.md |
74
201
  | 会议里描述第三方账号的风格 | 忽略风格部分,不碰 voice.md |
75
202
  | 用户说"我想学 XX 的风格" | 见下方特殊情况处理 |
76
203
 
@@ -92,47 +219,46 @@ inbox 里的内容不全是案例素材,提取前先判断类型,分发到
92
219
  > 「你是想把 XX 加为风格模仿对象(写入 voice.md),还是只是选题对标?」
93
220
 
94
221
  - 选风格模仿 → 写入 voice.md 的"参考博主风格"字段,并标注"待学习"
95
- - 选选题对标 → 存 cases.jsonl,不碰 voice.md
222
+ - 选选题对标 → 存 assets/,不碰 voice.md
96
223
 
97
- ### 4.3 并行调度 subagent
224
+ ### 7.3 并行调度 subagent
98
225
 
99
- - **提取 subagent**:从语料中提取主张相关的案例、观点、事件(经过 4.2 分类判断后)→ 写入 cases.jsonl
226
+ - **入库 subagent**:将素材分类后存入 assets/ 对应主题,更新索引
100
227
  - **转写 subagent**:如果有视频链接 → 调用视频转文字能力
101
228
  - **搜索 subagent**:如果需要佐证或补充素材 → 调用关键词搜索能力
102
229
  - **profile subagent**:如果语料中有用户个人信息 → 提醒用户更新 profile/
103
230
 
104
- ### 4.4 处理完成后
231
+ ### 7.4 处理完成后
105
232
 
106
233
  1. 将原始文件从 inbox/ 移入 inbox/processed/
107
- 2. 向用户汇报:提取了什么、写入了哪些文件、匹配了哪些主张
234
+ 2. 向用户汇报:提取了什么、存入了哪些主题、关联了哪些主张
108
235
 
109
- ## 5. 匹配流程
236
+ ## 8. 匹配流程
110
237
 
111
238
  ```
112
239
  输入(热点/案例/事件)
113
240
  → AI 提取关键词
114
- 遍历 claims/angles.jsonl,按 tags 字段匹配
115
- 命中已有角度:
116
- 推荐该角度 + cases.jsonl 拉历史案例
117
- 未命中:
118
- 读取 claims.json 中对应主张的 description
119
- 基于主张现推 2-3 个新角度
120
- 用户选定后新角度写回 angles.jsonl
121
- → 用户录制完成后 → 记录到 outputs.jsonl
241
+ assets/catalog.json 定位相关主题
242
+ 读对应 _index/*.jsonl 按标签匹配
243
+ 命中已有素材 推荐角度 + 拉历史案例
244
+ 未命中 → 读 claims/claims.json 中对应主张
245
+ 基于主张现推 2-3 个新角度
246
+ 用户选定后 → 新角度写回 claims/angles.jsonl
247
+ 用户录制完成后 记录到 claims/outputs.jsonl
122
248
  ```
123
249
 
124
- ## 6. subagent 调度规则
250
+ ## 9. subagent 调度规则
125
251
 
126
252
  - 独立任务应并行执行,不串行等待
127
253
  - 每个 subagent 只做一件事,做完汇报
128
254
  - subagent 不直接修改 claims/ 数据,汇报给主 agent,由主 agent 统一写入
129
255
  - 大量数据不塞上下文,用文件路径传递
130
256
 
131
- ## 7. 自我进化准则
257
+ ## 10. 自我进化准则
132
258
 
133
259
  这个系统应该越用越好用、越用越个性化。用户每一次纠正都是信号,不能丢掉。
134
260
 
135
- ### 7.1 用户纠正时的行为链
261
+ ### 10.1 用户纠正时的行为链
136
262
 
137
263
  当用户纠正 agent 的判断、输出或行为时,按以下顺序处理:
138
264
 
@@ -149,7 +275,8 @@ inbox 里的内容不全是案例素材,提取前先判断类型,分发到
149
275
  | 纠正性质 | 处理方式 |
150
276
  |---------|---------|
151
277
  | 用户画像不准(AI 不了解用户) | 询问用户是否更新对应 profile/ 文件 |
152
- | 工作流判断错误(系统级问题) | 提议提交 feedback-9000AI |
278
+ | 工作流判断错误(可固化的标准) | 提议追加到对应 workflows/ 文件的"已验证规则" |
279
+ | 工作流系统级问题 | 提议提交 feedback |
153
280
  | 单次误操作(偶发,不代表规律) | 当场纠正,不更新任何文件 |
154
281
 
155
282
  **第三步:更新 profile(如适用)**
@@ -164,18 +291,19 @@ inbox 里的内容不全是案例素材,提取前先判断类型,分发到
164
291
 
165
292
  如果纠正属于工作流或系统级问题,主动提议:
166
293
 
167
- > 「这个问题可能不只影响你,要不要提交一条 feedback 让团队知道?团队可以帮你做工程化落地,我可以帮你整理。」
294
+ > 「这个问题可能不只影响你,要不要提交一条 feedback 让团队知道?」
168
295
 
169
- ### 7.2 主动提议更新 profile 的时机
296
+ ### 10.2 主动提议更新的时机
170
297
 
171
- 不只是纠正时,以下情况也应主动询问是否更新画像:
298
+ 不只是纠正时,以下情况也应主动询问:
172
299
 
173
- - inbox 处理中发现了用户的第一人称风格自描述(见 4.2)
300
+ - inbox 处理中发现了用户的第一人称风格自描述(见 7.2)
174
301
  - 用户在对话中透露了新的业务信息、受众变化、领域调整
175
- - 用户多次对同一类输出表示满意(正向信号,可提炼为风格规律)
302
+ - 用户多次对同一类输出表示满意(正向信号,可提炼为工作流规则)
303
+ - 用户反复做同样的手动操作(可以固化为工作流步骤)
176
304
 
177
- ### 7.3 不该做的事
305
+ ### 10.3 不该做的事
178
306
 
179
307
  - 不要在用户没确认的情况下自动更新任何 profile 文件
180
- - 不要把单次纠正当成规律写入画像
181
- - 不要每次都问"要不要更新 profile",只在真正有信息量的时候问
308
+ - 不要把单次纠正当成规律写入工作流
309
+ - 不要每次都问"要不要更新",只在真正有信息量的时候问
@@ -0,0 +1,56 @@
1
+ # 内容弹药库
2
+
3
+ 所有内容素材统一存放在这里。按主题分文件夹,AI 通过三层检索找素材。
4
+
5
+ ## 目录结构
6
+
7
+ ```
8
+ assets/
9
+ ├── catalog.json ← 仓库总览(AI 第一个读的文件)
10
+ ├── _index/ ← 每个主题的素材索引
11
+ │ ├── 知识付费.jsonl
12
+ │ ├── AI就业.jsonl
13
+ │ └── _自己的.jsonl
14
+ ├── 知识付费/ ← 按主题存素材
15
+ │ ├── 栋哥_知识付费真相.md
16
+ │ └── 栋哥_知识付费真相.analysis.json
17
+ ├── _自己的/ ← 你自己的好内容
18
+ │ └── 片段/
19
+ ├── _待分类/ ← 还没归类的素材
20
+ └── README.md
21
+ ```
22
+
23
+ ## AI 怎么找素材
24
+
25
+ 1. 读 catalog.json → 判断该看哪几个主题
26
+ 2. 读 _index/{主题}.jsonl → 按标签筛出相关条目
27
+ 3. 只读命中的具体文件
28
+
29
+ **不允许跳过第一层直接扫全部索引。**
30
+
31
+ ## 素材入库
32
+
33
+ 每次存入新素材:
34
+ 1. 文件存到 `assets/{主题}/`,文件名:`{作者}_{标题}.md`
35
+ 2. `_index/{主题}.jsonl` 追加一行
36
+ 3. `catalog.json` 更新对应主题的 count 和 top_tags
37
+
38
+ ## 索引记录格式
39
+
40
+ ```jsonl
41
+ {"id":"a001","type":"transcription","title":"标题","tags":["标签1","标签2"],"source_ip":"作者","claim_ids":["cl001"],"hook":"开头第一句","structure":"递进","date":"2026-04-04","reuse_count":0,"file":"主题/作者_标题.md"}
42
+ ```
43
+
44
+ | 字段 | 说明 |
45
+ |------|------|
46
+ | id | 递增编号 a001, a002... |
47
+ | type | transcription(转写) / analysis(拆解) / quote(金句) / experience(亲身经历) / fragment(片段) |
48
+ | title | 素材标题 |
49
+ | tags | 关键词标签(行业+情绪+场景+人物) |
50
+ | source_ip | 来源博主名,自己的内容标记用户名 |
51
+ | claim_ids | 关联的主张 ID 列表,未关联时为 [] |
52
+ | hook | 开头钩子(第一句话),可选 |
53
+ | structure | 结构模式(递进/多角度/对比),可选 |
54
+ | date | 入库日期 |
55
+ | reuse_count | 被引用次数,初始 0 |
56
+ | file | 相对路径 |