@astro-minimax/cli 0.5.0 → 0.7.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.
Files changed (174) hide show
  1. package/README.md +69 -0
  2. package/dist/commands/ai.d.ts +2 -0
  3. package/dist/commands/ai.d.ts.map +1 -0
  4. package/dist/commands/ai.js +99 -0
  5. package/dist/commands/ai.js.map +1 -0
  6. package/dist/commands/data.d.ts +2 -0
  7. package/dist/commands/data.d.ts.map +1 -0
  8. package/dist/commands/data.js +111 -0
  9. package/dist/commands/data.js.map +1 -0
  10. package/dist/commands/hooks.d.ts +2 -0
  11. package/dist/commands/hooks.d.ts.map +1 -0
  12. package/dist/commands/hooks.js +378 -0
  13. package/dist/commands/hooks.js.map +1 -0
  14. package/dist/commands/init.d.ts +2 -0
  15. package/dist/commands/init.d.ts.map +1 -0
  16. package/dist/commands/init.js +50 -0
  17. package/dist/commands/init.js.map +1 -0
  18. package/dist/commands/podcast.d.ts +2 -0
  19. package/dist/commands/podcast.d.ts.map +1 -0
  20. package/dist/commands/podcast.js +89 -0
  21. package/dist/commands/podcast.js.map +1 -0
  22. package/dist/commands/post.d.ts +2 -0
  23. package/dist/commands/post.d.ts.map +1 -0
  24. package/dist/commands/post.js +190 -0
  25. package/dist/commands/post.js.map +1 -0
  26. package/dist/commands/profile.d.ts +2 -0
  27. package/dist/commands/profile.d.ts.map +1 -0
  28. package/dist/commands/profile.js +88 -0
  29. package/dist/commands/profile.js.map +1 -0
  30. package/dist/index.d.ts +3 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +81 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/tools/ai-process.d.ts +20 -0
  35. package/dist/tools/ai-process.d.ts.map +1 -0
  36. package/dist/tools/ai-process.js +607 -0
  37. package/dist/tools/ai-process.js.map +1 -0
  38. package/dist/tools/build-author-context.d.ts +13 -0
  39. package/dist/tools/build-author-context.d.ts.map +1 -0
  40. package/dist/tools/build-author-context.js +313 -0
  41. package/dist/tools/build-author-context.js.map +1 -0
  42. package/dist/tools/build-voice-profile.d.ts +12 -0
  43. package/dist/tools/build-voice-profile.d.ts.map +1 -0
  44. package/dist/tools/build-voice-profile.js +270 -0
  45. package/dist/tools/build-voice-profile.js.map +1 -0
  46. package/dist/tools/eval-ai-chat.d.ts +17 -0
  47. package/dist/tools/eval-ai-chat.d.ts.map +1 -0
  48. package/dist/tools/eval-ai-chat.js +362 -0
  49. package/dist/tools/eval-ai-chat.js.map +1 -0
  50. package/dist/tools/generate-author-profile.d.ts +14 -0
  51. package/dist/tools/generate-author-profile.d.ts.map +1 -0
  52. package/dist/tools/generate-author-profile.js +289 -0
  53. package/dist/tools/generate-author-profile.js.map +1 -0
  54. package/dist/tools/generate-cover.d.ts +14 -0
  55. package/dist/tools/generate-cover.d.ts.map +1 -0
  56. package/dist/tools/generate-cover.js +95 -0
  57. package/dist/tools/generate-cover.js.map +1 -0
  58. package/dist/tools/generate-og.d.ts +3 -0
  59. package/dist/tools/generate-og.d.ts.map +1 -0
  60. package/dist/tools/generate-og.js +254 -0
  61. package/dist/tools/generate-og.js.map +1 -0
  62. package/dist/tools/generate-related.d.ts +11 -0
  63. package/dist/tools/generate-related.d.ts.map +1 -0
  64. package/dist/tools/generate-related.js +124 -0
  65. package/dist/tools/generate-related.js.map +1 -0
  66. package/dist/tools/generate-tags.d.ts +14 -0
  67. package/dist/tools/generate-tags.d.ts.map +1 -0
  68. package/dist/tools/generate-tags.js +182 -0
  69. package/dist/tools/generate-tags.js.map +1 -0
  70. package/dist/tools/lib/ai-provider.d.ts +43 -0
  71. package/dist/tools/lib/ai-provider.d.ts.map +1 -0
  72. package/dist/tools/lib/ai-provider.js +146 -0
  73. package/dist/tools/lib/ai-provider.js.map +1 -0
  74. package/dist/tools/lib/audio-processor.d.ts +46 -0
  75. package/dist/tools/lib/audio-processor.d.ts.map +1 -0
  76. package/dist/tools/lib/audio-processor.js +188 -0
  77. package/dist/tools/lib/audio-processor.js.map +1 -0
  78. package/dist/tools/lib/frontmatter.d.ts +11 -0
  79. package/dist/tools/lib/frontmatter.d.ts.map +1 -0
  80. package/dist/tools/lib/frontmatter.js +80 -0
  81. package/dist/tools/lib/frontmatter.js.map +1 -0
  82. package/dist/tools/lib/index.d.ts +7 -0
  83. package/dist/tools/lib/index.d.ts.map +1 -0
  84. package/{template/tools/lib/index.ts → dist/tools/lib/index.js} +1 -0
  85. package/dist/tools/lib/index.js.map +1 -0
  86. package/dist/tools/lib/markdown.d.ts +6 -0
  87. package/dist/tools/lib/markdown.d.ts.map +1 -0
  88. package/dist/tools/lib/markdown.js +34 -0
  89. package/dist/tools/lib/markdown.js.map +1 -0
  90. package/dist/tools/lib/posts.d.ts +25 -0
  91. package/dist/tools/lib/posts.d.ts.map +1 -0
  92. package/dist/tools/lib/posts.js +63 -0
  93. package/dist/tools/lib/posts.js.map +1 -0
  94. package/dist/tools/lib/script-generator.d.ts +61 -0
  95. package/dist/tools/lib/script-generator.d.ts.map +1 -0
  96. package/dist/tools/lib/script-generator.js +182 -0
  97. package/dist/tools/lib/script-generator.js.map +1 -0
  98. package/dist/tools/lib/tts-provider.d.ts +65 -0
  99. package/dist/tools/lib/tts-provider.d.ts.map +1 -0
  100. package/dist/tools/lib/tts-provider.js +116 -0
  101. package/dist/tools/lib/tts-provider.js.map +1 -0
  102. package/dist/tools/lib/types.d.ts +129 -0
  103. package/dist/tools/lib/types.d.ts.map +1 -0
  104. package/dist/tools/lib/types.js +64 -0
  105. package/dist/tools/lib/types.js.map +1 -0
  106. package/dist/tools/lib/utils.d.ts +18 -0
  107. package/dist/tools/lib/utils.d.ts.map +1 -0
  108. package/dist/tools/lib/utils.js +121 -0
  109. package/dist/tools/lib/utils.js.map +1 -0
  110. package/dist/tools/lib/vectors.d.ts +27 -0
  111. package/dist/tools/lib/vectors.d.ts.map +1 -0
  112. package/dist/tools/lib/vectors.js +64 -0
  113. package/dist/tools/lib/vectors.js.map +1 -0
  114. package/dist/tools/podcast-feed.d.ts +6 -0
  115. package/dist/tools/podcast-feed.d.ts.map +1 -0
  116. package/dist/tools/podcast-feed.js +121 -0
  117. package/dist/tools/podcast-feed.js.map +1 -0
  118. package/dist/tools/podcast-generate.d.ts +15 -0
  119. package/dist/tools/podcast-generate.d.ts.map +1 -0
  120. package/dist/tools/podcast-generate.js +318 -0
  121. package/dist/tools/podcast-generate.js.map +1 -0
  122. package/dist/tools/podcast-list.d.ts +6 -0
  123. package/dist/tools/podcast-list.d.ts.map +1 -0
  124. package/dist/tools/podcast-list.js +66 -0
  125. package/dist/tools/podcast-list.js.map +1 -0
  126. package/dist/tools/summarize.d.ts +16 -0
  127. package/dist/tools/summarize.d.ts.map +1 -0
  128. package/dist/tools/summarize.js +108 -0
  129. package/dist/tools/summarize.js.map +1 -0
  130. package/dist/tools/translate.d.ts +13 -0
  131. package/dist/tools/translate.d.ts.map +1 -0
  132. package/dist/tools/translate.js +46 -0
  133. package/dist/tools/translate.js.map +1 -0
  134. package/dist/tools/vectorize.d.ts +13 -0
  135. package/dist/tools/vectorize.d.ts.map +1 -0
  136. package/dist/tools/vectorize.js +87 -0
  137. package/dist/tools/vectorize.js.map +1 -0
  138. package/package.json +14 -9
  139. package/template/astro.config.ts +8 -28
  140. package/template/datas/ai-seo.json +8 -0
  141. package/template/datas/ai-skip-list.json +1 -0
  142. package/template/datas/author-profile-context.json +21 -0
  143. package/template/datas/author-profile-report.json +21 -0
  144. package/template/datas/eval/gold-set.json +72 -0
  145. package/template/functions/README.md +82 -0
  146. package/template/functions/api/ai-info.ts +2 -2
  147. package/template/functions/api/chat.ts +4 -1
  148. package/template/functions/api/notify/comment.ts +140 -68
  149. package/template/functions/api/notify/debug.ts +41 -0
  150. package/template/functions/api/notify/status.ts +97 -0
  151. package/template/functions/api/notify/test-ai-chat.ts +67 -0
  152. package/template/package.json +22 -25
  153. package/template/src/config.ts +11 -0
  154. package/template/src/content.config.ts +29 -16
  155. package/template/src/env.d.ts +0 -5
  156. package/index.js +0 -36
  157. package/template/tools/README.md +0 -169
  158. package/template/tools/ai-process.ts +0 -816
  159. package/template/tools/build-author-context.ts +0 -405
  160. package/template/tools/build-voice-profile.ts +0 -322
  161. package/template/tools/generate-author-profile.ts +0 -369
  162. package/template/tools/generate-cover.ts +0 -123
  163. package/template/tools/generate-og.ts +0 -280
  164. package/template/tools/generate-related.ts +0 -146
  165. package/template/tools/generate-tags.ts +0 -251
  166. package/template/tools/lib/ai-provider.ts +0 -240
  167. package/template/tools/lib/frontmatter.ts +0 -94
  168. package/template/tools/lib/markdown.ts +0 -40
  169. package/template/tools/lib/posts.ts +0 -89
  170. package/template/tools/lib/utils.ts +0 -138
  171. package/template/tools/lib/vectors.ts +0 -96
  172. package/template/tools/summarize.ts +0 -142
  173. package/template/tools/translate.ts +0 -60
  174. package/template/tools/vectorize.ts +0 -105
