@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.
Files changed (77) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +74 -0
  3. package/dist/index.js +46 -100
  4. package/package.json +19 -1
  5. package/plugins/generations/content-gen/dist/index.js +42 -94
  6. package/plugins/generations/core-facts-gen/dist/index.js +51 -100
  7. package/plugins/generations/daily-article-gen/dist/index.js +98 -100
  8. package/plugins/generations/daily-article-gen/src/index.ts +80 -6
  9. package/plugins/generations/dailynews-gen/dist/index.js +144 -130
  10. package/plugins/generations/dailynews-gen/src/script-builder.ts +141 -33
  11. package/plugins/generations/episode-plan-gen/dist/index.js +124 -115
  12. package/plugins/generations/episode-plan-gen/src/index.ts +111 -24
  13. package/plugins/generations/image-gen/dist/index.js +63 -115
  14. package/plugins/generations/news-report-gen/dist/index.js +164 -100
  15. package/plugins/generations/news-report-gen/src/index.ts +199 -7
  16. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/pipeline.py +17 -1
  17. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/qc.py +4 -22
  18. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/voicescript.py +8 -0
  19. package/plugins/generations/voice-gen/dist/index.js +136 -117
  20. package/plugins/generations/voice-gen/src/concat.ts +34 -0
  21. package/plugins/generations/voice-gen/src/index.ts +6 -4
  22. package/plugins/generations/voice-gen/src/voicescript.ts +61 -0
  23. package/plugins/ingestions/aihot-ingest/dist/index.js +47 -97
  24. package/plugins/ingestions/horizon-ingest/dist/index.js +47 -96
  25. package/plugins/ingestions/karakeep-local/dist/index.js +217 -266
  26. package/plugins/ingestions/last30days-ingest/dist/index.js +42 -91
  27. package/plugins/ingestions/web-search/dist/index.js +68 -117
  28. package/plugins/kk/commands/workflow-run.md +25 -2
  29. package/plugins/kk/plugin.json +1 -1
  30. package/plugins/kk/scripts/kk-workflow-stages.ts +522 -163
  31. package/plugins/kk/scripts/validate-voicescript.ts +55 -0
  32. package/plugins/kk/skills/publish/SKILL.md +80 -0
  33. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +125 -120
  34. package/plugins/kk/workflows/leftover-publish-fanout-example.yaml +53 -0
  35. package/plugins/publishings/emdash-pub/dist/index.js +42 -91
  36. package/plugins/publishings/infoq-pub/dist/index.js +21994 -0
  37. package/plugins/publishings/infoq-pub/package.json +17 -0
  38. package/plugins/publishings/infoq-pub/plugin.json +7 -0
  39. package/plugins/publishings/infoq-pub/src/index.ts +123 -0
  40. package/plugins/publishings/infoq-pub/tsconfig.json +4 -0
  41. package/plugins/publishings/juejin-pub/dist/index.js +21994 -0
  42. package/plugins/publishings/juejin-pub/package.json +17 -0
  43. package/plugins/publishings/juejin-pub/plugin.json +7 -0
  44. package/plugins/publishings/juejin-pub/src/index.ts +123 -0
  45. package/plugins/publishings/juejin-pub/tsconfig.json +4 -0
  46. package/plugins/publishings/medium-pub/dist/index.js +22041 -0
  47. package/plugins/publishings/medium-pub/package.json +17 -0
  48. package/plugins/publishings/medium-pub/plugin.json +7 -0
  49. package/plugins/publishings/medium-pub/src/index.ts +215 -0
  50. package/plugins/publishings/medium-pub/tsconfig.json +4 -0
  51. package/plugins/publishings/podcast-pub/dist/index.js +183 -123
  52. package/plugins/publishings/podcast-pub/src/index.ts +47 -3
  53. package/plugins/publishings/podcast-pub/src/show-notes.ts +153 -34
  54. package/plugins/publishings/qiita-pub/dist/index.js +42 -91
  55. package/plugins/publishings/substack-pub/dist/index.js +21994 -0
  56. package/plugins/publishings/substack-pub/package.json +17 -0
  57. package/plugins/publishings/substack-pub/plugin.json +7 -0
  58. package/plugins/publishings/substack-pub/src/index.ts +123 -0
  59. package/plugins/publishings/substack-pub/tsconfig.json +4 -0
  60. package/plugins/publishings/surfdash-pub/dist/index.js +172 -103
  61. package/plugins/publishings/surfdash-pub/src/index.ts +32 -2
  62. package/plugins/publishings/wechatmp-pub/dist/index.js +22119 -0
  63. package/plugins/publishings/wechatmp-pub/package.json +17 -0
  64. package/plugins/publishings/wechatmp-pub/plugin.json +7 -0
  65. package/plugins/publishings/wechatmp-pub/src/index.ts +304 -0
  66. package/plugins/publishings/wechatmp-pub/tsconfig.json +4 -0
  67. package/plugins/publishings/x-pub/dist/index.js +22110 -0
  68. package/plugins/publishings/x-pub/package.json +17 -0
  69. package/plugins/publishings/x-pub/plugin.json +7 -0
  70. package/plugins/publishings/x-pub/src/index.ts +222 -0
  71. package/plugins/publishings/x-pub/tsconfig.json +4 -0
  72. package/plugins/publishings/xhs-pub/dist/index.js +21994 -0
  73. package/plugins/publishings/xhs-pub/package.json +17 -0
  74. package/plugins/publishings/xhs-pub/plugin.json +7 -0
  75. package/plugins/publishings/xhs-pub/src/index.ts +123 -0
  76. package/plugins/publishings/xhs-pub/tsconfig.json +4 -0
  77. package/plugins/publishings/zenn-pub/dist/index.js +42 -91
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@gobing-ai/substack-pub",
3
+ "type": "module",
4
+ "private": true,
5
+ "scripts": {
6
+ "typecheck": "tsc --noEmit"
7
+ },
8
+ "dependencies": {
9
+ "@gobing-ai/kk-core": "workspace:*",
10
+ "@gobing-ai/publish-harness": "workspace:*",
11
+ "@gobing-ai/ts-runtime": "catalog:",
12
+ "@gobing-ai/ts-utils": "catalog:"
13
+ },
14
+ "devDependencies": {
15
+ "@types/bun": "1.3.14"
16
+ }
17
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "substack-pub",
3
+ "kind": "publish",
4
+ "entry": "./dist/index.js",
5
+ "version": "1.0.0",
6
+ "description": "Publish plugin for Substack via browser/agent transport"
7
+ }
@@ -0,0 +1,123 @@
1
+ import { dirname } from 'node:path';
2
+ import { parseArgs } from 'node:util';
3
+ import { type Content, ContentSchema, type Result, ResultSchema } from '@gobing-ai/kk-core';
4
+ import {
5
+ type BrowserDriver,
6
+ FakeBrowserTransport,
7
+ type PublishTransport,
8
+ type PublishTransportPayload,
9
+ resolveBrowserProfileDir,
10
+ } from '@gobing-ai/publish-harness';
11
+ import { createNodeFileSystem } from '@gobing-ai/ts-runtime';
12
+ import { echoError } from '@gobing-ai/ts-utils';
13
+
14
+ const CHANNEL = 'substack';
15
+ const TARGET = 'substack';
16
+
17
+ function fail(error: string): Result {
18
+ return ResultSchema.parse({ ok: false, target: TARGET, error });
19
+ }
20
+
21
+ function draftId(content: Content): string {
22
+ const title = content.title ?? 'untitled';
23
+ return `${CHANNEL}-draft-${title.length}`;
24
+ }
25
+
26
+ function draftResult(content: Content, driver: BrowserDriver, profileDir: string): Result {
27
+ return ResultSchema.parse({
28
+ ok: true,
29
+ target: TARGET,
30
+ id: draftId(content),
31
+ message: `${TARGET} draft prepared (unpublished)`,
32
+ metadata: { driver, publishStatus: 'draft', profileDir },
33
+ });
34
+ }
35
+
36
+ export class SubstackBrowserTransport implements PublishTransport {
37
+ public readonly kind = 'browser';
38
+ public readonly driver: BrowserDriver;
39
+ private readonly loginTimeoutMs: number;
40
+ private readonly sessionReady: boolean;
41
+
42
+ constructor(driver: BrowserDriver = 'agent', opts?: { loginTimeoutMs?: number; sessionReady?: boolean }) {
43
+ this.driver = driver;
44
+ this.loginTimeoutMs = opts?.loginTimeoutMs ?? 120_000;
45
+ this.sessionReady = opts?.sessionReady ?? false;
46
+ }
47
+
48
+ public async publish(payload: PublishTransportPayload): Promise<Result> {
49
+ const override =
50
+ typeof payload.options?.profileDir === 'string'
51
+ ? payload.options.profileDir
52
+ : (process.env.SUBSTACK_PROFILE_DIR ?? process.env.KK_BROWSER_PROFILE_DIR);
53
+ const profileDir = resolveBrowserProfileDir(CHANNEL, override);
54
+ if (!this.sessionReady) {
55
+ return fail(
56
+ `login timeout after ${this.loginTimeoutMs}ms: no authenticated ${CHANNEL} session in ${profileDir}`,
57
+ );
58
+ }
59
+ return draftResult(payload.content, this.driver, profileDir);
60
+ }
61
+ }
62
+
63
+ export function getPublishTransport(driver: BrowserDriver = 'agent'): PublishTransport {
64
+ if (process.env.KNOWLEDGE_KIT_PUBLISH_TRANSPORT === 'fake') {
65
+ return new FakeBrowserTransport(driver);
66
+ }
67
+ return new SubstackBrowserTransport(driver);
68
+ }
69
+
70
+ async function writePublishResult(outputPath: string, result: Result): Promise<void> {
71
+ const outDir = dirname(outputPath);
72
+ if (outDir && outDir !== '.') await createNodeFileSystem().ensureDir(outDir);
73
+ await createNodeFileSystem().writeFile(outputPath, JSON.stringify(result, null, 2));
74
+ }
75
+
76
+ export async function processPublishIO(
77
+ inputPath: string,
78
+ outputPath: string,
79
+ transport: PublishTransport = getPublishTransport(),
80
+ ): Promise<void> {
81
+ await createNodeFileSystem().deleteFile(outputPath);
82
+ let content: Content;
83
+ let options: Record<string, unknown> | undefined;
84
+ try {
85
+ const raw = JSON.parse(await createNodeFileSystem().readFile(inputPath)) as Record<string, unknown>;
86
+ content = ContentSchema.parse(raw);
87
+ if (raw.options !== undefined && typeof raw.options === 'object' && raw.options !== null) {
88
+ options = raw.options as Record<string, unknown>;
89
+ }
90
+ } catch (error: unknown) {
91
+ const message = `Invalid Content input: ${error instanceof Error ? error.message : String(error)}`;
92
+ await writePublishResult(outputPath, fail(message));
93
+ throw new Error(message);
94
+ }
95
+ const result = ResultSchema.parse(await transport.publish({ content, options }));
96
+ await writePublishResult(outputPath, result);
97
+ if (!result.ok) throw new Error(result.error ?? 'Publish failed');
98
+ }
99
+
100
+ export async function main(): Promise<number> {
101
+ let values: { in?: string; out?: string };
102
+ try {
103
+ ({ values } = parseArgs({ options: { in: { type: 'string' }, out: { type: 'string' } } }));
104
+ } catch (error: unknown) {
105
+ echoError(`Invalid arguments: ${error instanceof Error ? error.message : String(error)}`);
106
+ return 1;
107
+ }
108
+ if (!values.in || !values.out) {
109
+ echoError('Missing required arguments: --in and --out');
110
+ return 1;
111
+ }
112
+ try {
113
+ await processPublishIO(values.in, values.out);
114
+ return 0;
115
+ } catch (error: unknown) {
116
+ echoError(`substack-pub failed: ${error instanceof Error ? error.message : String(error)}`);
117
+ return 1;
118
+ }
119
+ }
120
+
121
+ if (import.meta.main) {
122
+ process.exit(await main());
123
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "../../../tooling/typescript/base.json",
3
+ "include": ["src", "tests"]
4
+ }
@@ -14665,72 +14665,6 @@ var init_zod = __esm(() => {
14665
14665
  init_external();
14666
14666
  });
