@gobing-ai/knowledge-kit 0.0.23 → 0.0.25

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 (67) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +137 -59
  3. package/package.json +4 -4
  4. package/plugins/generations/content-gen/dist/index.js +97 -45
  5. package/plugins/generations/core-facts-gen/dist/index.js +106 -57
  6. package/plugins/generations/daily-article-gen/dist/index.js +105 -56
  7. package/plugins/generations/dailynews-gen/dist/index.js +235 -57
  8. package/plugins/generations/dailynews-gen/src/index.ts +67 -2
  9. package/plugins/generations/dailynews-gen/src/script-builder.ts +36 -15
  10. package/plugins/generations/dailynews-gen/src/script-plan.ts +142 -0
  11. package/plugins/generations/episode-plan-gen/dist/index.js +115 -66
  12. package/plugins/generations/gemini-tts/README.md +24 -0
  13. package/plugins/generations/gemini-tts/dist/index.js +326 -260
  14. package/plugins/generations/gemini-tts/src/gemini-client.ts +41 -11
  15. package/plugins/generations/image-gen/dist/index.js +338 -117
  16. package/plugins/generations/image-gen/presets/formats/daily-news-cover.yaml +19 -0
  17. package/plugins/generations/image-gen/presets/formats/xhs-card.yaml +55 -0
  18. package/plugins/generations/image-gen/presets/styles/daily-news.yaml +9 -0
  19. package/plugins/generations/image-gen/presets/styles/xhs-knowledge.yaml +7 -0
  20. package/plugins/generations/image-gen/src/index.ts +148 -14
  21. package/plugins/generations/image-gen/src/job.ts +30 -0
  22. package/plugins/generations/image-gen/src/providers/errors.ts +91 -0
  23. package/plugins/generations/image-gen/src/providers/types.ts +79 -0
  24. package/plugins/generations/news-report-gen/dist/index.js +136 -55
  25. package/plugins/generations/news-report-gen/src/index.ts +57 -1
  26. package/plugins/generations/voice-gen/dist/index.js +119 -67
  27. package/plugins/ingestions/aihot-ingest/dist/index.js +103 -53
  28. package/plugins/ingestions/horizon-ingest/dist/index.js +102 -53
  29. package/plugins/ingestions/karakeep-local/dist/index.js +273 -224
  30. package/plugins/ingestions/last30days-ingest/dist/index.js +97 -48
  31. package/plugins/ingestions/web-search/dist/index.js +121 -72
  32. package/plugins/kk/config.example.yaml +13 -0
  33. package/plugins/kk/plugin.json +1 -1
  34. package/plugins/kk/scripts/daily-voice-style.zh.md +52 -0
  35. package/plugins/kk/scripts/kk-workflow-stages.ts +433 -16
  36. package/plugins/kk/skills/publish/SKILL.md +24 -1
  37. package/plugins/kk/skills/xhs-images/SKILL.md +89 -0
  38. package/plugins/kk/skills/xhs-images/references/card-layouts.md +35 -0
  39. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +162 -11
  40. package/plugins/publishings/emdash-pub/dist/index.js +94 -45
  41. package/plugins/publishings/infoq-pub/dist/index.js +94 -45
  42. package/plugins/publishings/juejin-pub/dist/index.js +94 -45
  43. package/plugins/publishings/medium-pub/dist/index.js +94 -45
  44. package/plugins/publishings/podcast-pub/dist/index.js +115 -55
  45. package/plugins/publishings/podcast-pub/src/map.ts +30 -10
  46. package/plugins/publishings/podcast-pub/src/show-notes.ts +6 -1
  47. package/plugins/publishings/qiita-pub/dist/index.js +94 -45
  48. package/plugins/publishings/substack-pub/dist/index.js +94 -45
  49. package/plugins/publishings/surfdash-pub/dist/index.js +99 -63
  50. package/plugins/publishings/surfdash-pub/src/index.ts +10 -1
  51. package/plugins/publishings/wechatmp-pub/dist/index.js +94 -45
  52. package/plugins/publishings/x-pub/dist/index.js +94 -45
  53. package/plugins/publishings/xhs-pub/dist/index.js +8908 -229
  54. package/plugins/publishings/xhs-pub/package.json +1 -0
  55. package/plugins/publishings/xhs-pub/plugin.json +1 -1
  56. package/plugins/publishings/xhs-pub/src/automation-guard.ts +128 -0
  57. package/plugins/publishings/xhs-pub/src/index.ts +753 -24
  58. package/plugins/publishings/xhs-pub/src/profile-check.ts +133 -0
  59. package/plugins/publishings/zenn-pub/dist/index.js +94 -45
  60. package/plugins/sp/lib/env.ts +46 -0
  61. package/plugins/sp/lib/inline-run.generated.mjs +1489 -0
  62. package/plugins/sp/scripts/feature-verification-steps.ts +275 -0
  63. package/plugins/sp/scripts/inline-run-setup.ts +571 -47
  64. package/plugins/sp/scripts/wrapup-drift-probe.mjs +190 -0
  65. package/plugins/sp/scripts/wrapup-drift-probe.ts +258 -0
  66. package/plugins/sp/scripts/wrapup-steps.mjs +77 -6
  67. package/plugins/sp/scripts/wrapup-steps.ts +91 -5
@@ -1,5 +1,6 @@
1
1
  import type { Doc } from '@gobing-ai/kk-core';
2
2
  import { z } from 'zod';
3
+ import type { ScriptPlan } from './script-plan';
3
4
 