@@ -1,405 +0,0 @@
1
- #!/usr/bin/env npx tsx
2
- /**
3
- * 构建作者上下文数据
4
- *
5
- * 聚合博客文章数据为统一的 author-context.json,
6
- * 为 AI 博客分身对话提供上下文。
7
- *
8
- * 用法:
9
- * pnpm context:build 构建作者上下文
10
- * pnpm context:build --include-body 包含文章正文内容
11
- */
12
-
13
- import { readdir, readFile } from "node:fs/promises";
14
- import { join } from "node:path";
15
- import { createHash } from "node:crypto";
16
- import {
17
- loadEnv,
18
- readJson,
19
- writeJson,
20
- truncate,
21
- normalizeSpace,
22
- parseCliArgs,
23
- DATA_DIR,
24
- BLOG_DIR,
25
- } from "./lib/utils.js";
26
- import { stripMarkdown } from "./lib/markdown.js";
27
- import { extractFrontmatter } from "./lib/frontmatter.js";
28
- import { hasAPIKey, getConfig } from "./lib/ai-provider.js";
29
-
30
- // ─── 常量 ─────────────────────────────────────────────────────
31
-
32
- const OUTPUT_FILE = join(DATA_DIR, "author-context.json");
33
- const SOURCES_DIR = join(DATA_DIR, "sources");
34
- const MAX_RECENT_POSTS = 200;
35
-
36
- const CATEGORY_LABELS: Record<string, string> = {
37
- 教程: "教程",
38
- 技术: "技术",
39
- 生活: "生活",
40
- 随笔: "随笔",
41
- 其他: "其他",
42
- };
43
-
44
- const THEME_STOPWORDS = new Set([
45
- "可以",
46
- "这个",
47
- "那个",
48
- "一些",
49
- "以及",
50
- "并且",
51
- "如果",
52
- "因为",
53
- "所以",
54
- "还是",
55
- "一个",
56
- "我们",
57
- "他们",
58
- "你们",
59
- "自己",
60
- "进行",
61
- "使用",
62
- "通过",
63
- "关于",
64
- "相关",
65
- "作者",
66
- "文章",
67
- "项目",
68
- "内容",
69
- "技术",
70
- "博客",
71
- "最近",
72
- "持续",
73
- "方式",
74
- "经验",
75
- "记录",
76
- "分享",
77
- "实践",
78
- "问题",
79
- "方案",
80
- ]);
81
-
82
- // ─── CLI 参数 ─────────────────────────────────────────────────
83
-
84
- interface CliFlags {
85
- includeBody: boolean;
86
- }
87
-
88
- function parseArgs(): CliFlags {
89
- return parseCliArgs({ includeBody: false });
90
- }
91
-
92
- // ─── 文章扫描 ─────────────────────────────────────────────────
93
-
94
- async function collectMarkdownFiles(dir: string): Promise<string[]> {
95
- const entries = await readdir(dir, { withFileTypes: true });
96
- const files: string[] = [];
97
-
98
- for (const entry of entries) {
99
- const fullPath = join(dir, entry.name);
100
- if (entry.isDirectory() && !entry.name.startsWith("_")) {
101
- files.push(...(await collectMarkdownFiles(fullPath)));
102
- } else if (entry.isFile() && entry.name.endsWith(".md")) {
103
- files.push(fullPath);
104
- }
105
- }
106
-
107
- return files;
108
- }
109
-
110
- interface RawPost {
111
- id: string;
112
- title: string;
113
- date: string;
114
- lang: string;
115
- category: string;
116
- tags: string[];
117
- description: string;
118
- summary?: string;
119
- keyPoints?: string[];
120
- body: string;
121
- url: string;
122
- }
123
-
124
- async function collectPosts(
125
- _siteUrl: string,
126
- includeBody: boolean
127
- ): Promise<RawPost[]> {
128
- const files = await collectMarkdownFiles(BLOG_DIR);
129
- const aiSummaries = await readJson<{
130
- articles?: Record<string, { data?: { summary?: string; keyPoints?: string[] } }>
131
- }>(join(DATA_DIR, "ai-summaries.json"), {
132
- articles: {},
133
- });
134
- const posts: RawPost[] = [];
135
-
136
- for (const filePath of files) {
137
- const raw = await readFile(filePath, "utf-8");
138
- const fm = extractFrontmatter(raw);
139
- const data = fm.data;
140
-
141
- // 跳过没有标题或日期的文章,以及草稿
142
- if (!data.title || !data.pubDatetime || data.draft) continue;
143
-
144
- const relativePath = filePath.replace(BLOG_DIR + "/", "");
145
- const lang = relativePath.startsWith("en/") ? "en" : "zh";
146
- const id = relativePath.replace(/\.md$/, "");
147
-
148
- const summaryEntry = aiSummaries.articles?.[id]?.data;
149
- const plainContent = stripMarkdown(fm.body);
150
-
151
- // URL 格式: /{lang}/posts/{slug}/
152
- // slug 是 id 的最后部分(去掉语言前缀)
153
- const slug = id.split("/").slice(1).join("/");
154
- posts.push({
155
- id,
156
- title: String(data.title),
157
- date: String(data.pubDatetime),
158
- lang,
159
- category: String(data.category || ""),
160
- tags: Array.isArray(data.tags) ? (data.tags as string[]) : [],
161
- description: String(data.description || ""),
162
- summary: summaryEntry?.summary || truncate(plainContent, 150),
163
- keyPoints: summaryEntry?.keyPoints || [],
164
- body: includeBody ? fm.body.slice(0, 5000) : "",
165
- url: `/${lang}/posts/${slug}/`,
166
- });
167
- }
168
-
169
- // 按日期降序排列
170
- posts.sort(
171
- (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
172
- );
173
-
174
- return posts;
175
- }
176
-
177
- // ─── 主题分析 ─────────────────────────────────────────────────
178
-
179
- function tokenizeThemeText(text: string): string[] {
180
- const raw = normalizeSpace(text);
181
- const tokens = raw.match(/[A-Za-z][A-Za-z0-9.+#-]{1,}|[\u4e00-\u9fa5]{2,6}/g) ?? [];
182
- return tokens.filter((token) => {
183
- const lower = token.toLowerCase();
184
- return !THEME_STOPWORDS.has(lower) && token.length >= 2;
185
- });
186
- }
187
-
188
- function buildThemeStats(posts: RawPost[]): string[] {
189
- const counts = new Map<string, number>();
190
-
191
- for (const post of posts) {
192
- // 分类权重
193
- if (post.category) {
194
- const label = CATEGORY_LABELS[post.category] || post.category;
195
- counts.set(label, (counts.get(label) || 0) + 3);
196
- }
197
- // 标题权重
198
- for (const token of tokenizeThemeText(post.title)) {
199
- counts.set(token, (counts.get(token) || 0) + 3);
200
- }
201
- // 摘要权重
202
- for (const token of tokenizeThemeText(post.summary || "")) {
203
- counts.set(token, (counts.get(token) || 0) + 2);
204
- }
205
- // 标签权重
206
- for (const tag of post.tags) {
207
- counts.set(tag, (counts.get(tag) || 0) + 2);
208
- }
209
- }
210
-
211
- return [...counts.entries()]
212
- .sort((a, b) => b[1] - a[1])
213
- .slice(0, 20)
214
- .map(([token]) => token);
215
- }
216
-
217
- // ─── 稳定事实提取 ─────────────────────────────────────────────
218
-
219
- function buildStableFacts(posts: RawPost[]) {
220
- const categoryCounts = new Map<string, number>();
221
-
222
- for (const post of posts) {
223
- if (post.category) {
224
- categoryCounts.set(
225
- post.category,
226
- (categoryCounts.get(post.category) || 0) + 1
227
- );
228
- }
229
- }
230
-
231
- const focusAreas = [...categoryCounts.entries()]
232
- .sort((a, b) => b[1] - a[1])
233
- .slice(0, 5)
234
- .map(([category]) => CATEGORY_LABELS[category] || category);
235
-
236
- const recurringTopics = buildThemeStats(posts).filter(
237
- (topic) => !focusAreas.includes(topic)
238
- );
239
-
240
- // 代表性文章(取最新的几篇)
241
- const flagshipPosts = posts.slice(0, 5).map((post) => ({
242
- title: post.title,
243
- date: post.date,
244
- url: post.url,
245
- }));
246
-
247
- // 语言分布
248
- const langDistribution = {
249
- zh: posts.filter((p) => p.lang === "zh").length,
250
- en: posts.filter((p) => p.lang === "en").length,
251
- };
252
-
253
- // 标签聚合
254
- const tagCounts = new Map<string, number>();
255
- for (const post of posts) {
256
- for (const tag of post.tags) {
257
- tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
258
- }
259
- }
260
- const topTags = [...tagCounts.entries()]
261
- .sort((a, b) => b[1] - a[1])
262
- .slice(0, 15)
263
- .map(([tag]) => tag);
264
-
265
- return {
266
- focusAreas,
267
- recurringTopics,
268
- flagshipPosts,
269
- contentFootprint: {
270
- posts: posts.length,
271
- zhPosts: langDistribution.zh,
272
- enPosts: langDistribution.en,
273
- },
274
- topTags,
275
- };
276
- }
277
-
278
- // ─── 时间线数据 ───────────────────────────────────────────────
279
-
280
- function buildTimelineFacts(posts: RawPost[]) {
281
- const latestPosts = posts.slice(0, 10).map((post) => ({
282
- date: post.date,
283
- title: post.title,
284
- url: post.url,
285
- lang: post.lang,
286
- }));
287
-
288
- return {
289
- latestPosts,
290
- };
291
- }
292
-
293
- // ─── 哈希计算 ─────────────────────────────────────────────────
294
-
295
- function computeContextHash(payload: unknown): string {
296
- return createHash("sha256")
297
- .update(JSON.stringify(payload))
298
- .digest("hex")
299
- .slice(0, 16);
300
- }
301
-
302
- // ─── 主流程 ───────────────────────────────────────────────────
303
-
304
- async function main() {
305
- const args = parseArgs();
306
- await loadEnv();
307
-
308
- const siteUrl = process.env.SITE_URL || "https://example.com";
309
-
310
- console.log("📦 构建作者上下文数据");
311
- console.log("━".repeat(50));
312
- console.log(` 站点 URL: ${siteUrl}`);
313
- console.log(` 输出目录: ${DATA_DIR}`);
314
- console.log("");
315
-
316
- // 收集博客文章
317
- console.log("📂 扫描博客文章...");
318
- const posts = await collectPosts(siteUrl, args.includeBody);
319
- console.log(` 找到 ${posts.length} 篇文章`);
320
- console.log(
321
- ` - 中文: ${posts.filter((p) => p.lang === "zh").length} 篇`
322
- );
323
- console.log(
324
- ` - 英文: ${posts.filter((p) => p.lang === "en").length} 篇`
325
- );
326
-
327
- // 保存文章摘要到 sources
328
- await writeJson(join(SOURCES_DIR, "blog-digest.json"), {
329
- generatedAt: new Date().toISOString(),
330
- count: posts.length,
331
- posts: posts.slice(0, MAX_RECENT_POSTS).map((p) => ({
332
- id: p.id,
333
- title: p.title,
334
- date: p.date,
335
- lang: p.lang,
336
- category: p.category,
337
- tags: p.tags,
338
- summary: p.summary,
339
- url: p.url,
340
- })),
341
- });
342
-
343
- // 构建事实数据
344
- const stableFacts = buildStableFacts(posts);
345
- const timelineFacts = buildTimelineFacts(posts);
346
-
347
- // 检查 AI 配置
348
- const aiConfig = hasAPIKey() ? getConfig() : null;
349
- if (aiConfig) {
350
- console.log(`\n🤖 AI 配置:`);
351
- console.log(` 模型: ${aiConfig.model}`);
352
- console.log(` API: ${aiConfig.baseUrl}`);
353
- }
354
-
355
- // 构建统一上下文
356
- const baseContext = {
357
- $schema: "author-context-v1",
358
- generatedAt: new Date().toISOString(),
359
- profile: {
360
- // 从站点配置获取,或使用默认值
361
- name: process.env.SITE_AUTHOR || "博主",
362
- siteUrl,
363
- description: process.env.SITE_DESCRIPTION || "",
364
- },
365
- posts: posts.slice(0, MAX_RECENT_POSTS).map((p) => ({
366
- id: p.id,
367
- title: p.title,
368
- date: p.date,
369
- lang: p.lang,
370
- category: p.category,
371
- tags: p.tags,
372
- summary: p.summary,
373
- keyPoints: p.keyPoints,
374
- url: p.url,
375
- ...(args.includeBody && { body: p.body }),
376
- })),
377
- stableFacts,
378
- timelineFacts,
379
- aiConfig: aiConfig
380
- ? {
381
- model: aiConfig.model,
382
- provider: aiConfig.provider,
383
- }
384
- : null,
385
- };
386
-
387
- const context = {
388
- ...baseContext,
389
- contextHash: computeContextHash(baseContext),
390
- };
391
-
392
- await writeJson(OUTPUT_FILE, context);
393
-
394
- console.log("\n✅ 构建完成");
395
- console.log(`📄 输出文件: ${OUTPUT_FILE}`);
396
- console.log("\n📊 数据概览:");
397
- console.log(` 文章总数: ${stableFacts.contentFootprint.posts}`);
398
- console.log(` 聚焦领域: ${stableFacts.focusAreas.join("、")}`);
399
- console.log(` 热门标签: ${stableFacts.topTags.slice(0, 5).join("、")}`);
400
- }
401
-
402
- main().catch((error) => {
403
- console.error("❌ 构建失败:", error.message);
404
- process.exit(1);
405
- });