@gobing-ai/knowledge-kit 0.0.17 → 0.0.19

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 (76) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +74 -0
  3. package/dist/index.js +108 -107
  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 +57 -99
  8. package/plugins/generations/daily-article-gen/src/index.ts +12 -4
  9. package/plugins/generations/dailynews-gen/dist/index.js +57 -102
  10. package/plugins/generations/dailynews-gen/src/script-builder.ts +9 -1
  11. package/plugins/generations/episode-plan-gen/dist/index.js +120 -113
  12. package/plugins/generations/episode-plan-gen/src/index.ts +102 -22
  13. package/plugins/generations/image-gen/dist/index.js +63 -115
  14. package/plugins/generations/news-report-gen/dist/index.js +117 -99
  15. package/plugins/generations/news-report-gen/src/index.ts +117 -1
  16. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/pipeline.py +1 -1
  17. package/plugins/generations/omni-voice-gen/src/omni_voice_gen/voicescript.py +8 -0
  18. package/plugins/generations/voice-gen/dist/index.js +161 -120
  19. package/plugins/generations/voice-gen/src/concat.ts +34 -0
  20. package/plugins/generations/voice-gen/src/index.ts +14 -4
  21. package/plugins/generations/voice-gen/src/voicebox-server.ts +91 -0
  22. package/plugins/generations/voice-gen/src/voicescript.ts +8 -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 +10 -6
  29. package/plugins/kk/plugin.json +1 -1
  30. package/plugins/kk/scripts/kk-workflow-stages.ts +376 -70
  31. package/plugins/kk/skills/publish/SKILL.md +80 -0
  32. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +136 -224
  33. package/plugins/kk/workflows/leftover-publish-fanout-example.yaml +53 -0
  34. package/plugins/publishings/emdash-pub/dist/index.js +42 -91
  35. package/plugins/publishings/infoq-pub/dist/index.js +21994 -0
  36. package/plugins/publishings/infoq-pub/package.json +17 -0
  37. package/plugins/publishings/infoq-pub/plugin.json +7 -0
  38. package/plugins/publishings/infoq-pub/src/index.ts +123 -0
  39. package/plugins/publishings/infoq-pub/tsconfig.json +4 -0
  40. package/plugins/publishings/juejin-pub/dist/index.js +21994 -0
  41. package/plugins/publishings/juejin-pub/package.json +17 -0
  42. package/plugins/publishings/juejin-pub/plugin.json +7 -0
  43. package/plugins/publishings/juejin-pub/src/index.ts +123 -0
  44. package/plugins/publishings/juejin-pub/tsconfig.json +4 -0
  45. package/plugins/publishings/medium-pub/dist/index.js +22041 -0
  46. package/plugins/publishings/medium-pub/package.json +17 -0
  47. package/plugins/publishings/medium-pub/plugin.json +7 -0
  48. package/plugins/publishings/medium-pub/src/index.ts +215 -0
  49. package/plugins/publishings/medium-pub/tsconfig.json +4 -0
  50. package/plugins/publishings/podcast-pub/dist/index.js +183 -123
  51. package/plugins/publishings/podcast-pub/src/index.ts +47 -3
  52. package/plugins/publishings/podcast-pub/src/show-notes.ts +153 -34
  53. package/plugins/publishings/qiita-pub/dist/index.js +42 -91
  54. package/plugins/publishings/substack-pub/dist/index.js +21994 -0
  55. package/plugins/publishings/substack-pub/package.json +17 -0
  56. package/plugins/publishings/substack-pub/plugin.json +7 -0
  57. package/plugins/publishings/substack-pub/src/index.ts +123 -0
  58. package/plugins/publishings/substack-pub/tsconfig.json +4 -0
  59. package/plugins/publishings/surfdash-pub/dist/index.js +172 -103
  60. package/plugins/publishings/surfdash-pub/src/index.ts +32 -2
  61. package/plugins/publishings/wechatmp-pub/dist/index.js +22119 -0
  62. package/plugins/publishings/wechatmp-pub/package.json +17 -0
  63. package/plugins/publishings/wechatmp-pub/plugin.json +7 -0
  64. package/plugins/publishings/wechatmp-pub/src/index.ts +304 -0
  65. package/plugins/publishings/wechatmp-pub/tsconfig.json +4 -0
  66. package/plugins/publishings/x-pub/dist/index.js +22110 -0
  67. package/plugins/publishings/x-pub/package.json +17 -0
  68. package/plugins/publishings/x-pub/plugin.json +7 -0
  69. package/plugins/publishings/x-pub/src/index.ts +222 -0
  70. package/plugins/publishings/x-pub/tsconfig.json +4 -0
  71. package/plugins/publishings/xhs-pub/dist/index.js +21994 -0
  72. package/plugins/publishings/xhs-pub/package.json +17 -0
  73. package/plugins/publishings/xhs-pub/plugin.json +7 -0
  74. package/plugins/publishings/xhs-pub/src/index.ts +123 -0
  75. package/plugins/publishings/xhs-pub/tsconfig.json +4 -0
  76. package/plugins/publishings/zenn-pub/dist/index.js +42 -91
