@gobing-ai/knowledge-kit 0.0.16 → 0.0.18
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/.claude-plugin/marketplace.json +1 -1
- package/README.md +74 -0
- package/dist/index.js +46 -100
- package/package.json +19 -1
- package/plugins/generations/content-gen/dist/index.js +42 -94
- package/plugins/generations/core-facts-gen/dist/index.js +51 -100
- package/plugins/generations/daily-article-gen/dist/index.js +98 -100
- package/plugins/generations/daily-article-gen/src/index.ts +80 -6
- package/plugins/generations/dailynews-gen/dist/index.js +144 -130
- package/plugins/generations/dailynews-gen/src/script-builder.ts +141 -33
- package/plugins/generations/episode-plan-gen/dist/index.js +124 -115
- package/plugins/generations/episode-plan-gen/src/index.ts +111 -24
- package/plugins/generations/image-gen/dist/index.js +63 -115
- package/plugins/generations/news-report-gen/dist/index.js +164 -100
- package/plugins/generations/news-report-gen/src/index.ts +199 -7
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/pipeline.py +17 -1
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/qc.py +4 -22
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/voicescript.py +8 -0
- package/plugins/generations/voice-gen/dist/index.js +136 -117
- package/plugins/generations/voice-gen/src/concat.ts +34 -0
- package/plugins/generations/voice-gen/src/index.ts +6 -4
- package/plugins/generations/voice-gen/src/voicescript.ts +61 -0
- package/plugins/ingestions/aihot-ingest/dist/index.js +47 -97
- package/plugins/ingestions/horizon-ingest/dist/index.js +47 -96
- package/plugins/ingestions/karakeep-local/dist/index.js +217 -266
- package/plugins/ingestions/last30days-ingest/dist/index.js +42 -91
- package/plugins/ingestions/web-search/dist/index.js +68 -117
- package/plugins/kk/commands/workflow-run.md +25 -2
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/kk-workflow-stages.ts +522 -163
- package/plugins/kk/scripts/validate-voicescript.ts +55 -0
- package/plugins/kk/skills/publish/SKILL.md +80 -0
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +125 -120
- package/plugins/kk/workflows/leftover-publish-fanout-example.yaml +53 -0
- package/plugins/publishings/emdash-pub/dist/index.js +42 -91
- package/plugins/publishings/infoq-pub/dist/index.js +21994 -0
- package/plugins/publishings/infoq-pub/package.json +17 -0
- package/plugins/publishings/infoq-pub/plugin.json +7 -0
- package/plugins/publishings/infoq-pub/src/index.ts +123 -0
- package/plugins/publishings/infoq-pub/tsconfig.json +4 -0
- package/plugins/publishings/juejin-pub/dist/index.js +21994 -0
- package/plugins/publishings/juejin-pub/package.json +17 -0
- package/plugins/publishings/juejin-pub/plugin.json +7 -0
- package/plugins/publishings/juejin-pub/src/index.ts +123 -0
- package/plugins/publishings/juejin-pub/tsconfig.json +4 -0
- package/plugins/publishings/medium-pub/dist/index.js +22041 -0
- package/plugins/publishings/medium-pub/package.json +17 -0
- package/plugins/publishings/medium-pub/plugin.json +7 -0
- package/plugins/publishings/medium-pub/src/index.ts +215 -0
- package/plugins/publishings/medium-pub/tsconfig.json +4 -0
- package/plugins/publishings/podcast-pub/dist/index.js +183 -123
- package/plugins/publishings/podcast-pub/src/index.ts +47 -3
- package/plugins/publishings/podcast-pub/src/show-notes.ts +153 -34
- package/plugins/publishings/qiita-pub/dist/index.js +42 -91
- package/plugins/publishings/substack-pub/dist/index.js +21994 -0
- package/plugins/publishings/substack-pub/package.json +17 -0
- package/plugins/publishings/substack-pub/plugin.json +7 -0
- package/plugins/publishings/substack-pub/src/index.ts +123 -0
- package/plugins/publishings/substack-pub/tsconfig.json +4 -0
- package/plugins/publishings/surfdash-pub/dist/index.js +172 -103
- package/plugins/publishings/surfdash-pub/src/index.ts +32 -2
- package/plugins/publishings/wechatmp-pub/dist/index.js +22119 -0
- package/plugins/publishings/wechatmp-pub/package.json +17 -0
- package/plugins/publishings/wechatmp-pub/plugin.json +7 -0
- package/plugins/publishings/wechatmp-pub/src/index.ts +304 -0
- package/plugins/publishings/wechatmp-pub/tsconfig.json +4 -0
- package/plugins/publishings/x-pub/dist/index.js +22110 -0
- package/plugins/publishings/x-pub/package.json +17 -0
- package/plugins/publishings/x-pub/plugin.json +7 -0
- package/plugins/publishings/x-pub/src/index.ts +222 -0
- package/plugins/publishings/x-pub/tsconfig.json +4 -0
- package/plugins/publishings/xhs-pub/dist/index.js +21994 -0
- package/plugins/publishings/xhs-pub/package.json +17 -0
- package/plugins/publishings/xhs-pub/plugin.json +7 -0
- package/plugins/publishings/xhs-pub/src/index.ts +123 -0
- package/plugins/publishings/xhs-pub/tsconfig.json +4 -0
- package/plugins/publishings/zenn-pub/dist/index.js +42 -91
|
@@ -35,6 +35,9 @@ export const VoiceScriptSegmentSchema = z.object({
|
|
|
35
35
|
engine: z.string().min(1).optional(),
|
|
36
36
|
language: z.string().min(1).optional(),
|
|
37
37
|
gap_ms: z.number().int().nonnegative().optional(),
|
|
38
|
+
// Per-segment playback rate (0.5–2.0). Facts read at 1.0 (precise), insights at 1.12
|
|
39
|
+
// (conversational); omitted -> the generator's global default (workflow OMNIVOICE_SPEED).
|
|
40
|
+
speed: z.number().min(0.5).max(2.0).optional(),
|
|
38
41
|
max_chunk_chars: z.number().int().optional(),
|
|
39
42
|
crossfade_ms: z.number().int().optional(),
|
|
40
43
|
personality: z.boolean().optional(),
|
|
@@ -144,39 +147,131 @@ export function normalizeBroadcastText(raw: string, isZh: boolean): string {
|
|
|
144
147
|
/**
|
|
145
148
|
* Synthesizes a natural, concise spoken perspective / takeaway based on the story facts.
|
|
146
149
|
* Gives the monologue a demonstrative tech podcast talk feel rather than mechanical news reading.
|
|
150
|
+
*
|
|
151
|
+
* `used` is the per-episode set of lines already spoken (task 0139 R2). A colliding item gets
|
|
152
|
+
* `null` — no commentary segment at all — because a repeated line is worse than none and a
|
|
153
|
+
* second-choice line would be a different topic misfire (Q&A item 5).
|
|
147
154
|
*/
|
|
148
|
-
export function synthesizePersonalUnderstanding(
|
|
155
|
+
export function synthesizePersonalUnderstanding(
|
|
156
|
+
title: string,
|
|
157
|
+
body: string,
|
|
158
|
+
isZh: boolean,
|
|
159
|
+
used: Set<string>,
|
|
160
|
+
): string | null {
|
|
149
161
|
const combined = `${title} ${body}`.toLowerCase();
|
|
162
|
+
const line = isZh ? zhInsightFor(combined) : enInsightFor(combined);
|
|
163
|
+
if (used.has(line)) return null;
|
|
164
|
+
used.add(line);
|
|
165
|
+
return line;
|
|
166
|
+
}
|
|
150
167
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
return '这项新动态反映出技术演进正在进一步提速,对相关领域的实际影响也非常值得我们持续关注。';
|
|
168
|
+
/**
|
|
169
|
+
* Bucket order is first-match-wins, and the buckets are deliberately disjoint (task 0139 R2).
|
|
170
|
+
* The energy line requires energy-side keywords: the pre-fix bucket matched bare
|
|
171
|
+
* `芯片|硬件|工厂`, so it fired after the Nvidia-regulation and Apple Secure-Enclave items on the
|
|
172
|
+
* 2026-09-16 run. Hardware/chip content now has its own line instead.
|
|
173
|
+
*/
|
|
174
|
+
function zhInsightFor(combined: string): string {
|
|
175
|
+
if (/jakub|szymon|首席科学家|创始团队|技术领袖|黄金搭档/i.test(combined)) {
|
|
176
|
+
return '在各种技术光环背后,正是这样专注而不知疲倦的底层攻坚者,为今天整个大模型生态奠定了最坚实的技术基石。';
|
|
177
|
+
}
|
|
178
|
+
if (/算力|吉瓦|电力|能源|数据中心|基础设施|基建/.test(combined)) {
|
|
179
|
+
return '这其实释放了一个非常明确的信号:AI竞争的下半场,已经不再只是单纯拼模型参数,更是电力、能源和全栈工业基础设施的硬核较量。';
|
|
165
180
|
}
|
|
181
|
+
if (/芯片|硬件|处理器|enclave/i.test(combined)) {
|
|
182
|
+
return '真正被重新定义的,是硬件层承担的安全与算力责任:把能力做进硅片层更可靠、也更难绕过,但生态锁定的代价需要一起算进去。';
|
|
183
|
+
}
|
|
184
|
+
// Video/creation only — bare `生成` matched every generative-AI body (Meta and both Gemini
|
|
185
|
+
// items on the 2026-09-16 run) and bare `chatgpt` matched the Meta item, which is what made
|
|
186
|
+
// this line a topic misfire. A product name is not a creation-barrier story.
|
|
187
|
+
if (/sora|视频|生成视频|视频生成|文生视频|内容创作|cameo/i.test(combined)) {
|
|
188
|
+
return '我个人觉得,这次最大的看点是内容创作门槛的再次降低。不过正如团队提到的,如何平衡创作自由与防止低质内容泛滥,会是整个行业接下来最值得观察的关键考题。';
|
|
189
|
+
}
|
|
190
|
+
if (/融资|投资|市场|商业|估值|合作/.test(combined)) {
|
|
191
|
+
return '这项进展反映出资本与产业界正在加速形成合力,后续实际业务场景的落地效果非常值得我们持续跟进。';
|
|
192
|
+
}
|
|
193
|
+
return '这项新动态反映出技术演进正在进一步提速,对相关领域的实际影响也非常值得我们持续关注。';
|
|
194
|
+
}
|
|
166
195
|
|
|
167
|
-
|
|
196
|
+
/** English mirror of {@link zhInsightFor} — same bucket order, same disjointness rule. */
|
|
197
|
+
function enInsightFor(combined: string): string {
|
|
168
198
|
if (/jakub|szymon|chief scientist|founding team|technical leader|partnership/i.test(combined)) {
|
|
169
199
|
return 'Behind the headlines, it is relentless technical leadership and deep engineering rigor that continue to drive these foundational milestones.';
|
|
170
200
|
}
|
|
171
|
-
if (/compute|gigawatt|
|
|
201
|
+
if (/compute|gigawatt|power|energy|datacenter|infrastructure/.test(combined)) {
|
|
172
202
|
return 'This signals clearly that the AI frontier is no longer just about algorithms, but a massive contest of power, energy, and full-stack industrial engineering.';
|
|
173
203
|
}
|
|
174
|
-
if (/
|
|
204
|
+
if (/chip|hardware|processor|enclave/i.test(combined)) {
|
|
205
|
+
return 'What stands out is the hardware layer quietly taking on the security and compute burden: putting the guarantee in silicon is harder to bypass, and it carries its own lock-in cost.';
|
|
206
|
+
}
|
|
207
|
+
if (/sora|video|video generation|content creation|cameo/i.test(combined)) {
|
|
175
208
|
return 'In my view, the biggest takeaway here is the dramatic lowering of creation barriers, while content quality governance will be the key test ahead.';
|
|
176
209
|
}
|
|
177
210
|
return 'This development highlights how rapidly the ecosystem is moving from experimental proofs of concept into large-scale execution.';
|
|
178
211
|
}
|
|
179
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Longest fact segment the script emits (task 0139 R4).
|
|
215
|
+
* ponytail: observed zh speech rates 3.4–6.1 chars/s put 300 chars at 49–88s per chunk; raise only
|
|
216
|
+
* with measured evidence. 400 would reach ~117s at the slowest observed rate, where whisper
|
|
217
|
+
* transcription fidelity collapsed to 0.25–0.41 on the 2026-09-16 run.
|
|
218
|
+
*/
|
|
219
|
+
export const MAX_FACT_SEGMENT_CHARS = 300;
|
|
220
|
+
|
|
221
|
+
/** Punctuation and space the translator drifts between a title and its body restatement. */
|
|
222
|
+
const TITLE_ECHO_NOISE_RE = /[,。、::;;!!??…\s「」『』【】]/g;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Drop a leading restatement of the item title from its body (task 0139 R1).
|
|
226
|
+
*
|
|
227
|
+
* `plan-translate` derives each title from the body's first sentence, so the echo is always a
|
|
228
|
+
* prefix with at most punctuation drift (`:` → `,`) — the rule is a deterministic significant-
|
|
229
|
+
* character prefix compare, never a fuzzy/LLM similarity match (anti-pattern list). The body is
|
|
230
|
+
* returned untouched whenever the normalized prefix does not equal the normalized title, and a
|
|
231
|
+
* body that is *only* the restatement collapses to the empty string (the lead alone carries it).
|
|
232
|
+
*/
|
|
233
|
+
export function stripLeadingTitleEcho(body: string, title: string): string {
|
|
234
|
+
const wanted = title.replace(TITLE_ECHO_NOISE_RE, '');
|
|
235
|
+
if (wanted.length < 4) return body; // a short title would match too much of the body
|
|
236
|
+
let matched = '';
|
|
237
|
+
let end = -1;
|
|
238
|
+
for (let i = 0; i < body.length && matched.length < wanted.length; i++) {
|
|
239
|
+
const kept = (body[i] ?? '').replace(TITLE_ECHO_NOISE_RE, '');
|
|
240
|
+
if (kept === '') continue; // drift punctuation between title and echo
|
|
241
|
+
matched += kept;
|
|
242
|
+
if (matched.length >= wanted.length) end = i + 1;
|
|
243
|
+
}
|
|
244
|
+
if (end === -1 || matched !== wanted) return body;
|
|
245
|
+
return body.slice(end).replace(/^[,。、::;;!!??…\s「」『』【】]+/, '');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Split a fact text into chunks of at most `maxChars` (task 0139 R4).
|
|
250
|
+
*
|
|
251
|
+
* Greedy packing at clause boundaries: a chunk closes when the next clause would push it past the
|
|
252
|
+
* cap, so `chunks.join('') === text` always holds — the `[\s\S]` alternative is what keeps leading
|
|
253
|
+
* or stray punctuation from being dropped by the match. `;` is a boundary too: a clause-joined
|
|
254
|
+
* sentence would otherwise stay whole and blow past the ~90s budget the cap exists to protect. A
|
|
255
|
+
* single clause longer than the cap is emitted whole — splitting mid-thought is worse than a long
|
|
256
|
+
* segment — and is left to the QC duration audit.
|
|
257
|
+
*/
|
|
258
|
+
export function splitFactText(text: string, maxChars: number = MAX_FACT_SEGMENT_CHARS): string[] {
|
|
259
|
+
if (text.length <= maxChars) return [text];
|
|
260
|
+
const clauses = text.match(/[^。!?…;]+[。!?…;]*|[\s\S]/g) ?? [text];
|
|
261
|
+
const chunks: string[] = [];
|
|
262
|
+
let current = '';
|
|
263
|
+
for (const clause of clauses) {
|
|
264
|
+
if (current !== '' && current.length + clause.length > maxChars) {
|
|
265
|
+
chunks.push(current);
|
|
266
|
+
current = clause;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
current += clause;
|
|
270
|
+
}
|
|
271
|
+
if (current !== '') chunks.push(current);
|
|
272
|
+
return chunks.length > 0 ? chunks : [text];
|
|
273
|
+
}
|
|
274
|
+
|
|
180
275
|
export function buildNewsVoiceScript(docs: Doc[], options?: ScriptBuilderOptions): VoiceScript {
|
|
181
276
|
const language = z.enum(['zh', 'en']).parse((options?.language || 'zh').toLowerCase());
|
|
182
277
|
const isZh = language === 'zh';
|
|
@@ -259,10 +354,13 @@ export function buildNewsVoiceScript(docs: Doc[], options?: ScriptBuilderOptions
|
|
|
259
354
|
});
|
|
260
355
|
|
|
261
356
|
// 2. Story segments: demonstrative talk with facts + conversational insight
|
|
357
|
+
const usedInsights = new Set<string>(); // one per episode (task 0139 R2)
|
|
262
358
|
docs.forEach((doc, idx) => {
|
|
263
359
|
const isLast = idx === docs.length - 1 && docs.length > 1;
|
|
264
360
|
const cleanTitle = (doc.title ?? '').trim();
|
|
265
|
-
|
|
361
|
+
// R1: drop the title restatement plan-translate prepends to the body, so the lead's
|
|
362
|
+
// 【title】 is the only place the title is spoken for this item.
|
|
363
|
+
const cleanBody = stripLeadingTitleEcho((doc.body ?? '').trim(), cleanTitle);
|
|
266
364
|
|
|
267
365
|
// Conversational lead
|
|
268
366
|
const lead = isZh
|
|
@@ -278,23 +376,30 @@ export function buildNewsVoiceScript(docs: Doc[], options?: ScriptBuilderOptions
|
|
|
278
376
|
: `Next, turning our attention to ${cleanTitle}.`;
|
|
279
377
|
|
|
280
378
|
const normalizedLead = normalizeBroadcastText(lead, isZh);
|
|
281
|
-
const normalizedBody = normalizeBroadcastText(cleanBody, isZh);
|
|
282
|
-
const insight = synthesizePersonalUnderstanding(cleanTitle, cleanBody, isZh);
|
|
283
|
-
const normalizedInsight = normalizeBroadcastText(insight, isZh);
|
|
379
|
+
const normalizedBody = cleanBody === '' ? '' : normalizeBroadcastText(cleanBody, isZh);
|
|
380
|
+
const insight = synthesizePersonalUnderstanding(cleanTitle, cleanBody, isZh, usedInsights);
|
|
381
|
+
const normalizedInsight = insight === null ? null : normalizeBroadcastText(insight, isZh);
|
|
284
382
|
|
|
285
383
|
const factText = isZh ? `${normalizedLead}${normalizedBody}` : `${normalizedLead} ${normalizedBody}`;
|
|
286
384
|
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
385
|
+
// R4: facts first, chunked so no single segment runs past ~90s of speech; then the insight
|
|
386
|
+
// only when this episode has not already spoken that line (R2 collision ⇒ no segment).
|
|
387
|
+
for (const chunk of splitFactText(factText)) {
|
|
388
|
+
segments.push({
|
|
389
|
+
text: chunk,
|
|
390
|
+
gap_ms: 500, // Natural breathing pause before giving personal perspective
|
|
391
|
+
emotion: 'professional, clear',
|
|
392
|
+
speed: 1.0, // 20260917 pacing: facts/data read slower than the global 1.06 default
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
if (normalizedInsight !== null) {
|
|
396
|
+
segments.push({
|
|
397
|
+
text: normalizedInsight,
|
|
398
|
+
gap_ms: 1200, // Clear breath between news items (listener complaint: items ran together)
|
|
399
|
+
emotion: 'thoughtful, personal',
|
|
400
|
+
speed: 1.12, // 20260917 pacing: conversational asides read faster than facts
|
|
401
|
+
});
|
|
402
|
+
}
|
|
298
403
|
});
|
|
299
404
|
|
|
300
405
|
// 3. Outro segment: Warm, conversational wrap-up
|
|
@@ -403,6 +508,9 @@ export function formatVoiceScriptToYaml(script: VoiceScript): string {
|
|
|
403
508
|
if (segment.gap_ms !== undefined) {
|
|
404
509
|
lines.push(` gap_ms: ${segment.gap_ms}`);
|
|
405
510
|
}
|
|
511
|
+
if (segment.speed !== undefined) {
|
|
512
|
+
lines.push(` speed: ${segment.speed}`);
|
|
513
|
+
}
|
|
406
514
|
if (segment.seed !== undefined) {
|
|
407
515
|
lines.push(` seed: ${segment.seed}`);
|
|
408
516
|
}
|