14667
14667
 
14668
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.39/node_modules/@gobing-ai/ts-utils/dist/errors.js
14669
- var ErrorCode;
14670
- var init_errors3 = __esm(() => {
14671
- ErrorCode = {
14672
- NotFound: "NOT_FOUND",
14673
- Validation: "VALIDATION",
14674
- Conflict: "CONFLICT",
14675
- Internal: "INTERNAL"
14676
- };
14677
- });
14678
-
14679
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.39/node_modules/@gobing-ai/ts-utils/dist/api-response.js
14680
- var API_ERROR_CODES, ERROR_CODE_TO_HTTP, CLIENT_SAFE_CODES;
14681
- var init_api_response = __esm(() => {
14682
- init_errors3();
14683
- API_ERROR_CODES = {
14684
- SUCCESS: 0,
14685
- NOT_FOUND: 404,
14686
- VALIDATION_ERROR: 422,
14687
- BAD_REQUEST: 400,
14688
- UNAUTHORIZED: 401,
14689
- FORBIDDEN: 403,
14690
- CONFLICT: 409,
14691
- INTERNAL_ERROR: 500
14692
- };
14693
- ERROR_CODE_TO_HTTP = {
14694
- [ErrorCode.NotFound]: API_ERROR_CODES.NOT_FOUND,
14695
- [ErrorCode.Validation]: API_ERROR_CODES.VALIDATION_ERROR,
14696
- [ErrorCode.Conflict]: API_ERROR_CODES.CONFLICT,
14697
- [ErrorCode.Internal]: API_ERROR_CODES.INTERNAL_ERROR
14698
- };
14699
- CLIENT_SAFE_CODES = new Set([ErrorCode.NotFound, ErrorCode.Validation, ErrorCode.Conflict]);
14700
- });
14701
-
14702
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.39/node_modules/@gobing-ai/ts-utils/dist/cursor.js
14703
- var init_cursor = () => {};
14704
-
14705
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.39/node_modules/@gobing-ai/ts-utils/dist/event-severity.js
14706
- var init_event_severity = () => {};
14707
-
14708
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.39/node_modules/@gobing-ai/ts-utils/dist/output.js
14709
- function processStream(name) {
14710
- const proc = globalThis.process;
14711
- const stream = proc?.[name];
14712
- if (stream === undefined) {
14713
- throw new Error(`No ${name} target available: set one via setDefaultOutputTargets or pass an explicit target`);
14714
- }
14715
- return stream;
14716
- }
14717
- function writeLine(message, target) {
14718
- target.write(`${message}
14719
- `);
14720
- }
14721
- function echoError(message, target = defaultStderrTarget ?? processStream("stderr")) {
14722
- writeLine(message, target);
14723
- }
14724
- var defaultStderrTarget;
14725
-
14726
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.39/node_modules/@gobing-ai/ts-utils/dist/index.js
14727
- var init_dist = __esm(() => {
14728
- init_api_response();
14729
- init_cursor();
14730
- init_errors3();
14731
- init_event_severity();
14732
- });
14733
-
14734
14668
  // ../../node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js