@@ -14642,72 +14642,6 @@ var init_zod = __esm(() => {
14642
14642
  init_external();
14643
14643
  });
14644
14644
 
14645
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.39/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.4.39/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.4.39/node_modules/@gobing-ai/ts-utils/dist/cursor.js
14680
- var init_cursor = () => {};
14681
-
14682
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.39/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.4.39/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.4.39/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
-
14711
14645
  // ../../node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js
14712
14646
  var require_identity = __commonJS((exports) => {
14713
14647
  var ALIAS = Symbol.for("yaml.alias");
@@ -17612,15 +17546,15 @@ var require_timestamp = __commonJS((exports) => {
17612
17546
  throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd");
17613
17547
  const [, year, month, day, hour, minute, second] = match.map(Number);
17614
17548
  const millisec = match[7] ? Number((match[7] + "00").substr(1, 3)) : 0;
17615
- let date6 = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
17549
+ let date5 = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
17616
17550
  const tz = match[8];
17617
17551
  if (tz && tz !== "Z") {
17618
17552
  let d = parseSexagesimal(tz, false);
17619
17553
  if (Math.abs(d) < 30)
17620
17554
  d *= 60;
17621
- date6 -= 60000 * d;
17555
+ date5 -= 60000 * d;
17622
17556
  }
17623
- return new Date(date6);
17557
+ return new Date(date5);
17624
17558
  },
17625
17559
  stringify: ({ value }) => value?.toISOString().replace(/(T00:00:00)?\.000Z$/, "") ?? ""
17626
17560
  };
@@ -19480,7 +19414,7 @@ var require_composer = __commonJS((exports) => {
19480
19414
  var node_process = __require("process");
19481
19415
  var directives = require_directives();
19482
19416
  var Document = require_Document();
19483
- var errors4 = require_errors();
19417
+ var errors3 = require_errors();
19484
19418
  var identity = require_identity();
19485
19419
  var composeDoc = require_compose_doc();
19486
19420
  var resolveEnd = require_resolve_end();
@@ -19531,9 +19465,9 @@ var require_composer = __commonJS((exports) => {
19531
19465
  this.onError = (source, code, message, warning) => {
19532
19466
  const pos = getErrorPos(source);
19533
19467
  if (warning)
19534
- this.warnings.push(new errors4.YAMLWarning(pos, code, message));
19468
+ this.warnings.push(new errors3.YAMLWarning(pos, code, message));
19535
19469
  else
19536
- this.errors.push(new errors4.YAMLParseError(pos, code, message));
19470
+ this.errors.push(new errors3.YAMLParseError(pos, code, message));
19537
19471
  };
19538
19472
  this.directives = new directives.Directives({ version: options.version || "1.2" });
19539
19473
  this.options = options;
@@ -19619,7 +19553,7 @@ ${cb}` : comment;
19619
19553
  break;
19620
19554
  case "error": {
19621
19555
  const msg = token.source ? `${token.message}: ${JSON.stringify(token.source)}` : token.message;
19622
- const error51 = new errors4.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg);
19556
+ const error51 = new errors3.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg);
19623
19557
  if (this.atDirectives || !this.doc)
19624
19558
  this.errors.push(error51);
19625
19559
  else
@@ -19629,7 +19563,7 @@ ${cb}` : comment;
19629
19563
  case "doc-end": {
19630
19564
  if (!this.doc) {
19631
19565
  const msg = "Unexpected doc-end without preceding document";
19632
- this.errors.push(new errors4.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg));
19566
+ this.errors.push(new errors3.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg));
19633
19567
  break;
19634
19568
  }
19635
19569
  this.doc.directives.docEnd = true;
@@ -19644,7 +19578,7 @@ ${end.comment}` : end.comment;
19644
19578
  break;
19645
19579
  }
19646
19580
  default:
19647
- this.errors.push(new errors4.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", `Unsupported token ${token.type}`));
19581
+ this.errors.push(new errors3.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", `Unsupported token ${token.type}`));
19648
19582
  }
19649
19583
  }
19650
19584
  *end(forceDoc = false, endOffset = -1) {
@@ -19670,7 +19604,7 @@ ${end.comment}` : end.comment;
19670
19604
  var require_cst_scalar = __commonJS((exports) => {
19671
19605
  var resolveBlockScalar = require_resolve_block_scalar();
19672
19606
  var resolveFlowScalar = require_resolve_flow_scalar();
19673
- var errors4 = require_errors();
19607
+ var errors3 = require_errors();
19674
19608
  var stringifyString = require_stringifyString();
19675
19609
  function resolveAsScalar(token, strict = true, onError) {
19676
19610
  if (token) {
@@ -19679,7 +19613,7 @@ var require_cst_scalar = __commonJS((exports) => {
19679
19613
  if (onError)
19680
19614
  onError(offset, code, message);
19681
19615
  else
19682
- throw new errors4.YAMLParseError([offset, offset + 1], code, message);
19616
+ throw new errors3.YAMLParseError([offset, offset + 1], code, message);
19683
19617
  };
19684
19618
  switch (token.type) {
19685
19619
  case "scalar":
@@ -21559,7 +21493,7 @@ var require_parser = __commonJS((exports) => {
21559
21493
  var require_public_api = __commonJS((exports) => {
21560
21494
  var composer = require_composer();
21561
21495
  var Document = require_Document();
21562
- var errors4 = require_errors();
21496
+ var errors3 = require_errors();
21563
21497
  var log = require_log();
21564
21498
  var identity = require_identity();
21565
21499
  var lineCounter = require_line_counter();
@@ -21576,8 +21510,8 @@ var require_public_api = __commonJS((exports) => {
21576
21510
  const docs = Array.from(composer$1.compose(parser$1.parse(source)));
21577
21511
  if (prettyErrors && lineCounter2)
21578
21512
  for (const doc2 of docs) {
21579
- doc2.errors.forEach(errors4.prettifyError(source, lineCounter2));
21580
- doc2.warnings.forEach(errors4.prettifyError(source, lineCounter2));
21513
+ doc2.errors.forEach(errors3.prettifyError(source, lineCounter2));
21514
+ doc2.warnings.forEach(errors3.prettifyError(source, lineCounter2));
21581
21515
  }
21582
21516
  if (docs.length > 0)
21583
21517
  return docs;
@@ -21592,13 +21526,13 @@ var require_public_api = __commonJS((exports) => {
21592
21526
  if (!doc2)
21593
21527
  doc2 = _doc;
21594
21528
  else if (doc2.options.logLevel !== "silent") {
21595
- doc2.errors.push(new errors4.YAMLParseError(_doc.range.slice(0, 2), "MULTIPLE_DOCS", "Source contains multiple documents; please use YAML.parseAllDocuments()"));
21529
+ doc2.errors.push(new errors3.YAMLParseError(_doc.range.slice(0, 2), "MULTIPLE_DOCS", "Source contains multiple documents; please use YAML.parseAllDocuments()"));
21596
21530
  break;
21597
21531
  }
21598
21532
  }
21599
21533
  if (prettyErrors && lineCounter2) {
21600
- doc2.errors.forEach(errors4.prettifyError(source, lineCounter2));
21601
- doc2.warnings.forEach(errors4.prettifyError(source, lineCounter2));
21534
+ doc2.errors.forEach(errors3.prettifyError(source, lineCounter2));
21535
+ doc2.warnings.forEach(errors3.prettifyError(source, lineCounter2));
21602
21536
  }
21603
21537
  return doc2;
21604
21538
  }
@@ -21650,12 +21584,12 @@ var require_public_api = __commonJS((exports) => {
21650
21584
  });
21651
21585
 
21652
21586
  // ../../node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/index.js
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(() => {
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(() => {
21655
21589
  composer = require_composer();
21656
21590
  Document = require_Document();
21657
21591
  Schema = require_Schema();
21658
- errors4 = require_errors();
21592
+ errors3 = require_errors();
21659
21593
  Alias = require_Alias();
21660
21594
  identity = require_identity();
21661
21595
  Pair = require_Pair();
@@ -21671,9 +21605,9 @@ var init_dist2 = __esm(() => {
21671
21605
  $Composer = composer.Composer;
21672
21606
  $Document = Document.Document;
21673
21607
  $Schema = Schema.Schema;
21674
- $YAMLError = errors4.YAMLError;
21675
- $YAMLParseError = errors4.YAMLParseError;
21676
- $YAMLWarning = errors4.YAMLWarning;
21608
+ $YAMLError = errors3.YAMLError;
21609
+ $YAMLParseError = errors3.YAMLParseError;
21610
+ $YAMLWarning = errors3.YAMLWarning;
21677
21611
  $Alias = Alias.Alias;
21678
21612
  $isAlias = identity.isAlias;
21679
21613
  $isCollection = identity.isCollection;
@@ -21701,7 +21635,7 @@ var init_dist2 = __esm(() => {
21701
21635
  // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.39/node_modules/@gobing-ai/ts-runtime/dist/config.js
21702
21636
  var configSchema;
21703
21637
  var init_config = __esm(() => {
21704
- init_dist2();
21638
+ init_dist();
21705
21639
  init_zod();
21706
21640
  configSchema = exports_external.object({
21707
21641
  app: exports_external.object({
@@ -21810,6 +21744,7 @@ function findProjectRoot(startDir) {
21810
21744
  var init_file_system_node = () => {};
21811
21745
 
21812
21746
  // ../../plugins/publishings/podcast-pub/src/index.ts
21747
+ import { readFileSync as readFileSync2 } from "fs";
21813
21748
  import { readFile } from "fs/promises";
21814
21749
  import { dirname as dirname2 } from "path";
21815
21750
  import { parseArgs } from "util";
@@ -21888,6 +21823,24 @@ var PluginManifestSchema = exports_external.object({
21888
21823
  init_file_system_node();
21889
21824
  init_config();
21890
21825
 
21826
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.67/node_modules/@gobing-ai/ts-utils/dist/output.js
21827
+ var defaultStderrTarget;
21828
+ function processStream(name) {
21829
+ const proc = globalThis.process;
21830
+ const stream = proc?.[name];
21831
+ if (stream === undefined) {
21832
+ throw new Error(`No ${name} target available: set one via setDefaultOutputTargets or pass an explicit target`);
21833
+ }
21834
+ return stream;
21835
+ }
21836
+ function writeLine(message, target) {
21837
+ target.write(`${message}
21838
+ `);
21839
+ }
21840
+ function echoError(message, target = defaultStderrTarget ?? processStream("stderr")) {
21841
+ writeLine(message, target);
21842
+ }
21843
+
21891
21844
  // ../../packages/kk-core/src/render-md.ts
21892
21845
  if (false) {}
21893
21846
  // ../../packages/publish-harness/src/index.ts
@@ -21926,9 +21879,6 @@ class FakeFileCliTransport {
21926
21879
  }
21927
21880
  }
21928
21881
 
21929
- // ../../plugins/publishings/podcast-pub/src/index.ts
21930
- init_dist();
21931
-
21932
21882
  // ../../plugins/publishings/podcast-pub/src/show-notes.ts
21933
21883
  function escapeHtml(value) {
21934
21884
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
@@ -21945,8 +21895,80 @@ function renderInline(escaped) {
21945
21895
  }
21946
21896
  return (out + rest).replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
21947
21897
  }
21898
+ function normalizeForMatch(text) {
21899
+ return text.toLowerCase().replace(/[^\p{Script=Han}\p{L}\p{N}]+/gu, "");
21900
+ }
21901
+ function formatOffset(seconds) {
21902
+ if (!Number.isFinite(seconds) || seconds < 0)
21903
+ return "--:--";
21904
+ const m = Math.floor(seconds / 60);
21905
+ const s = Math.round(seconds % 60);
21906
+ return s === 60 ? `${m + 1}:00` : `${m}:${String(s).padStart(2, "0")}`;
21907
+ }
21908
+ function computeAudioOffsets(docs, segments) {
21909
+ const fallback = () => {
21910
+ const chars = docs.map((d) => Math.max((d.body ?? "").trim().length, 1));
21911
+ const total = chars.reduce((a, b) => a + b, 0);
21912
+ const sumDuration = (segments ?? []).reduce((a, s) => a + (s.duration ?? 0), 0);
21913
+ let acc = 0;
21914
+ return chars.map((c) => {
21915
+ const at = acc;
21916
+ acc += c / total * sumDuration;
21917
+ return at;
21918
+ });
21919
+ };
21920
+ if (!segments || segments.length < 2)
21921
+ return fallback();
21922
+ const starts = [];
21923
+ let elapsed = 0;
21924
+ const marks = segments.map((s) => {
21925
+ const at = elapsed;
21926
+ elapsed += s.duration ?? 0;
21927
+ return { at, text: normalizeForMatch(s.transcription ?? "") };
21928
+ });
21929
+ let cursor = 1;
21930
+ for (const d of docs) {
21931
+ const title = normalizeForMatch(d.title ?? "");
21932
+ const needle = title.slice(0, 12);
21933
+ const hit = needle.length >= 4 ? marks.findIndex((m, i) => i >= cursor && m.text.includes(needle)) : -1;
21934
+ if (hit >= 0) {
21935
+ starts.push(marks[hit]?.at ?? 0);
21936
+ cursor = hit + 1;
21937
+ } else {
21938
+ starts.push(Number.NaN);
21939
+ }
21940
+ }
21941
+ if (starts.some((v) => !Number.isFinite(v)))
21942
+ return fallback();
21943
+ return starts;
21944
+ }
21945
+ function renderTimelineHtml(docs, offsets, articleUrl) {
21946
+ const rows = docs.map((d, i) => {
21947
+ const title = escapeHtml(d.title ?? `\u6761\u76EE ${i + 1}`);
21948
+ const brief = escapeHtml(itemBrief(d));
21949
+ const at = formatOffset(offsets[i] ?? Number.NaN);
21950
+ const link = d.sourceUri ? ` <a href="${escapeHtml(d.sourceUri)}" target="_blank" rel="noopener">\u6765\u6E90</a>` : "";
21951
+ return `
21952
+ <div class="kk-tl-row"><div class="kk-tl-t">${at}</div><div class="kk-tl-body"><div class="kk-tl-title">${i + 1}. ${title}${link}</div><p class="kk-tl-brief">${brief}</p></div></div>`;
21953
+ }).join("");
21954
+ return `<!--kk:timeline-->
21955
+ <style>
21956
+ .kk-tl{margin:1em 0}
21957
+ .kk-tl-row{display:flex;gap:12px;padding:10px 0;border-bottom:1px solid rgba(128,128,128,.25)}
21958
+ .kk-tl-t{flex:0 0 56px;font-variant-numeric:tabular-nums;font-weight:700;color:#0969da}
21959
+ .kk-tl-body{flex:1;min-width:0}
21960
+ .kk-tl-title{font-weight:600}
21961
+ .kk-tl-brief{margin:4px 0 0;color:#666;font-size:.95em}
21962
+ </style>
21963
+ <div class="kk-tl">${rows}
21964
+ </div>
21965
+ <p>\u5B8C\u6574\u56FE\u6587\u7248\uFF1A<a href="${escapeHtml(articleUrl)}" target="_blank" rel="noopener">${escapeHtml("\u672C\u671F\u5B8C\u6574\u6587\u7AE0")}</a></p>
21966
+ <!--/kk:timeline-->`;
21967
+ }
21948
21968
  function renderShowNotesHtml(markdown) {
21949
21969
  const blocks = [];
21970
+ const timeline = [];
21971
+ let inTimeline = false;
21950
21972
  let paragraph = [];
21951
21973
  const flush = () => {
21952
21974
  if (paragraph.length > 0) {
@@ -21957,6 +21979,19 @@ function renderShowNotesHtml(markdown) {
21957
21979
  for (const rawLine of markdown.split(`
21958
21980
  `)) {
21959
21981
  const line = rawLine.trim();
21982
+ if (line === "<!--kk:timeline-->") {
21983
+ flush();
21984
+ inTimeline = true;
21985
+ continue;
21986
+ }
21987
+ if (inTimeline) {
21988
+ if (line === "<!--/kk:timeline-->") {
21989
+ inTimeline = false;
21990
+ continue;
21991
+ }
21992
+ timeline.push(rawLine);
21993
+ continue;
21994
+ }
21960
21995
  if (line.length === 0) {
21961
21996
  flush();
21962
21997
  continue;
@@ -21974,9 +22009,18 @@ function renderShowNotesHtml(markdown) {
21974
22009
  paragraph.push(line);
21975
22010
  }
21976
22011
  flush();
22012
+ if (timeline.length > 0)
22013
+ blocks.push(timeline.join(`
22014
+ `));
21977
22015
  return blocks.join(`
21978
22016
  `);
21979
22017
  }
22018
+ function itemBrief(d) {
22019
+ const meta3 = d.metadata ?? {};
22020
+ if (typeof meta3.brief === "string" && meta3.brief.trim().length > 0)
22021
+ return meta3.brief.trim();
22022
+ return (d.body ?? "").trim().split(/(?<=[\u3002\uFF01\uFF1F])\s*/).slice(0, 2).join("");
22023
+ }
21980
22024
  function renderShowNotesMarkdown(docs, opts) {
21981
22025
  const slugify2 = (t) => {
21982
22026
  const s = t.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/[\s_]+/g, "-");
@@ -21998,33 +22042,18 @@ function renderShowNotesMarkdown(docs, opts) {
21998
22042
  return "\u6765\u6E90";
21999
22043
  }
22000
22044
  };
22001
- const itemBrief = (d) => {
22002
- const meta3 = d.metadata ?? {};
22003
- if (typeof meta3.brief === "string" && meta3.brief.trim().length > 0)
22004
- return meta3.brief.trim();
22005
- return (d.body ?? "").trim().split(/(?<=[\u3002\uFF01\uFF1F])\s*/).slice(0, 2).join("");
22006
- };
22007
- const itemTimestamp = (d) => {
22008
- const meta3 = d.metadata ?? {};
22009
- const raw = meta3.publishAt;
22010
- const t = typeof raw === "string" ? Date.parse(raw) : typeof raw === "number" ? raw : Number.NaN;
22011
- if (!Number.isFinite(t))
22012
- return "--:--";
22013
- const at = new Date(t);
22014
- return `${String(at.getHours()).padStart(2, "0")}:${String(at.getMinutes()).padStart(2, "0")}`;
22015
- };
22045
+ const offsets = computeAudioOffsets(docs, opts.audioSegments);
22016
22046
  const lines = [`# ${opts.title}`, ""];
22017
- docs.forEach((d, i) => {
22018
- const body = (d.body ?? "").trim();
22019
- const sentences = body.split(/(?<=[\u3002\uFF01\uFF1F])\s*/).slice(0, 2).join("");
22020
- lines.push(`## ${i + 1}. ${d.title ?? `\u6761\u76EE ${i + 1}`}`, "");
22021
- if (briefMode) {
22022
- lines.push(`${itemTimestamp(d)} \xB7 ${itemBrief(d)}`, "", `[\u6765\u6E90\uFF1A${sourceLabel(d)}](${d.sourceUri ?? ""}) \xB7 [\u6587\u7AE0](${articleUrl})`, "");
22023
- } else {
22024
- lines.push(sentences, "", `[\u6765\u6E90\uFF1A${sourceLabel(d)}](${d.sourceUri ?? ""})`, "");
22025
- }
22026
- });
22027
- lines.push(`\u8BE6\u60C5\u89C1\u672C\u671F\u5B8C\u6574\u6587\u7AE0\uFF1A[ ${opts.title} ](${articleUrl})`, "");
22047
+ if (briefMode) {
22048
+ lines.push(renderTimelineHtml(docs, offsets, articleUrl), "");
22049
+ } else {
22050
+ docs.forEach((d, i) => {
22051
+ const body = (d.body ?? "").trim();
22052
+ const sentences = body.split(/(?<=[\u3002\uFF01\uFF1F])\s*/).slice(0, 2).join("");
22053
+ lines.push(`## ${i + 1}. ${d.title ?? `\u6761\u76EE ${i + 1}`}`, "", sentences, "", `[\u6765\u6E90\uFF1A${sourceLabel(d)}](${d.sourceUri ?? ""})`, "");
22054
+ });
22055
+ lines.push(`\u8BE6\u60C5\u89C1\u672C\u671F\u5B8C\u6574\u6587\u7AE0\uFF1A[ ${opts.title} ](${articleUrl})`, "");
22056
+ }
22028
22057
  return lines.join(`
22029
22058
  `);
22030
22059
  }
@@ -22255,6 +22284,14 @@ class MicrofeedClient {
22255
22284
  }
22256
22285
 
22257
22286
  // ../../plugins/publishings/podcast-pub/src/index.ts
22287
+ function publicItemUrl(item, itemId, client) {
22288
+ const microfeed = item?._microfeed;
22289
+ const webUrl = typeof microfeed?.web_url === "string" ? microfeed.web_url : "";
22290
+ if (webUrl !== "")
22291
+ return webUrl;
22292
+ const feedUrl = typeof item?.url === "string" ? item.url : "";
22293
+ return feedUrl !== "" ? feedUrl : client.itemUrl(itemId);
22294
+ }
22258
22295
  var TARGET = "podcast-pub";
22259
22296
  var OPERATIONS = ["publish", "show-notes", "list", "get", "update", "delete", "validate"];
22260
22297
  function resolveClient() {
@@ -22310,13 +22347,28 @@ function resolveShowNotesRenderOptions(options) {
22310
22347
  const briefModeRaw = options.briefMode ?? process.env.PODCAST_PUB_BRIEF_MODE;
22311
22348
  const briefMode = briefModeRaw === true || briefModeRaw === "true" || briefModeRaw === "1";
22312
22349
  const articlePostPath = resolveOptionString(options.articlePostPath, "PODCAST_PUB_ARTICLE_POST_PATH");
22350
+ let audioSegments;
22351
+ const audioContentPath = resolveOptionString(undefined, "PODCAST_PUB_AUDIO_CONTENT");
22352
+ if (audioContentPath !== undefined) {
22353
+ try {
22354
+ const parsed = JSON.parse(readFileSync2(audioContentPath, "utf-8"));
22355
+ const meta3 = parsed?.metadata;
22356
+ if (Array.isArray(meta3?.segments)) {
22357
+ audioSegments = meta3.segments.map((s) => ({
22358
+ duration: typeof s.duration === "number" ? s.duration : undefined,
22359
+ transcription: typeof s.transcription === "string" ? s.transcription : undefined
22360
+ }));
22361
+ }
22362
+ } catch {}
22363
+ }
22313
22364
  return {
22314
22365
  renderOptions: {
22315
22366
  title,
22316
22367
  runDate,
22317
22368
  ...articleBaseUrl !== undefined ? { articleBaseUrl } : {},
22318
22369
  ...briefMode ? { briefMode: true } : {},
22319
- ...articlePostPath !== undefined ? { articlePostPath } : {}
22370
+ ...articlePostPath !== undefined ? { articlePostPath } : {},
22371
+ ...audioSegments !== undefined ? { audioSegments } : {}
22320
22372
  }
22321
22373
  };
22322
22374
  }
@@ -22410,10 +22462,16 @@ async function publishEpisode(client, payload, options) {
22410
22462
  attachments: [audioAttachment(episode.audio, episode.audio.url, undefined, episode.durationSeconds)],
22411
22463
  ...episodeExtras(episode)
22412
22464
  }, idempotencyKey);
22465
+ let hostedItem;
22466
+ try {
22467
+ hostedItem = (await client.getItem(itemId2)).items?.[0];
22468
+ } catch {
22469
+ hostedItem = undefined;
22470
+ }
22413
22471
  return ResultSchema.parse({
22414
22472
  ok: true,
22415
22473
  target: TARGET,
22416
- url: client.itemUrl(itemId2),
22474
+ url: publicItemUrl(hostedItem, itemId2, client),
22417
22475
  id: itemId2,
22418
22476
  message: "Published successfully to microfeed (hosted audio, single-call create)"
22419
22477
  });
@@ -22455,9 +22513,10 @@ async function publishEpisode(client, payload, options) {
22455
22513
  return ResultSchema.parse({
22456
22514
  ok: true,
22457
22515
  target: TARGET,
22458
- url: typeof item.url === "string" && item.url.length > 0 ? item.url : client.itemUrl(itemId),
22516
+ url: publicItemUrl(item, itemId, client),
22459
22517
  id: itemId,
22460
- message: "Published successfully to microfeed (two-phase upload + publish)"
22518
+ message: "Published successfully to microfeed (two-phase upload + publish)",
22519
+ metadata: { itemId, imageUrl: imageUrl ?? null }
22461
22520
  });
22462
22521
  }
22463
22522
 
@@ -22643,6 +22702,7 @@ if (import.meta.main) {
22643
22702
  process.exit(await main2());
22644
22703
  }
22645
22704
  export {
22705
+ publicItemUrl,
22646
22706
  processPublishIO,
22647
22707
  main2 as main,
22648
22708
  getPublishTransport,
@@ -1,3 +1,4 @@
1
+ import { readFileSync } from 'node:fs';
1
2
  import { readFile } from 'node:fs/promises';
2
3
  import { dirname } from 'node:path';
3
4
  import { parseArgs } from 'node:util';
@@ -6,10 +7,23 @@ import { FakeFileCliTransport, type PublishTransport, type PublishTransportPaylo
6
7
  import { createNodeFileSystem } from '@gobing-ai/ts-runtime';
7
8
  import { echoError } from '@gobing-ai/ts-utils';
8
9
  import { type EpisodeMapping, mapContentToEpisode } from './map';
9
- import { MicrofeedClient, type MicrofeedItemInput, resolveTimeoutMs } from './microfeed-client';
10
+ import { MicrofeedClient, type MicrofeedItem, type MicrofeedItemInput, resolveTimeoutMs } from './microfeed-client';
10
11
  import { renderShowNotesMarkdown, type ShowNotesRenderOptions } from './show-notes';
11
12
 
12
13
  /** Result target name for this plugin. */
14
+ /**
15
+ * Canonical public page URL from a microfeed item: `_microfeed.web_url` (`/i/{slug}-{id}/` — the
16
+ * deployment's real page shape) wins, then the JSON Feed `url`, then the client's `/items/{id}`
17
+ * fallback (which 404s on some deployments but is the only shape derivable from the id alone).
18
+ */
19
+ export function publicItemUrl(item: MicrofeedItem | undefined, itemId: string, client: MicrofeedClient): string {
20
+ const microfeed = item?._microfeed as Record<string, unknown> | undefined;
21
+ const webUrl = typeof microfeed?.web_url === 'string' ? microfeed.web_url : '';
22
+ if (webUrl !== '') return webUrl;
23
+ const feedUrl = typeof item?.url === 'string' ? item.url : '';
24
+ return feedUrl !== '' ? feedUrl : client.itemUrl(itemId);
25
+ }
26
+
13
27
  const TARGET = 'podcast-pub';
14
28
 
15
29
  /** Management operations routed via `options.operation` (design D5). */
@@ -113,6 +127,24 @@ function resolveShowNotesRenderOptions(
113
127
  const briefModeRaw = options.briefMode ?? process.env.PODCAST_PUB_BRIEF_MODE;
114
128
  const briefMode = briefModeRaw === true || briefModeRaw === 'true' || briefModeRaw === '1';
115
129
  const articlePostPath = resolveOptionString(options.articlePostPath, 'PODCAST_PUB_ARTICLE_POST_PATH');
130
+ // Audio segment metadata for real timeline offsets: the generated episode content's
131
+ // `metadata.segments[]` (duration + ASR transcription). Absent ⇒ `--:--` offsets.
132
+ let audioSegments: ShowNotesRenderOptions['audioSegments'];
133
+ const audioContentPath = resolveOptionString(undefined, 'PODCAST_PUB_AUDIO_CONTENT');
134
+ if (audioContentPath !== undefined) {
135
+ try {
136
+ const parsed: unknown = JSON.parse(readFileSync(audioContentPath, 'utf-8'));
137
+ const meta = (parsed as { metadata?: unknown })?.metadata as Record<string, unknown> | undefined;
138
+ if (Array.isArray(meta?.segments)) {
139
+ audioSegments = (meta.segments as Array<Record<string, unknown>>).map((s) => ({
140
+ duration: typeof s.duration === 'number' ? s.duration : undefined,
141
+ transcription: typeof s.transcription === 'string' ? s.transcription : undefined,
142
+ }));
143
+ }
144
+ } catch {
145
+ // Timeline offsets degrade to proportional — never block show-notes on audio metadata.
146
+ }
147
+ }
116
148
  return {
117
149
  renderOptions: {
118
150
  title,
@@ -120,6 +152,7 @@ function resolveShowNotesRenderOptions(
120
152
  ...(articleBaseUrl !== undefined ? { articleBaseUrl } : {}),
121
153
  ...(briefMode ? { briefMode: true } : {}),
122
154
  ...(articlePostPath !== undefined ? { articlePostPath } : {}),
155
+ ...(audioSegments !== undefined ? { audioSegments } : {}),
123
156
  },
124
157
  };
125
158
  }
@@ -262,10 +295,18 @@ async function publishEpisode(
262
295
  },
263
296
  idempotencyKey,
264
297
  );
298
+ // Best-effort canonical-URL enrichment: the episode is already published, so a failed
299
+ // follow-up GET (or an unparsable response) falls back to publicItemUrl's own chain.
300
+ let hostedItem: MicrofeedItem | undefined;
301
+ try {
302
+ hostedItem = (await client.getItem(itemId)).items?.[0];
303
+ } catch {
304
+ hostedItem = undefined;
305
+ }
265
306
  return ResultSchema.parse({
266
307
  ok: true,
267
308
  target: TARGET,
268
- url: client.itemUrl(itemId),
309
+ url: publicItemUrl(hostedItem, itemId, client),
269
310
  id: itemId,
270
311
  message: 'Published successfully to microfeed (hosted audio, single-call create)',
271
312
  });
@@ -319,9 +360,12 @@ async function publishEpisode(
319
360
  return ResultSchema.parse({
320
361
  ok: true,
321
362
  target: TARGET,
322
- url: typeof item.url === 'string' && item.url.length > 0 ? item.url : client.itemUrl(itemId),
363
+ url: publicItemUrl(item, itemId, client),
323
364
  id: itemId,
324
365
  message: 'Published successfully to microfeed (two-phase upload + publish)',
366
+ // 20260917 review #6: downstream surfdash patch reuses the podcast-hosted cover URL
367
+ // (no re-upload); null when the episode carried no local cover.
368
+ metadata: { itemId, imageUrl: imageUrl ?? null },
325
369
  });
326
370
  }
327
371