4
5
  export const EffectConfigSchema = z.object({
5
6
  type: z.string().min(1),
@@ -74,6 +75,8 @@ export interface ScriptBuilderOptions {
74
75
  emotion?: string;
75
76
  effectPreset?: string;
76
77
  speakers?: Record<string, VoiceSpeaker>;
78
+ /** LLM-authored spoken plan (F613). Substituted on the zh path only; en ignores it. */
79
+ scriptPlan?: ScriptPlan;
77
80
  }
78
81
 
79
82
  const ZH_DIGITS = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
@@ -353,6 +356,11 @@ export function buildNewsVoiceScript(docs: Doc[], options?: ScriptBuilderOptions
353
356
  hostName = hostName.charAt(0).toUpperCase() + hostName.slice(1);
354
357
  }
355
358
 
359
+ // F613: the plan owns the words on the zh path; en keeps templates (design: substitution is
360
+ // zh-only). Items are matched to docs by id and doc order wins; plan item order is ignored.
361
+ const scriptPlan = isZh ? options?.scriptPlan : undefined;
362
+ const planItems = new Map((scriptPlan?.items ?? []).map((item) => [item.id, item]));
363
+
356
364
  // 1. Opening segment: Conversational podcast hook + overview
357
365
  const countText = isZh ? `${formatChineseCount(docs.length)}条` : `${docs.length}`;
358
366
  const titlesSummary = docs
@@ -362,7 +370,11 @@ export function buildNewsVoiceScript(docs: Doc[], options?: ScriptBuilderOptions
362
370
  .join('、');
363
371
 
364
372
  const introText = isZh
365
- ? `大家好,今天是${dateStr},欢迎收听由${hostName}为您带来的今日AI科技漫谈。今天科技圈有几条非常有看点的大消息,我精选了${countText}最值得关注的重点动态来和大家聊一聊${titlesSummary ? `,包括${titlesSummary}` : ''}。话不多说,我们马上进入今天的正题。`
373
+ ? scriptPlan
374
+ ? // F613: fixed greeting stays builder-owned; the plan only fills the slot between
375
+ // greeting and first story (the template tail 从「今天科技圈有几条…正题。」 is replaced).
376
+ `大家好,今天是${dateStr},欢迎收听由${hostName}为您带来的今日AI科技漫谈。${scriptPlan.intro}`
377
+ : `大家好,今天是${dateStr},欢迎收听由${hostName}为您带来的今日AI科技漫谈。今天科技圈有几条非常有看点的大消息,我精选了${countText}最值得关注的重点动态来和大家聊一聊${titlesSummary ? `,包括${titlesSummary}` : ''}。话不多说,我们马上进入今天的正题。`
366
378
  : `Hello and welcome. Today is ${dateStr}. This is ${hostName} with your AI Daily Briefing. In today's episode, we have ${countText} fascinating stories from the AI world. Let's dive straight in.`;
367
379
 
368
380
  segments.push({
@@ -380,22 +392,28 @@ export function buildNewsVoiceScript(docs: Doc[], options?: ScriptBuilderOptions
380
392
  // 【title】 is the only place the title is spoken for this item.
381
393
  const cleanBody = stripLeadingTitleEcho(stripTagMetadataLines((doc.body ?? '').trim()), cleanTitle);
382
394
 
383
- // Conversational lead
384
- const lead = isZh
385
- ? idx === 0
386
- ? `首先来聊聊大家非常关注的【${cleanTitle}】。`
395
+ // Conversational lead. F613: the plan lead is a bridge that keeps 【title】 spoken exactly
396
+ // once (0139 R1) and must not contain the title itself (style contract, R1).
397
+ const planItem = planItems.get(doc.id);
398
+ const lead = planItem
399
+ ? `${planItem.lead}【${cleanTitle}】。`
400
+ : isZh
401
+ ? idx === 0
402
+ ? `首先来聊聊大家非常关注的【${cleanTitle}】。`
403
+ : isLast
404
+ ? `最后,来看看今天的最后一条资讯,【${cleanTitle}】。`
405
+ : `接着我们把目光转向另一条重要进展,【${cleanTitle}】。`
406
+ : idx === 0
407
+ ? `First up today, let's look at ${cleanTitle}.`
387
408
  : isLast
388
- ? `最后,来看看今天的最后一条资讯,【${cleanTitle}】。`
389
- : `接着我们把目光转向另一条重要进展,【${cleanTitle}】。`
390
- : idx === 0
391
- ? `First up today, let's look at ${cleanTitle}.`
392
- : isLast
393
- ? `And finally today, let's wrap up with ${cleanTitle}.`
394
- : `Next, turning our attention to ${cleanTitle}.`;
409
+ ? `And finally today, let's wrap up with ${cleanTitle}.`
410
+ : `Next, turning our attention to ${cleanTitle}.`;
395
411
 
396
412
  const normalizedLead = normalizeBroadcastText(lead, isZh);
397
413
  const normalizedBody = cleanBody === '' ? '' : normalizeBroadcastText(cleanBody, isZh);
398
- const insight = synthesizePersonalUnderstanding(cleanTitle, cleanBody, isZh, usedInsights);
414
+ // F613: the plan insight is grounded in this item and always spoken; the keyword-bucket
415
+ // synthesizer (and its uniqueness set) only runs on the template path.
416
+ const insight = planItem?.insight ?? synthesizePersonalUnderstanding(cleanTitle, cleanBody, isZh, usedInsights);
399
417
  const normalizedInsight = insight === null ? null : normalizeBroadcastText(insight, isZh);
400
418
 
401
419
  const factText = isZh ? `${normalizedLead}${normalizedBody}` : `${normalizedLead} ${normalizedBody}`;
@@ -420,9 +438,12 @@ export function buildNewsVoiceScript(docs: Doc[], options?: ScriptBuilderOptions
420
438
  }
421
439
  });
422
440
 
423
- // 3. Outro segment: Warm, conversational wrap-up
441
+ // 3. Outro segment: Warm, conversational wrap-up. F613: the plan outro replaces the template
442
+ // wrap-up head; the fixed sign-off stays builder-owned.
424
443
  const outroText = isZh
425
- ? `好了,以上就是今天AI科技漫谈的全部内容。不知道今天这几条消息有没有给您带来一些新的启发?感谢您的收听,我是${hostName},祝您拥有充实高效的一天,我们下期节目不见不散!`
444
+ ? scriptPlan
445
+ ? `${scriptPlan.outro}感谢您的收听,我是${hostName},祝您拥有充实高效的一天,我们下期节目不见不散!`
446
+ : `好了,以上就是今天AI科技漫谈的全部内容。不知道今天这几条消息有没有给您带来一些新的启发?感谢您的收听,我是${hostName},祝您拥有充实高效的一天,我们下期节目不见不散!`
426
447
  : `That wraps up today's AI Daily Briefing. I hope today's highlights gave you some useful takeaways. Thank you for listening. This is ${hostName}, wishing you a productive and wonderful day. Until next time!`;
427
448
 
428
449
  segments.push({
@@ -0,0 +1,142 @@
1
+ import type { Doc } from '@gobing-ai/kk-core';
2
+ import { z } from 'zod';
3
+
4
+ /**
5
+ * LLM-authored spoken plan for one daily episode (F613). The plan owns words only; the builder
6
+ * keeps segment order, pacing tokens, fact chunking, and validation
7
+ * (`docs/design/daily-voice-script-plan.md` §3). `version` is pinned so a future shape change
8
+ * fails loud as `schema` instead of mis-substituting.
9
+ */
10
+ export interface ScriptPlan {
11
+ version: 1;
12
+ intro: string;
13
+ outro: string;
14
+ items: { id: string; lead: string; insight: string }[];
15
+ }
16
+
17
+ /**
18
+ * Machine-readable part of the style sidecar `plugins/kk/scripts/daily-voice-style.zh.md` — the
19
+ * same fenced `json` block the script-write prompt reads, so prompt and validator share one source.
20
+ */
21
+ export interface StyleContract {
22
+ caps: { intro: number; outro: number; lead: number; insight: number };
23
+ banned: string[];
24
+ }
25
+
26
+ /** One plan rejection: which item (`episode` for plan-level fields) broke which rule. */
27
+ export interface ScriptPlanError {
28
+ itemId: string;
29
+ rule: string;
30
+ }
31
+
32
+ export type ScriptPlanValidation = { ok: true; plan: ScriptPlan } | { ok: false; errors: ScriptPlanError[] };
33
+
34
+ /**
35
+ * Empty spoken text is a shape error (`schema`), not a cap/banned case: an empty intro or insight
36
+ * would normalize to a bare 「。」 segment, which no valid episode contains.
37
+ */
38
+ const ScriptPlanItemSchema = z.object({
39
+ id: z.string().min(1),
40
+ lead: z.string().min(1),
41
+ insight: z.string().min(1),
42
+ });
43
+
44
+ const ScriptPlanSchema = z.object({
45
+ version: z.literal(1),
46
+ intro: z.string().min(1),
47
+ outro: z.string().min(1),
48
+ items: z.array(ScriptPlanItemSchema),
49
+ });
50
+
51
+ const StyleContractSchema = z.object({
52
+ caps: z.object({
53
+ intro: z.number().int().positive(),
54
+ outro: z.number().int().positive(),
55
+ lead: z.number().int().positive(),
56
+ insight: z.number().int().positive(),
57
+ }),
58
+ banned: z.array(z.string().min(1)),
59
+ });
60
+
61
+ const STYLE_JSON_FENCE_RE = /```json\s*([\s\S]*?)```/;
62
+
63
+ /**
64
+ * Parse the style contract out of the sidecar markdown: the FIRST fenced `json` block. A missing
65
+ * or invalid block throws — the caller turns that into the `no-style` fallback rule (a plan must
66
+ * never run without its contract).
67
+ */
68
+ export function parseStyleContract(markdown: string): StyleContract {
69
+ const match = markdown.match(STYLE_JSON_FENCE_RE);
70
+ if (!match?.[1]) {
71
+ throw new Error('style contract has no ```json fenced block');
72
+ }
73
+ let parsed: unknown;
74
+ try {
75
+ parsed = JSON.parse(match[1].trim());
76
+ } catch (err: unknown) {
77
+ throw new Error(
78
+ `style contract json block is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
79
+ );
80
+ }
81
+ return StyleContractSchema.parse(parsed);
82
+ }
83
+
84
+ /**
85
+ * Validate a plan against the docs and the style contract. Rules (task 0161 R2), accumulated —
86
+ * one warn line names every violation, so a rejected plan is diagnosable in one read:
87
+ *
88
+ * - `schema`: wrong shape or version (`itemId: episode`)
89
+ * - `unknown-id`: a plan item id that is not a doc
90
+ * - `missing-item`: a doc with no plan item
91
+ * - `cap:<field>`: a field over its cap (`intro`/`outro` report `itemId: episode`)
92
+ * - `banned:<phrase>`: a field containing a banned phrase
93
+ * - `duplicate-insight`: the same insight text on two items in one episode
94
+ *
95
+ * All-or-nothing by contract (Q&A): a partially valid plan is never partially applied.
96
+ */
97
+ export function validateScriptPlan(plan: unknown, docs: Doc[], style: StyleContract): ScriptPlanValidation {
98
+ const parsed = ScriptPlanSchema.safeParse(plan);
99
+ if (!parsed.success) {
100
+ return { ok: false, errors: [{ itemId: 'episode', rule: 'schema' }] };
101
+ }
102
+ const valid = parsed.data;
103
+ const errors: ScriptPlanError[] = [];
104
+ const docIds = new Set(docs.map((doc) => doc.id));
105
+
106
+ for (const item of valid.items) {
107
+ if (!docIds.has(item.id)) errors.push({ itemId: item.id, rule: 'unknown-id' });
108
+ }
109
+ const plannedIds = new Set(valid.items.map((item) => item.id));
110
+ for (const doc of docs) {
111
+ if (!plannedIds.has(doc.id)) errors.push({ itemId: doc.id, rule: 'missing-item' });
112
+ }
113
+
114
+ if (valid.intro.length > style.caps.intro) errors.push({ itemId: 'episode', rule: 'cap:intro' });
115
+ if (valid.outro.length > style.caps.outro) errors.push({ itemId: 'episode', rule: 'cap:outro' });
116
+ for (const item of valid.items) {
117
+ if (item.lead.length > style.caps.lead) errors.push({ itemId: item.id, rule: 'cap:lead' });
118
+ if (item.insight.length > style.caps.insight) errors.push({ itemId: item.id, rule: 'cap:insight' });
119
+ }
120
+
121
+ const fields: { itemId: string; text: string }[] = [
122
+ { itemId: 'episode', text: valid.intro },
123
+ { itemId: 'episode', text: valid.outro },
124
+ ...valid.items.flatMap((item) => [
125
+ { itemId: item.id, text: item.lead },
126
+ { itemId: item.id, text: item.insight },
127
+ ]),
128
+ ];
129
+ for (const field of fields) {
130
+ for (const phrase of style.banned) {
131
+ if (field.text.includes(phrase)) errors.push({ itemId: field.itemId, rule: `banned:${phrase}` });
132
+ }
133
+ }
134
+
135
+ const seenInsights = new Set<string>();
136
+ for (const item of valid.items) {
137
+ if (seenInsights.has(item.insight)) errors.push({ itemId: item.id, rule: 'duplicate-insight' });
138
+ else seenInsights.add(item.insight);
139
+ }
140
+
141
+ return errors.length > 0 ? { ok: false, errors } : { ok: true, plan: valid };
142
+ }
@@ -14642,6 +14642,72 @@ var init_zod = __esm(() => {
14642
14642
  init_external();
14643
14643
  });
14644
14644
 
14645
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/errors.js
14646
+ var ErrorCode;
14647
+ var init_errors3 = __esm(() => {
14648
+ ErrorCode = {
14649
+ NotFound: "NOT_FOUND",
14650
+ Validation: "VALIDATION",
14651
+ Conflict: "CONFLICT",
14652
+ Internal: "INTERNAL"
14653
+ };
14654
+ });
14655
+
14656
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/api-response.js
14657
+ var API_ERROR_CODES, ERROR_CODE_TO_HTTP, CLIENT_SAFE_CODES;
14658
+ var init_api_response = __esm(() => {
14659
+ init_errors3();
14660
+ API_ERROR_CODES = {
14661
+ SUCCESS: 0,
14662
+ NOT_FOUND: 404,
14663
+ VALIDATION_ERROR: 422,
14664
+ BAD_REQUEST: 400,
14665
+ UNAUTHORIZED: 401,
14666
+ FORBIDDEN: 403,
14667
+ CONFLICT: 409,
14668
+ INTERNAL_ERROR: 500
14669
+ };
14670
+ ERROR_CODE_TO_HTTP = {
14671
+ [ErrorCode.NotFound]: API_ERROR_CODES.NOT_FOUND,
14672
+ [ErrorCode.Validation]: API_ERROR_CODES.VALIDATION_ERROR,
14673
+ [ErrorCode.Conflict]: API_ERROR_CODES.CONFLICT,
14674
+ [ErrorCode.Internal]: API_ERROR_CODES.INTERNAL_ERROR
14675
+ };
14676
+ CLIENT_SAFE_CODES = new Set([ErrorCode.NotFound, ErrorCode.Validation, ErrorCode.Conflict]);
14677
+ });
14678
+
14679
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/cursor.js
14680
+ var init_cursor = () => {};
14681
+
14682
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/event-severity.js
14683
+ var init_event_severity = () => {};
14684
+
14685
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/output.js
14686
+ function processStream(name) {
14687
+ const proc = globalThis.process;
14688
+ const stream = proc?.[name];
14689
+ if (stream === undefined) {
14690
+ throw new Error(`No ${name} target available: set one via setDefaultOutputTargets or pass an explicit target`);
14691
+ }
14692
+ return stream;
14693
+ }
14694
+ function writeLine(message, target) {
14695
+ target.write(`${message}
14696
+ `);
14697
+ }
14698
+ function echoError(message, target = defaultStderrTarget ?? processStream("stderr")) {
14699
+ writeLine(message, target);
14700
+ }
14701
+ var defaultStderrTarget;
14702
+
14703
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/index.js
14704
+ var init_dist = __esm(() => {
14705
+ init_api_response();
14706
+ init_cursor();
14707
+ init_errors3();
14708
+ init_event_severity();
14709
+ });
14710
+
14645
14711
  // ../../node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js
14646
14712
  var require_identity = __commonJS((exports) => {
14647
14713
  var ALIAS = Symbol.for("yaml.alias");
@@ -17546,15 +17612,15 @@ var require_timestamp = __commonJS((exports) => {
17546
17612
  throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd");
17547
17613
  const [, year, month, day, hour, minute, second] = match.map(Number);
17548
17614
  const millisec = match[7] ? Number((match[7] + "00").substr(1, 3)) : 0;
17549
- let date5 = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
17615
+ let date6 = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
17550
17616
  const tz = match[8];
17551
17617
  if (tz && tz !== "Z") {
17552
17618
  let d = parseSexagesimal(tz, false);
17553
17619
  if (Math.abs(d) < 30)
17554
17620
  d *= 60;
17555
- date5 -= 60000 * d;
17621
+ date6 -= 60000 * d;
17556
17622
  }
17557
- return new Date(date5);
17623
+ return new Date(date6);
17558
17624
  },
17559
17625
  stringify: ({ value }) => value?.toISOString().replace(/(T00:00:00)?\.000Z$/, "") ?? ""
17560
17626
  };
@@ -19414,7 +19480,7 @@ var require_composer = __commonJS((exports) => {
19414
19480
  var node_process = __require("process");
19415
19481
  var directives = require_directives();
19416
19482
  var Document = require_Document();
19417
- var errors3 = require_errors();
19483
+ var errors4 = require_errors();
19418
19484
  var identity = require_identity();
19419
19485
  var composeDoc = require_compose_doc();
19420
19486
  var resolveEnd = require_resolve_end();
@@ -19465,9 +19531,9 @@ var require_composer = __commonJS((exports) => {
19465
19531
  this.onError = (source, code, message, warning) => {
19466
19532
  const pos = getErrorPos(source);
19467
19533
  if (warning)
19468
- this.warnings.push(new errors3.YAMLWarning(pos, code, message));
19534
+ this.warnings.push(new errors4.YAMLWarning(pos, code, message));
19469
19535
  else
19470
- this.errors.push(new errors3.YAMLParseError(pos, code, message));
19536
+ this.errors.push(new errors4.YAMLParseError(pos, code, message));
19471
19537
  };
19472
19538
  this.directives = new directives.Directives({ version: options.version || "1.2" });
19473
19539
  this.options = options;
@@ -19553,7 +19619,7 @@ ${cb}` : comment;
19553
19619
  break;
19554
19620
  case "error": {
19555
19621
  const msg = token.source ? `${token.message}: ${JSON.stringify(token.source)}` : token.message;
19556
- const error51 = new errors3.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg);
19622
+ const error51 = new errors4.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg);
19557
19623
  if (this.atDirectives || !this.doc)
19558
19624
  this.errors.push(error51);
19559
19625
  else
@@ -19563,7 +19629,7 @@ ${cb}` : comment;
19563
19629
  case "doc-end": {
19564
19630
  if (!this.doc) {
19565
19631
  const msg = "Unexpected doc-end without preceding document";
19566
- this.errors.push(new errors3.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg));
19632
+ this.errors.push(new errors4.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg));
19567
19633
  break;
19568
19634
  }
19569
19635
  this.doc.directives.docEnd = true;
@@ -19578,7 +19644,7 @@ ${end.comment}` : end.comment;
19578
19644
  break;
19579
19645
  }
19580
19646
  default:
19581
- this.errors.push(new errors3.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", `Unsupported token ${token.type}`));
19647
+ this.errors.push(new errors4.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", `Unsupported token ${token.type}`));
19582
19648
  }
19583
19649
  }
19584
19650
  *end(forceDoc = false, endOffset = -1) {
@@ -19604,7 +19670,7 @@ ${end.comment}` : end.comment;
19604
19670
  var require_cst_scalar = __commonJS((exports) => {
19605
19671
  var resolveBlockScalar = require_resolve_block_scalar();
19606
19672
  var resolveFlowScalar = require_resolve_flow_scalar();
19607
- var errors3 = require_errors();
19673
+ var errors4 = require_errors();
19608
19674
  var stringifyString = require_stringifyString();
19609
19675
  function resolveAsScalar(token, strict = true, onError) {
19610
19676
  if (token) {
@@ -19613,7 +19679,7 @@ var require_cst_scalar = __commonJS((exports) => {
19613
19679
  if (onError)
19614
19680
  onError(offset, code, message);
19615
19681
  else
19616
- throw new errors3.YAMLParseError([offset, offset + 1], code, message);
19682
+ throw new errors4.YAMLParseError([offset, offset + 1], code, message);
19617
19683
  };
19618
19684
  switch (token.type) {
19619
19685
  case "scalar":
@@ -21493,7 +21559,7 @@ var require_parser = __commonJS((exports) => {
21493
21559
  var require_public_api = __commonJS((exports) => {
21494
21560
  var composer = require_composer();
21495
21561
  var Document = require_Document();
21496
- var errors3 = require_errors();
21562
+ var errors4 = require_errors();
21497
21563
  var log = require_log();
21498
21564
  var identity = require_identity();
21499
21565
  var lineCounter = require_line_counter();
@@ -21510,8 +21576,8 @@ var require_public_api = __commonJS((exports) => {
21510
21576
  const docs = Array.from(composer$1.compose(parser$1.parse(source)));
21511
21577
  if (prettyErrors && lineCounter2)
21512
21578
  for (const doc2 of docs) {
21513
- doc2.errors.forEach(errors3.prettifyError(source, lineCounter2));
21514
- doc2.warnings.forEach(errors3.prettifyError(source, lineCounter2));
21579
+ doc2.errors.forEach(errors4.prettifyError(source, lineCounter2));
21580
+ doc2.warnings.forEach(errors4.prettifyError(source, lineCounter2));
21515
21581
  }
21516
21582
  if (docs.length > 0)
21517
21583
  return docs;
@@ -21526,13 +21592,13 @@ var require_public_api = __commonJS((exports) => {
21526
21592
  if (!doc2)
21527
21593
  doc2 = _doc;
21528
21594
  else if (doc2.options.logLevel !== "silent") {
21529
- doc2.errors.push(new errors3.YAMLParseError(_doc.range.slice(0, 2), "MULTIPLE_DOCS", "Source contains multiple documents; please use YAML.parseAllDocuments()"));
21595
+ doc2.errors.push(new errors4.YAMLParseError(_doc.range.slice(0, 2), "MULTIPLE_DOCS", "Source contains multiple documents; please use YAML.parseAllDocuments()"));
21530
21596
  break;
21531
21597
  }
21532
21598
  }
21533
21599
  if (prettyErrors && lineCounter2) {
21534
- doc2.errors.forEach(errors3.prettifyError(source, lineCounter2));
21535
- doc2.warnings.forEach(errors3.prettifyError(source, lineCounter2));
21600
+ doc2.errors.forEach(errors4.prettifyError(source, lineCounter2));
21601
+ doc2.warnings.forEach(errors4.prettifyError(source, lineCounter2));
21536
21602
  }
21537
21603
  return doc2;
21538
21604
  }
@@ -21584,12 +21650,12 @@ var require_public_api = __commonJS((exports) => {
21584
21650
  });
21585
21651
 
21586
21652
  // ../../node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/index.js
21587
- var composer, Document, Schema, errors3, Alias, identity, Pair, Scalar, YAMLMap, YAMLSeq, cst, lexer, lineCounter, parser, publicApi, visit, $Composer, $Document, $Schema, $YAMLError, $YAMLParseError, $YAMLWarning, $Alias, $isAlias, $isCollection, $isDocument, $isMap, $isNode, $isPair, $isScalar, $isSeq, $Pair, $Scalar, $YAMLMap, $YAMLSeq, $Lexer, $LineCounter, $Parser, $parse, $parseAllDocuments, $parseDocument, $stringify, $visit, $visitAsync;
21588
- var init_dist = __esm(() => {
21653
+ var composer, Document, Schema, errors4, Alias, identity, Pair, Scalar, YAMLMap, YAMLSeq, cst, lexer, lineCounter, parser, publicApi, visit, $Composer, $Document, $Schema, $YAMLError, $YAMLParseError, $YAMLWarning, $Alias, $isAlias, $isCollection, $isDocument, $isMap, $isNode, $isPair, $isScalar, $isSeq, $Pair, $Scalar, $YAMLMap, $YAMLSeq, $Lexer, $LineCounter, $Parser, $parse, $parseAllDocuments, $parseDocument, $stringify, $visit, $visitAsync;
21654
+ var init_dist2 = __esm(() => {
21589
21655
  composer = require_composer();
21590
21656
  Document = require_Document();
21591
21657
  Schema = require_Schema();
21592
- errors3 = require_errors();
21658
+ errors4 = require_errors();
21593
21659
  Alias = require_Alias();
21594
21660
  identity = require_identity();
21595
21661
  Pair = require_Pair();
@@ -21605,9 +21671,9 @@ var init_dist = __esm(() => {
21605
21671
  $Composer = composer.Composer;
21606
21672
  $Document = Document.Document;
21607
21673
  $Schema = Schema.Schema;
21608
- $YAMLError = errors3.YAMLError;
21609
- $YAMLParseError = errors3.YAMLParseError;
21610
- $YAMLWarning = errors3.YAMLWarning;
21674
+ $YAMLError = errors4.YAMLError;
21675
+ $YAMLParseError = errors4.YAMLParseError;
21676
+ $YAMLWarning = errors4.YAMLWarning;
21611
21677
  $Alias = Alias.Alias;
21612
21678
  $isAlias = identity.isAlias;
21613
21679
  $isCollection = identity.isCollection;
@@ -21632,10 +21698,10 @@ var init_dist = __esm(() => {
21632
21698
  $visitAsync = visit.visitAsync;
21633
21699
  });
21634
21700
 
21635
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.39/node_modules/@gobing-ai/ts-runtime/dist/config.js
21701
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/config.js
21636
21702
  var configSchema;
21637
21703
  var init_config = __esm(() => {
21638
- init_dist();
21704
+ init_dist2();
21639
21705
  init_zod();
21640
21706
  configSchema = exports_external.object({
21641
21707
  app: exports_external.object({
@@ -21656,7 +21722,7 @@ var init_config = __esm(() => {
21656
21722
  });
21657
21723
  });
21658
21724
 
21659
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.39/node_modules/@gobing-ai/ts-runtime/dist/file-system-node.js
21725
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/file-system-node.js
21660
21726
  import { appendFileSync, cpSync, createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
21661
21727
  import { dirname, resolve as resolvePath } from "path";
21662
21728
  function createNodeFileSystem(root, paths) {
@@ -21834,14 +21900,14 @@ var PluginManifestSchema = exports_external.looseObject({
21834
21900
  description: exports_external.string().optional(),
21835
21901
  operations: exports_external.record(exports_external.string(), PluginOperationSchema).optional()
21836
21902
  });
21837
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.39/node_modules/@gobing-ai/ts-runtime/dist/index.js
21903
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/index.js
21838
21904
  init_file_system_node();
21839
21905
  init_config();
21840
21906
 
21841
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.39/node_modules/@gobing-ai/ts-runtime/dist/fs.js
21907
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/fs.js
21842
21908
  init_file_system_node();
21843
21909
 
21844
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.39/node_modules/@gobing-ai/ts-runtime/dist/path.js
21910
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/path.js
21845
21911
  function normalizeSeparators(path) {
21846
21912
  return path.replaceAll("\\", "/");
21847
21913
  }
@@ -21875,7 +21941,7 @@ function dirnamePath(path) {
21875
21941
  return normalized.slice(0, index);
21876
21942
  }
21877
21943
 
21878
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.39/node_modules/@gobing-ai/ts-runtime/dist/fs.js
21944
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/fs.js
21879
21945
  async function ensureDirForFile(path, fs = createNodeFileSystem()) {
21880
21946
  await fs.ensureDir(dirnamePath(path));
21881
21947
  }
@@ -21903,27 +21969,10 @@ function getProcessPid() {
21903
21969
  function uniqueToken() {
21904
21970
  return `${Date.now()}.${crypto.randomUUID()}`;
21905
21971
  }
21906
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.67/node_modules/@gobing-ai/ts-utils/dist/output.js
21907
- var defaultStderrTarget;
21908
- function processStream(name) {
21909
- const proc = globalThis.process;
21910
- const stream = proc?.[name];
21911
- if (stream === undefined) {
21912
- throw new Error(`No ${name} target available: set one via setDefaultOutputTargets or pass an explicit target`);
21913
- }
21914
- return stream;
21915
- }
21916
- function writeLine(message, target) {
21917
- target.write(`${message}
21918
- `);
21919
- }
21920
- function echoError(message, target = defaultStderrTarget ?? processStream("stderr")) {
21921
- writeLine(message, target);
21922
- }
21923
-
21924
21972
  // ../../packages/kk-core/src/render-md.ts
21925
21973
  if (false) {}
21926
21974
  // ../../plugins/generations/episode-plan-gen/src/index.ts
21975
+ init_dist();
21927
21976
  var SOURCE_BASE = {
21928
21977
  aihot: 30,
21929
21978
  rss: 12,
@@ -22046,9 +22095,9 @@ function docTimestamp(doc2) {
22046
22095
  return t;
22047
22096
  }
22048
22097
  }
22049
- const date5 = meta3.date;
22050
- if (typeof date5 === "string" && /^\d{4}-\d{2}-\d{2}/.test(date5.trim())) {
22051
- return Date.parse(date5.trim().slice(0, 10));
22098
+ const date6 = meta3.date;
22099
+ if (typeof date6 === "string" && /^\d{4}-\d{2}-\d{2}/.test(date6.trim())) {
22100
+ return Date.parse(date6.trim().slice(0, 10));
22052
22101
  }
22053
22102
  return null;
22054
22103
  }
@@ -22318,7 +22367,7 @@ function sourceOf(doc2) {
22318
22367
  const meta3 = asRecord(doc2.metadata);
22319
22368
  return typeof meta3.source === "string" ? meta3.source : "unknown";
22320
22369
  }
22321
- function renderPlanMarkdown(docs, date5) {
22370
+ function renderPlanMarkdown(docs, date6) {
22322
22371
  const items = docs.map((doc2, i) => {
22323
22372
  const meta3 = asRecord(doc2.metadata);
22324
22373
  const parts = [
@@ -22332,25 +22381,25 @@ function renderPlanMarkdown(docs, date5) {
22332
22381
  }).join(`
22333
22382
 
22334
22383
  `);
22335
- return `# \u6BCF\u65E5 AI \u64AD\u5BA2\u9009\u7A3F\u8BA1\u5212 \u2014 ${date5}
22384
+ return `# \u6BCF\u65E5 AI \u64AD\u5BA2\u9009\u7A3F\u8BA1\u5212 \u2014 ${date6}
22336
22385
 
22337
22386
  \u672C\u671F\u5171 ${docs.length} \u6761\uFF08\u8BC4\u5206/\u53BB\u91CD/\u6807\u8BB0\u540E\u7CBE\u9009\uFF09\u3002
22338
22387
 
22339
22388
  ${items}
22340
22389
  `;
22341
22390
  }
22342
- function docsToContent(docs, date5, audit) {
22343
- const planned = planEpisode(docs, { maxItems: maxItemsFromEnv(), date: date5, audit });
22344
- return planContent(planned, date5);
22391
+ function docsToContent(docs, date6, audit) {
22392
+ const planned = planEpisode(docs, { maxItems: maxItemsFromEnv(), date: date6, audit });
22393
+ return planContent(planned, date6);
22345
22394
  }
22346
- function planContent(docs, date5) {
22395
+ function planContent(docs, date6) {
22347
22396
  return ContentSchema.parse({
22348
- title: `\u6BCF\u65E5 AI \u64AD\u5BA2\u9009\u7A3F\u8BA1\u5212 \u2014 ${date5}`,
22349
- body: renderPlanMarkdown(docs, date5),
22397
+ title: `\u6BCF\u65E5 AI \u64AD\u5BA2\u9009\u7A3F\u8BA1\u5212 \u2014 ${date6}`,
22398
+ body: renderPlanMarkdown(docs, date6),
22350
22399
  format: "markdown",
22351
22400
  metadata: {
22352
22401
  generator: "kk:episode-plan",
22353
- date: date5,
22402
+ date: date6,
22354
22403
  doc_count: docs.length,
22355
22404
  docs
22356
22405
  }
@@ -22398,13 +22447,13 @@ function parseFilterCategories(raw) {
22398
22447
  }
22399
22448
  return entries;
22400
22449
  }
22401
- function filterConfigFromEnv(env = process.env) {
22450
+ function filterConfigFromEnv(env2 = process.env) {
22402
22451
  return {
22403
- categories: parseFilterCategories(env.QC_CATEGORIES),
22404
- minQuality: parseFilterThreshold(env.QC_MIN_QUALITY, "QC_MIN_QUALITY", DEFAULT_FILTER_CONFIG.minQuality),
22405
- minImportance: parseFilterThreshold(env.QC_MIN_IMPORTANCE, "QC_MIN_IMPORTANCE", DEFAULT_FILTER_CONFIG.minImportance),
22406
- minUrgency: parseFilterThreshold(env.QC_MIN_URGENCY, "QC_MIN_URGENCY", DEFAULT_FILTER_CONFIG.minUrgency),
22407
- minImpact: parseFilterThreshold(env.QC_MIN_IMPACT, "QC_MIN_IMPACT", DEFAULT_FILTER_CONFIG.minImpact)
22452
+ categories: parseFilterCategories(env2.QC_CATEGORIES),
22453
+ minQuality: parseFilterThreshold(env2.QC_MIN_QUALITY, "QC_MIN_QUALITY", DEFAULT_FILTER_CONFIG.minQuality),
22454
+ minImportance: parseFilterThreshold(env2.QC_MIN_IMPORTANCE, "QC_MIN_IMPORTANCE", DEFAULT_FILTER_CONFIG.minImportance),
22455
+ minUrgency: parseFilterThreshold(env2.QC_MIN_URGENCY, "QC_MIN_URGENCY", DEFAULT_FILTER_CONFIG.minUrgency),
22456
+ minImpact: parseFilterThreshold(env2.QC_MIN_IMPACT, "QC_MIN_IMPACT", DEFAULT_FILTER_CONFIG.minImpact)
22408
22457
  };
22409
22458
  }
22410
22459
  function requireAnnotation(doc2) {
@@ -65,6 +65,7 @@ rm -rf "$T"
65
65
  Stdout is the `voice_…` id. A non-zero exit prints the HTTP status and API message and does not
66
66
  print the key. Note on HTTP 500 / Voice mismatch errors:
67
67
  - Upstream `POST /v1beta/voices` can return HTTP 500 when server-side validation serializes an invalid argument or consent mismatch.
68
+ - 2026-09-24: the zh pair that created the original voice (28 s zh source + 7 s zh consent) started returning HTTP 500 "Error translating server response to JSON" on both accounts. The en-pair path (below) succeeded first try — prefer it for renewals.
68
69
  - A Chinese source paired with an English consent fails with `Voice mismatch detected`.
69
70
  - Using a clean 10–30s English recording containing the verbatim consent statement as both `--source` and `--consent` succeeds reliably via the API, creating a stored voice that synthesizes both English and Chinese content.
70
71
  - Google AI Studio's interactive voice replication UI is also available as a manual fallback.
@@ -94,6 +95,29 @@ when you explicitly want a lite-specific id.
94
95
  Run the daily workflow with `--vars '{"vdriver":"gemini"}'`. The default `vdriver` remains
95
96
  `voicebox`.
96
97
 
98
+ ### Stable persona across segments (`GEMINI_TTS_STYLE`)
99
+
100
+ Gemini re-interprets its short-clip custom voice per style annotation. A script that varies
101
+ `emotion` per segment (intro warm, news clear, commentary thoughtful, …) therefore drifts the
102
+ persona across segment boundaries — audible as voice-identity loss and mood swings (2026-09-24
103
+ episode review). Set one style for the whole episode to pin the persona:
104
+
105
+ ```yaml
106
+ GEMINI_TTS_STYLE: calm professional podcast host, steady pace, natural conversational tone
107
+ ```
108
+
109
+ When set it replaces every segment's own instruct; per-segment instructions still apply when
110
+ unset.
111
+
112
+ ### Daily quota (Tier 1: ~100 requests/day)
113
+
114
+ `gemini-3.8-flash-tts` on a Tier 1 key allows ~100 requests/day (UTC reset — quota 429s quote
115
+ a retry-in horizon into the next UTC midnight). A full episode (~31 segments + QC regens ≈
116
+ 35–45 calls) essentially spends the day's budget, and the daily workflow shares it with any
117
+ tuning run. The client caps transient 429 backoff at 120s and fails loudly when `retry-after`
118
+ exceeds that (it used to sleep out the ~19h horizon, which looked like a wedged run —
119
+ 2026-09-24). For style/voice tuning, synthesize 2–3 segments instead of the full episode.
120
+
97
121
  ## Renewal
98
122
 
99
123
  A stored voice lasts 1 year (200 stored voices per project, shared with prompted voices). Before