14735
14669
  var require_identity = __commonJS((exports) => {
14736
14670
  var ALIAS = Symbol.for("yaml.alias");
@@ -17635,15 +17569,15 @@ var require_timestamp = __commonJS((exports) => {
17635
17569
  throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd");
17636
17570
  const [, year, month, day, hour, minute, second] = match.map(Number);
17637
17571
  const millisec = match[7] ? Number((match[7] + "00").substr(1, 3)) : 0;
17638
- let date6 = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
17572
+ let date5 = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
17639
17573
  const tz = match[8];
17640
17574
  if (tz && tz !== "Z") {
17641
17575
  let d = parseSexagesimal(tz, false);
17642
17576
  if (Math.abs(d) < 30)
17643
17577
  d *= 60;
17644
- date6 -= 60000 * d;
17578
+ date5 -= 60000 * d;
17645
17579
  }
17646
- return new Date(date6);
17580
+ return new Date(date5);
17647
17581
  },
17648
17582
  stringify: ({ value }) => value?.toISOString().replace(/(T00:00:00)?\.000Z$/, "") ?? ""
17649
17583
  };
@@ -19503,7 +19437,7 @@ var require_composer = __commonJS((exports) => {
19503
19437
  var node_process = __require("process");
19504
19438
  var directives = require_directives();
19505
19439
  var Document = require_Document();
19506
- var errors4 = require_errors();
19440
+ var errors3 = require_errors();
19507
19441
  var identity = require_identity();
19508
19442
  var composeDoc = require_compose_doc();
19509
19443
  var resolveEnd = require_resolve_end();
@@ -19554,9 +19488,9 @@ var require_composer = __commonJS((exports) => {
19554
19488
  this.onError = (source, code, message, warning) => {
19555
19489
  const pos = getErrorPos(source);
19556
19490
  if (warning)
19557
- this.warnings.push(new errors4.YAMLWarning(pos, code, message));
19491
+ this.warnings.push(new errors3.YAMLWarning(pos, code, message));
19558
19492
  else
19559
- this.errors.push(new errors4.YAMLParseError(pos, code, message));
19493
+ this.errors.push(new errors3.YAMLParseError(pos, code, message));
19560
19494
  };
19561
19495
  this.directives = new directives.Directives({ version: options.version || "1.2" });
19562
19496
  this.options = options;
@@ -19642,7 +19576,7 @@ ${cb}` : comment;
19642
19576
  break;
19643
19577
  case "error": {
19644
19578
  const msg = token.source ? `${token.message}: ${JSON.stringify(token.source)}` : token.message;
19645
- const error51 = new errors4.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg);
19579
+ const error51 = new errors3.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg);
19646
19580
  if (this.atDirectives || !this.doc)
19647
19581
  this.errors.push(error51);
19648
19582
  else
@@ -19652,7 +19586,7 @@ ${cb}` : comment;
19652
19586
  case "doc-end": {
19653
19587
  if (!this.doc) {
19654
19588
  const msg = "Unexpected doc-end without preceding document";
19655
- this.errors.push(new errors4.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg));
19589
+ this.errors.push(new errors3.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg));
19656
19590
  break;
19657
19591
  }
19658
19592
  this.doc.directives.docEnd = true;
@@ -19667,7 +19601,7 @@ ${end.comment}` : end.comment;
19667
19601
  break;
19668
19602
  }
19669
19603
  default:
19670
- this.errors.push(new errors4.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", `Unsupported token ${token.type}`));
19604
+ this.errors.push(new errors3.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", `Unsupported token ${token.type}`));
19671
19605
  }
19672
19606
  }
19673
19607
  *end(forceDoc = false, endOffset = -1) {
@@ -19693,7 +19627,7 @@ ${end.comment}` : end.comment;
19693
19627
  var require_cst_scalar = __commonJS((exports) => {
19694
19628
  var resolveBlockScalar = require_resolve_block_scalar();
19695
19629
  var resolveFlowScalar = require_resolve_flow_scalar();
19696
- var errors4 = require_errors();
19630
+ var errors3 = require_errors();
19697
19631
  var stringifyString = require_stringifyString();
19698
19632
  function resolveAsScalar(token, strict = true, onError) {
19699
19633
  if (token) {
@@ -19702,7 +19636,7 @@ var require_cst_scalar = __commonJS((exports) => {
19702
19636
  if (onError)
19703
19637
  onError(offset, code, message);
19704
19638
  else
19705
- throw new errors4.YAMLParseError([offset, offset + 1], code, message);
19639
+ throw new errors3.YAMLParseError([offset, offset + 1], code, message);
19706
19640
  };
19707
19641
  switch (token.type) {
19708
19642
  case "scalar":
@@ -21582,7 +21516,7 @@ var require_parser = __commonJS((exports) => {
21582
21516
  var require_public_api = __commonJS((exports) => {
21583
21517
  var composer = require_composer();
21584
21518
  var Document = require_Document();
21585
- var errors4 = require_errors();
21519
+ var errors3 = require_errors();
21586
21520
  var log = require_log();
21587
21521
  var identity = require_identity();
21588
21522
  var lineCounter = require_line_counter();
@@ -21599,8 +21533,8 @@ var require_public_api = __commonJS((exports) => {
21599
21533
  const docs = Array.from(composer$1.compose(parser$1.parse(source)));
21600
21534
  if (prettyErrors && lineCounter2)
21601
21535
  for (const doc2 of docs) {
21602
- doc2.errors.forEach(errors4.prettifyError(source, lineCounter2));
21603
- doc2.warnings.forEach(errors4.prettifyError(source, lineCounter2));
21536
+ doc2.errors.forEach(errors3.prettifyError(source, lineCounter2));
21537
+ doc2.warnings.forEach(errors3.prettifyError(source, lineCounter2));
21604
21538
  }
21605
21539
  if (docs.length > 0)
21606
21540
  return docs;
@@ -21615,13 +21549,13 @@ var require_public_api = __commonJS((exports) => {
21615
21549
  if (!doc2)
21616
21550
  doc2 = _doc;
21617
21551
  else if (doc2.options.logLevel !== "silent") {
21618
- doc2.errors.push(new errors4.YAMLParseError(_doc.range.slice(0, 2), "MULTIPLE_DOCS", "Source contains multiple documents; please use YAML.parseAllDocuments()"));
21552
+ doc2.errors.push(new errors3.YAMLParseError(_doc.range.slice(0, 2), "MULTIPLE_DOCS", "Source contains multiple documents; please use YAML.parseAllDocuments()"));
21619
21553
  break;
21620
21554
  }
21621
21555
  }
21622
21556
  if (prettyErrors && lineCounter2) {
21623
- doc2.errors.forEach(errors4.prettifyError(source, lineCounter2));
21624
- doc2.warnings.forEach(errors4.prettifyError(source, lineCounter2));
21557
+ doc2.errors.forEach(errors3.prettifyError(source, lineCounter2));
21558
+ doc2.warnings.forEach(errors3.prettifyError(source, lineCounter2));
21625
21559
  }
21626
21560
  return doc2;
21627
21561
  }
@@ -21673,12 +21607,12 @@ var require_public_api = __commonJS((exports) => {
21673
21607
  });
21674
21608
 
21675
21609
  // ../../node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/index.js
21676
- 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;
21677
- var init_dist2 = __esm(() => {
21610
+ 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;
21611
+ var init_dist = __esm(() => {
21678
21612
  composer = require_composer();
21679
21613
  Document = require_Document();
21680
21614
  Schema = require_Schema();
21681
- errors4 = require_errors();
21615
+ errors3 = require_errors();
21682
21616
  Alias = require_Alias();
21683
21617
  identity = require_identity();
21684
21618
  Pair = require_Pair();
@@ -21694,9 +21628,9 @@ var init_dist2 = __esm(() => {
21694
21628
  $Composer = composer.Composer;
21695
21629
  $Document = Document.Document;
21696
21630
  $Schema = Schema.Schema;
21697
- $YAMLError = errors4.YAMLError;
21698
- $YAMLParseError = errors4.YAMLParseError;
21699
- $YAMLWarning = errors4.YAMLWarning;
21631
+ $YAMLError = errors3.YAMLError;
21632
+ $YAMLParseError = errors3.YAMLParseError;
21633
+ $YAMLWarning = errors3.YAMLWarning;
21700
21634
  $Alias = Alias.Alias;
21701
21635
  $isAlias = identity.isAlias;
21702
21636
  $isCollection = identity.isCollection;
@@ -21724,7 +21658,7 @@ var init_dist2 = __esm(() => {
21724
21658
  // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.39/node_modules/@gobing-ai/ts-runtime/dist/config.js
21725
21659
  var configSchema;
21726
21660
  var init_config = __esm(() => {
21727
- init_dist2();
21661
+ init_dist();
21728
21662
  init_zod();
21729
21663
  configSchema = exports_external.object({
21730
21664
  app: exports_external.object({
@@ -21835,8 +21769,14 @@ var init_file_system_node = () => {};
21835
21769
  // ../../packages/publish-harness/src/index.ts
21836
21770
  var exports_src = {};
21837
21771
  __export(exports_src, {
21838
- FakeFileCliTransport: () => FakeFileCliTransport
21772
+ trySelectors: () => trySelectors,
21773
+ resolveBrowserProfileDir: () => resolveBrowserProfileDir,
21774
+ launchPersistentBrowser: () => launchPersistentBrowser,
21775
+ FakeFileCliTransport: () => FakeFileCliTransport,
21776
+ FakeBrowserTransport: () => FakeBrowserTransport
21839
21777
  });
21778
+ import { homedir } from "os";
21779
+ import { join } from "path";
21840
21780
 
21841
21781
  class FakeFileCliTransport {
21842
21782
  kind = "file-cli";
@@ -21873,10 +21813,96 @@ class FakeFileCliTransport {
21873
21813
  }
21874
21814
  }
21875
21815
 
21816
+ class FakeBrowserTransport {
21817
+ kind = "browser";
21818
+ driver;
21819
+ callLogs;
21820
+ nextResult;
21821
+ constructor(driver = "agent") {
21822
+ this.driver = driver;
21823
+ this.callLogs = [];
21824
+ }
21825
+ setNextResult(result) {
21826
+ this.nextResult = result;
21827
+ }
21828
+ async publish(payload) {
21829
+ this.callLogs.push({
21830
+ timestamp: new Date().toISOString(),
21831
+ payload
21832
+ });
21833
+ if (this.nextResult) {
21834
+ return this.nextResult;
21835
+ }
21836
+ return {
21837
+ ok: true,
21838
+ target: "fake-browser",
21839
+ url: "https://example.com/browser-draft",
21840
+ id: "fake-browser-id-123",
21841
+ message: "Published offline via FakeBrowserTransport",
21842
+ metadata: { driver: this.driver }
21843
+ };
21844
+ }
21845
+ getCallLogs() {
21846
+ return this.callLogs;
21847
+ }
21848
+ reset() {
21849
+ this.callLogs = [];
21850
+ this.nextResult = undefined;
21851
+ }
21852
+ }
21853
+ function resolveBrowserProfileDir(channel, override) {
21854
+ if (override && override.trim().length > 0) {
21855
+ const trimmed = override.trim();
21856
+ if (trimmed.startsWith("~/")) {
21857
+ return join(homedir(), trimmed.slice(2));
21858
+ }
21859
+ return trimmed;
21860
+ }
21861
+ return join(homedir(), ".config", "kk", "browser-profiles", channel);
21862
+ }
21863
+ async function trySelectors(page, selectors, options = {}) {
21864
+ const timeout = options.timeout ?? 5000;
21865
+ const visible = options.visible ?? true;
21866
+ for (const selector of selectors) {
21867
+ try {
21868
+ const locator = page.locator(selector).first();
21869
+ const attached = await locator.count() > 0;
21870
+ if (!attached) {
21871
+ continue;
21872
+ }
21873
+ if (visible) {
21874
+ const isVisible = await locator.isVisible({ timeout: Math.min(timeout, 2000) }).catch(() => false);
21875
+ if (!isVisible) {
21876
+ continue;
21877
+ }
21878
+ }
21879
+ return { found: true, selector };
21880
+ } catch {}
21881
+ }
21882
+ return { found: false, selector: null };
21883
+ }
21884
+ async function launchPersistentBrowser(opts) {
21885
+ let chromium;
21886
+ try {
21887
+ const playwright = await import("playwright");
21888
+ chromium = playwright.chromium;
21889
+ } catch (error51) {
21890
+ const reason = error51 instanceof Error ? error51.message : String(error51);
21891
+ throw new Error(`launchPersistentBrowser requires the playwright package on the caller's dependency tree: ${reason}`);
21892
+ }
21893
+ if (typeof chromium?.launchPersistentContext !== "function") {
21894
+ throw new Error("launchPersistentBrowser requires the playwright package on the caller's dependency tree: chromium.launchPersistentContext is missing");
21895
+ }
21896
+ return chromium.launchPersistentContext(opts.profileDir, {
21897
+ headless: opts.headless ?? false
21898
+ });
21899
+ }
21900
+ var init_src = () => {};
21901
+
21876
21902
  // ../../plugins/publishings/surfdash-pub/src/index.ts
21877
- import { existsSync as existsSync2 } from "fs";
21878
- import { copyFile, cp } from "fs/promises";
21879
- import { dirname as dirname2, isAbsolute, join, resolve, sep } from "path";
21903
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
21904
+ import { copyFile, cp, writeFile } from "fs/promises";
21905
+ import { dirname as dirname2, isAbsolute, join as join2, resolve, sep } from "path";
21880
21906
  import { parseArgs } from "util";
21881
21907
 
21882
21908
  // ../../packages/kk-core/src/kinds.ts
@@ -21953,10 +21979,27 @@ var PluginManifestSchema = exports_external.object({
21953
21979
  init_file_system_node();
21954
21980
  init_config();
21955
21981
 
21982
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.67/node_modules/@gobing-ai/ts-utils/dist/output.js
21983
+ var defaultStderrTarget;
21984
+ function processStream(name) {
21985
+ const proc = globalThis.process;
21986
+ const stream = proc?.[name];
21987
+ if (stream === undefined) {
21988
+ throw new Error(`No ${name} target available: set one via setDefaultOutputTargets or pass an explicit target`);
21989
+ }
21990
+ return stream;
21991
+ }
21992
+ function writeLine(message, target) {
21993
+ target.write(`${message}
21994
+ `);
21995
+ }
21996
+ function echoError(message, target = defaultStderrTarget ?? processStream("stderr")) {
21997
+ writeLine(message, target);
21998
+ }
21999
+
21956
22000
  // ../../packages/kk-core/src/render-md.ts
21957
22001
  if (false) {}
21958
22002
  // ../../plugins/publishings/surfdash-pub/src/index.ts
21959
- init_dist();
21960
22003
  var TARGET = "surfdash";
21961
22004
  var OPERATIONS = ["publish", "scaffold-locale", "sync"];
21962
22005
  var SCAFFOLD_LOCALES = ["en", "ja"];
@@ -22146,9 +22189,9 @@ async function scaffoldLocale(options) {
22146
22189
  if (!existsSync2(draftPath)) {
22147
22190
  return fail(`sd translate reported success but the draft is missing: ${draftPath}`);
22148
22191
  }
22149
- const assetsDir = join(dirname2(postPath.postPath), "assets");
22192
+ const assetsDir = join2(dirname2(postPath.postPath), "assets");
22150
22193
  if (existsSync2(assetsDir)) {
22151
- await cp(assetsDir, join(dirname2(draftPath), "assets"), { recursive: true });
22194
+ await cp(assetsDir, join2(dirname2(draftPath), "assets"), { recursive: true });
22152
22195
  }
22153
22196
  return ResultSchema.parse({
22154
22197
  ok: true,
@@ -22160,6 +22203,21 @@ async function scaffoldLocale(options) {
22160
22203
  return fail(`scaffold-locale transport error: ${error51 instanceof Error ? error51.message : String(error51)}`);
22161
22204
  }
22162
22205
  }
22206
+ function normalizeLocaleFrontmatter(markdown) {
22207
+ const lines = markdown.split(`
22208
+ `);
22209
+ const inFrontmatter = lines[0]?.trim() === "---";
22210
+ for (let i = 1;inFrontmatter && i < lines.length; i++) {
22211
+ if (lines[i]?.trim() === "---")
22212
+ break;
22213
+ if (/^canonical\s*:/.test(lines[i] ?? ""))
22214
+ lines[i] = "";
22215
+ else if (/^draft\s*:/.test(lines[i] ?? ""))
22216
+ lines[i] = "draft: false";
22217
+ }
22218
+ return lines.filter((line) => line !== "").join(`
22219
+ `);
22220
+ }
22163
22221
  async function syncPost(options) {
22164
22222
  const postPath = resolvePostPath(options);
22165
22223
  if ("error" in postPath)
@@ -22171,6 +22229,16 @@ async function syncPost(options) {
22171
22229
  const outsideRoot = postPathOutsideRoot(postPath.postPath, surfdashRoot);
22172
22230
  if (outsideRoot)
22173
22231
  return fail(outsideRoot);
22232
+ for (const locale of ["en", "ja"]) {
22233
+ const sibling = deriveLocaleDraftPath(postPath.postPath, locale);
22234
+ if (!sibling || !existsSync2(sibling))
22235
+ continue;
22236
+ const normalized = normalizeLocaleFrontmatter(readFileSync2(sibling, "utf-8"));
22237
+ if (normalized !== readFileSync2(sibling, "utf-8")) {
22238
+ await writeFile(sibling, normalized);
22239
+ echoError(`sync: normalized frontmatter for ${locale} sibling: ${sibling}`);
22240
+ }
22241
+ }
22174
22242
  try {
22175
22243
  const spawned = await spawnCli(["bun", "scripts/publish-orchestrator.ts", "sync", postPath.postPath], surfdashRoot);
22176
22244
  if (spawned.exitCode !== 0) {
@@ -22198,22 +22266,22 @@ async function publishPost(payload) {
22198
22266
  const dateStamp = runDate || today;
22199
22267
  const titleSlug = slugify2(payload.content.title ?? "");
22200
22268
  const slug = titleSlug ? `${dateStamp}-${titleSlug}` : `${dateStamp}-post`;
22201
- const postDir = join(surfdashRoot, "content", "posts", "articles", locale, slug);
22269
+ const postDir = join2(surfdashRoot, "content", "posts", "articles", locale, slug);
22202
22270
  await createNodeFileSystem().ensureDir(postDir);
22203
22271
  let imageFrontmatter;
22204
22272
  const coverPath = payload.content.metadata?.coverImage;
22205
22273
  if (typeof coverPath === "string" && existsSync2(coverPath)) {
22206
- const assetsDir = join(postDir, "assets");
22274
+ const assetsDir = join2(postDir, "assets");
22207
22275
  await createNodeFileSystem().ensureDir(assetsDir);
22208
22276
  const coverName = `cover${coverPath.slice(coverPath.lastIndexOf("."))}`;
22209
- await copyFile(coverPath, join(assetsDir, coverName));
22277
+ await copyFile(coverPath, join2(assetsDir, coverName));
22210
22278
  imageFrontmatter = `./assets/${coverName}`;
22211
22279
  }
22212
22280
  const markdown = mapContentToSurfingMarkdown(payload.content, {
22213
22281
  image: imageFrontmatter,
22214
22282
  publishDate: runDate ? `${runDate.slice(0, 4)}-${runDate.slice(4, 6)}-${runDate.slice(6, 8)}` : undefined
22215
22283
  });
22216
- const filePath = join(postDir, "index.md");
22284
+ const filePath = join2(postDir, "index.md");
22217
22285
  try {
22218
22286
  await createNodeFileSystem().writeFile(filePath, markdown);
22219
22287
  const spawned = await spawnCli([postsurfingBin, "publish", filePath], surfdashRoot);
@@ -22230,7 +22298,7 @@ async function publishPost(payload) {
22230
22298
  metadata: { postPath: filePath }
22231
22299
  });
22232
22300
  }
22233
- const relPostDir = join("content", "posts", "articles", locale, slug);
22301
+ const relPostDir = join2("content", "posts", "articles", locale, slug);
22234
22302
  const pushed = await pushPostGit(surfdashRoot, relPostDir, `content: daily AI news ${dateStamp} (zh)`);
22235
22303
  if (!pushed.ok)
22236
22304
  return fail(pushed.error);
@@ -22266,7 +22334,7 @@ class PostsurfingFileCliTransport {
22266
22334
  }
22267
22335
  function getPublishTransport() {
22268
22336
  if (process.env.KNOWLEDGE_KIT_PUBLISH_TRANSPORT === "fake") {
22269
- const { FakeFileCliTransport: FakeFileCliTransport2 } = __toCommonJS(exports_src);
22337
+ const { FakeFileCliTransport: FakeFileCliTransport2 } = (init_src(), __toCommonJS(exports_src));
22270
22338
  return new FakeFileCliTransport2;
22271
22339
  }
22272
22340
  return new PostsurfingFileCliTransport;
@@ -22315,6 +22383,7 @@ export {
22315
22383
  slugify2 as slugify,
22316
22384
  pushPostGit,
22317
22385
  processPublishIO,
22386
+ normalizeLocaleFrontmatter,
22318
22387
  mapContentToSurfingMarkdown,
22319
22388
  main2 as main,
22320
22389
  getPublishTransport,
@@ -1,5 +1,5 @@
1
- import { existsSync } from 'node:fs';
2
- import { copyFile, cp } from 'node:fs/promises';
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { copyFile, cp, writeFile } from 'node:fs/promises';
3
3
  import { dirname, isAbsolute, join, resolve, sep } from 'node:path';
4
4
  import { parseArgs } from 'node:util';
5
5
  import { type Content, ContentSchema, type Result, ResultSchema } from '@gobing-ai/kk-core';
@@ -350,6 +350,25 @@ async function scaffoldLocale(options: SurfdashPubOptions): Promise<Result> {
350
350
  * `bun scripts/publish-orchestrator.ts sync <postPath>` inside the surfdash checkout
351
351
  * and returns ok/fail with stderr on failure.
352
352
  */
353
+ /**
354
+ * Deterministic frontmatter normalization for locale siblings before CMS sync
355
+ * (2026-09-16 hreflang feedback): the site's alternates builder reads published
356
+ * disk siblings, so each non-primary locale must end `draft: false` and must not
357
+ * carry a stale cross-locale `canonical` (the translation agent sometimes copies
358
+ * zh frontmatter verbatim). Line-based — no YAML lib in this plugin. Backstop
359
+ * for the agent prompt; safe because sync always runs after the agent's edit.
360
+ */
361
+ export function normalizeLocaleFrontmatter(markdown: string): string {
362
+ const lines = markdown.split('\n');
363
+ const inFrontmatter = lines[0]?.trim() === '---';
364
+ for (let i = 1; inFrontmatter && i < lines.length; i++) {
365
+ if (lines[i]?.trim() === '---') break;
366
+ if (/^canonical\s*:/.test(lines[i] ?? '')) lines[i] = '';
367
+ else if (/^draft\s*:/.test(lines[i] ?? '')) lines[i] = 'draft: false';
368
+ }
369
+ return lines.filter((line) => line !== '').join('\n');
370
+ }
371
+
353
372
  async function syncPost(options: SurfdashPubOptions): Promise<Result> {
354
373
  const postPath = resolvePostPath(options);
355
374
  if ('error' in postPath) return fail(postPath.error);
@@ -361,6 +380,17 @@ async function syncPost(options: SurfdashPubOptions): Promise<Result> {
361
380
  const outsideRoot = postPathOutsideRoot(postPath.postPath, surfdashRoot);
362
381
  if (outsideRoot) return fail(outsideRoot);
363
382
 
383
+ // Normalize en/ja siblings (draft flag + stale canonical) before the orchestrator syncs them.
384
+ for (const locale of ['en', 'ja'] as const) {
385
+ const sibling = deriveLocaleDraftPath(postPath.postPath, locale);
386
+ if (!sibling || !existsSync(sibling)) continue;
387
+ const normalized = normalizeLocaleFrontmatter(readFileSync(sibling, 'utf-8'));
388
+ if (normalized !== readFileSync(sibling, 'utf-8')) {
389
+ await writeFile(sibling, normalized);
390
+ echoError(`sync: normalized frontmatter for ${locale} sibling: ${sibling}`);
391
+ }
392
+ }
393
+
364
394
  try {
365
395
  const spawned = await spawnCli(
366
396
  ['bun', 'scripts/publish-orchestrator.ts', 'sync', postPath.postPath],