@gobing-ai/knowledge-kit 0.0.11 → 0.0.12

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 (42) hide show
  1. package/dist/index.js +363 -138
  2. package/package.json +1 -1
  3. package/plugins/generations/content-gen/src/storm.ts +99 -50
  4. package/plugins/generations/core-facts-gen/package.json +17 -0
  5. package/plugins/generations/core-facts-gen/plugin.json +7 -0
  6. package/plugins/generations/core-facts-gen/src/index.ts +116 -0
  7. package/plugins/generations/core-facts-gen/tsconfig.json +4 -0
  8. package/plugins/generations/daily-article-gen/package.json +17 -0
  9. package/plugins/generations/daily-article-gen/plugin.json +7 -0
  10. package/plugins/generations/daily-article-gen/src/index.ts +91 -0
  11. package/plugins/generations/daily-article-gen/tsconfig.json +4 -0
  12. package/plugins/generations/dailynews-gen/src/index.ts +11 -0
  13. package/plugins/generations/dailynews-gen/src/script-builder.ts +1 -1
  14. package/plugins/generations/episode-plan-gen/package.json +17 -0
  15. package/plugins/generations/episode-plan-gen/plugin.json +7 -0
  16. package/plugins/generations/episode-plan-gen/src/index.ts +726 -0
  17. package/plugins/generations/episode-plan-gen/tsconfig.json +4 -0
  18. package/plugins/generations/voice-gen/src/index.ts +102 -11
  19. package/plugins/generations/voice-gen/src/qc.ts +154 -9
  20. package/plugins/ingestions/aihot-ingest/plugin.json +1 -1
  21. package/plugins/ingestions/aihot-ingest/src/index.ts +72 -13
  22. package/plugins/ingestions/aihot-ingest/src/mapper.ts +1 -0
  23. package/plugins/ingestions/aihot-ingest/src/rss.ts +151 -0
  24. package/plugins/ingestions/horizon-ingest/package.json +17 -0
  25. package/plugins/ingestions/horizon-ingest/plugin.json +7 -0
  26. package/plugins/ingestions/horizon-ingest/src/index.ts +205 -0
  27. package/plugins/ingestions/horizon-ingest/tsconfig.json +4 -0
  28. package/plugins/ingestions/last30days-ingest/package.json +17 -0
  29. package/plugins/ingestions/last30days-ingest/plugin.json +7 -0
  30. package/plugins/ingestions/last30days-ingest/src/index.ts +148 -0
  31. package/plugins/ingestions/last30days-ingest/tsconfig.json +4 -0
  32. package/plugins/kk/skills/taste-unslop/SKILL.md +12 -6
  33. package/plugins/kk/skills/taste-unslop/references/pattern-guide.md +128 -48
  34. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +432 -19
  35. package/plugins/publishings/podcast-pub/package.json +17 -0
  36. package/plugins/publishings/podcast-pub/plugin.json +7 -0
  37. package/plugins/publishings/podcast-pub/src/index.ts +538 -0
  38. package/plugins/publishings/podcast-pub/src/map.ts +165 -0
  39. package/plugins/publishings/podcast-pub/src/microfeed-client.ts +196 -0
  40. package/plugins/publishings/podcast-pub/src/show-notes.ts +132 -0
  41. package/plugins/publishings/podcast-pub/tsconfig.json +4 -0
  42. package/plugins/publishings/surfdash-pub/src/index.ts +328 -62
package/dist/index.js CHANGED
@@ -26016,7 +26016,8 @@ function useColor() {
26016
26016
  var program = new Command;
26017
26017
 
26018
26018
  // src/fanin.ts
26019
- import { resolve as resolve2 } from "path";
26019
+ import { tmpdir } from "os";
26020
+ import { isAbsolute as isAbsolute2, join as join3, resolve as resolve3 } from "path";
26020
26021
 
26021
26022
  // ../../packages/kk-core/src/kinds.ts
26022
26023
  init_zod();
@@ -26080,7 +26081,7 @@ function kindForCollection(name) {
26080
26081
  var CONTRACTS = {
26081
26082
  ingestion: { kind: "ingestion", input: null, output: DocListSchema },
26082
26083
  generator: { kind: "generator", input: DocListSchema, output: ContentSchema },
26083
- publish: { kind: "publish", input: ContentSchema, output: ResultSchema }
26084
+ publish: { kind: "publish", input: exports_external.union([ContentSchema, DocListSchema]), output: ResultSchema }
26084
26085
  };
26085
26086
  function kindContract(kind) {
26086
26087
  if (!isPluginKind(kind)) {
@@ -26365,134 +26366,11 @@ init_config();
26365
26366
  if (false) {}
26366
26367
  // src/fanin.ts
26367
26368
  init_dist();
26368
- var EXIT_USAGE = 2;
26369
- var EXIT_INPUT = 1;
26370
- function mergeDocLists(inputs, sources) {
26371
- const merged = [];
26372
- const byId = new Map;
26373
- for (const [i, docList] of inputs.entries()) {
26374
- const file2 = sources[i] ?? `input[${i}]`;
26375
- for (const doc2 of docList) {
26376
- const entry = { file: file2 };
26377
- if (doc2.sourceUri !== undefined) {
26378
- entry.sourceUri = doc2.sourceUri;
26379
- }
26380
- const existing = byId.get(doc2.id);
26381
- if (existing) {
26382
- const list = existing.metadata?.sources;
26383
- if (Array.isArray(list)) {
26384
- list.push(entry);
26385
- }
26386
- continue;
26387
- }
26388
- const mergedDoc = {
26389
- ...doc2,
26390
- metadata: { ...doc2.metadata ?? {}, sources: [entry] }
26391
- };
26392
- byId.set(doc2.id, mergedDoc);
26393
- merged.push(mergedDoc);
26394
- }
26395
- }
26396
- return merged;
26397
- }
26398
- async function readDocList(fs2, path3) {
26399
- if (!await fs2.exists(path3)) {
26400
- echoError(`Input file does not exist: ${path3}`);
26401
- return null;
26402
- }
26403
- let content;
26404
- try {
26405
- content = await fs2.readFile(path3);
26406
- } catch (err) {
26407
- echoError(`Failed to read input file ${path3}: ${err instanceof Error ? err.message : String(err)}`);
26408
- return null;
26409
- }
26410
- let json2;
26411
- try {
26412
- json2 = JSON.parse(content);
26413
- } catch (err) {
26414
- echoError(`Input file ${path3} is invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
26415
- return null;
26416
- }
26417
- const result = DocListSchema.safeParse(json2);
26418
- if (!result.success) {
26419
- const issues = result.error.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join("; ");
26420
- echoError(`Input file ${path3} violates the Doc list schema: ${issues}`);
26421
- return null;
26422
- }
26423
- return result.data;
26424
- }
26425
- function parseArgs(argv) {
26426
- const inPaths = [];
26427
- let outPath;
26428
- for (let i = 0;i < argv.length; i++) {
26429
- const arg = argv[i];
26430
- if (arg === "--in") {
26431
- const value = argv[++i];
26432
- if (value === undefined) {
26433
- throw new Error("--in requires a file path");
26434
- }
26435
- inPaths.push(value);
26436
- } else if (arg === "--out") {
26437
- const value = argv[++i];
26438
- if (value === undefined) {
26439
- throw new Error("--out requires a file path");
26440
- }
26441
- if (outPath !== undefined) {
26442
- throw new Error("--out may be specified only once");
26443
- }
26444
- outPath = value;
26445
- } else {
26446
- throw new Error(`Unknown argument: ${arg}`);
26447
- }
26448
- }
26449
- if (inPaths.length === 0) {
26450
- throw new Error("at least one --in file is required");
26451
- }
26452
- if (outPath === undefined) {
26453
- throw new Error("--out file is required");
26454
- }
26455
- return { inPaths, outPath };
26456
- }
26457
- async function runFanin(argv) {
26458
- let opts;
26459
- try {
26460
- opts = parseArgs(argv);
26461
- } catch (err) {
26462
- echoError(`fanin: ${err instanceof Error ? err.message : String(err)}`);
26463
- return EXIT_USAGE;
26464
- }
26465
- const fs2 = createNodeFileSystem();
26466
- const inputs = [];
26467
- const sources = [];
26468
- for (const inPath of opts.inPaths) {
26469
- const docList = await readDocList(fs2, inPath);
26470
- if (docList === null) {
26471
- return EXIT_INPUT;
26472
- }
26473
- inputs.push(docList);
26474
- sources.push(inPath);
26475
- }
26476
- const merged = mergeDocLists(inputs, sources);
26477
- try {
26478
- await fs2.writeFile(resolve2(opts.outPath), `${JSON.stringify(merged, null, 2)}
26479
- `);
26480
- } catch (err) {
26481
- echoError(`Failed to write output file ${resolve2(opts.outPath)}: ${err instanceof Error ? err.message : String(err)}`);
26482
- return EXIT_INPUT;
26483
- }
26484
- return 0;
26485
- }
26486
-
26487
- // src/fanout.ts
26488
- import { tmpdir } from "os";
26489
- import { isAbsolute as isAbsolute2, join as join3, resolve as resolve4 } from "path";
26490
- init_dist();
26491
26369
 
26492
26370
  // src/invoke.ts
26493
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
26371
+ import { existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
26494
26372
  import { homedir as homedir2 } from "os";
26495
- import { isAbsolute, join as join2, resolve as resolve3 } from "path";
26373
+ import { isAbsolute, join as join2, resolve as resolve2 } from "path";
26496
26374
  init_dist();
26497
26375
  function resolveKkConfigPath() {
26498
26376
  return process.env.KK_CONFIG || join2(homedir2(), ".config/kk/config.yaml");
@@ -26546,12 +26424,21 @@ function pluginEnvFromConfig(kind, name, configFile = resolveKkConfigPath()) {
26546
26424
  }
26547
26425
  return out;
26548
26426
  }
26549
- function spawnPluginEntry(opts) {
26550
- const cmd = opts.entryPath.endsWith(".ts") || opts.entryPath.endsWith(".js") ? [process.execPath, opts.entryPath, "--in", opts.inPath, "--out", opts.outPath] : [opts.entryPath, "--in", opts.inPath, "--out", opts.outPath];
26427
+ function pluginEntryCmd(opts) {
26428
+ return opts.entryPath.endsWith(".ts") || opts.entryPath.endsWith(".js") ? [process.execPath, opts.entryPath, "--in", opts.inPath, "--out", opts.outPath] : [opts.entryPath, "--in", opts.inPath, "--out", opts.outPath];
26429
+ }
26430
+ function pluginEntryEnv(opts) {
26551
26431
  const fromConfig = opts.pluginKind !== undefined && opts.pluginName !== undefined ? pluginEnvFromConfig(opts.pluginKind, opts.pluginName) : {};
26552
- const proc = Bun.spawnSync(cmd, {
26432
+ return {
26433
+ ...fromConfig,
26434
+ ...process.env,
26435
+ KNOWLEDGE_KIT_PROJECT_ROOT: process.env.KNOWLEDGE_KIT_PROJECT_ROOT ?? process.cwd()
26436
+ };
26437
+ }
26438
+ function spawnPluginEntry(opts) {
26439
+ const proc = Bun.spawnSync(pluginEntryCmd(opts), {
26553
26440
  cwd: opts.cwd,
26554
- env: { ...fromConfig, ...process.env },
26441
+ env: pluginEntryEnv(opts),
26555
26442
  stdout: "pipe",
26556
26443
  stderr: "pipe"
26557
26444
  });
@@ -26561,6 +26448,28 @@ function spawnPluginEntry(opts) {
26561
26448
  stderr: proc.stderr.toString().trim()
26562
26449
  };
26563
26450
  }
26451
+ async function spawnPluginEntryAsync(opts) {
26452
+ const signal = opts.timeoutMs === undefined ? undefined : AbortSignal.timeout(opts.timeoutMs);
26453
+ let timedOut = false;
26454
+ signal?.addEventListener("abort", () => {
26455
+ timedOut = true;
26456
+ });
26457
+ const proc = Bun.spawn(pluginEntryCmd(opts), {
26458
+ cwd: opts.cwd,
26459
+ env: pluginEntryEnv(opts),
26460
+ stdout: "pipe",
26461
+ stderr: "pipe",
26462
+ signal
26463
+ });
26464
+ const [stdout, stderr] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
26465
+ await proc.exited;
26466
+ return {
26467
+ exitCode: proc.exitCode === null ? 1 : proc.exitCode,
26468
+ stdout: stdout.trim(),
26469
+ stderr: stderr.trim(),
26470
+ ...timedOut ? { timedOut: true } : {}
26471
+ };
26472
+ }
26564
26473
  async function runInvoke(opts) {
26565
26474
  const discovery = discoverAllPlugins({ pluginsRoot: opts.pluginsRoot, extraPaths: opts.pluginsPath });
26566
26475
  const record2 = getByName(discovery, opts.plugin);
@@ -26574,8 +26483,9 @@ async function runInvoke(opts) {
26574
26483
  return 1;
26575
26484
  }
26576
26485
  const contract = kindContract(record2.kind);
26577
- const absInPath = resolve3(opts.inPath);
26578
- const absOutPath = resolve3(opts.outPath);
26486
+ const absInPath = resolve2(opts.inPath);
26487
+ const absOutPath = resolve2(opts.outPath);
26488
+ let showNotesArrayInput = false;
26579
26489
  if (contract.input !== null) {
26580
26490
  if (!existsSync3(absInPath)) {
26581
26491
  echoError(`Input file does not exist: ${absInPath}`);
@@ -26601,13 +26511,14 @@ async function runInvoke(opts) {
26601
26511
  echoError(`Input schema violation for kind '${record2.kind}': ${issues}`);
26602
26512
  return 1;
26603
26513
  }
26514
+ showNotesArrayInput = record2.kind === "publish" && Array.isArray(json2);
26604
26515
  } else {
26605
26516
  if (!existsSync3(absInPath)) {
26606
26517
  echoError(`Source path does not exist: ${absInPath}`);
26607
26518
  return 1;
26608
26519
  }
26609
26520
  }
26610
- const entryPath = isAbsolute(record2.entry) ? record2.entry : resolve3(record2.dir, record2.entry);
26521
+ const entryPath = isAbsolute(record2.entry) ? record2.entry : resolve2(record2.dir, record2.entry);
26611
26522
  if (!existsSync3(entryPath)) {
26612
26523
  echoError(`Plugin entry script does not exist: ${entryPath}`);
26613
26524
  return 1;
@@ -26630,6 +26541,13 @@ async function runInvoke(opts) {
26630
26541
  echoError(`Plugin entry succeeded but did not write output file: ${absOutPath}`);
26631
26542
  return 1;
26632
26543
  }
26544
+ if (showNotesArrayInput) {
26545
+ if (statSync3(absOutPath).size === 0) {
26546
+ echoError(`Plugin entry succeeded but wrote an empty output file: ${absOutPath}`);
26547
+ return 1;
26548
+ }
26549
+ return 0;
26550
+ }
26633
26551
  let outContent;
26634
26552
  try {
26635
26553
  outContent = readFileSync3(absOutPath, "utf-8");
@@ -26653,7 +26571,296 @@ async function runInvoke(opts) {
26653
26571
  return 0;
26654
26572
  }
26655
26573
 
26574
+ // src/fanin.ts
26575
+ var DEFAULT_SOURCE_TIMEOUT_MS = 30 * 60 * 1000;
26576
+ var SOURCE_ERROR_MAX_CHARS = 2048;
26577
+ function capSourceError(error51) {
26578
+ if (error51.length <= SOURCE_ERROR_MAX_CHARS) {
26579
+ return error51;
26580
+ }
26581
+ return `${error51.slice(0, SOURCE_ERROR_MAX_CHARS)}... [truncated; ${error51.length} chars total]`;
26582
+ }
26583
+ var EXIT_USAGE = 2;
26584
+ var EXIT_INPUT = 1;
26585
+ function mergeDocLists(inputs, sources) {
26586
+ const merged = [];
26587
+ const byId = new Map;
26588
+ for (const [i, docList] of inputs.entries()) {
26589
+ const file2 = sources[i] ?? `input[${i}]`;
26590
+ for (const doc2 of docList) {
26591
+ const entry = { file: file2 };
26592
+ if (doc2.sourceUri !== undefined) {
26593
+ entry.sourceUri = doc2.sourceUri;
26594
+ }
26595
+ const existing = byId.get(doc2.id);
26596
+ if (existing) {
26597
+ const list = existing.metadata?.sources;
26598
+ if (Array.isArray(list)) {
26599
+ list.push(entry);
26600
+ }
26601
+ continue;
26602
+ }
26603
+ const mergedDoc = {
26604
+ ...doc2,
26605
+ metadata: { ...doc2.metadata ?? {}, sources: [entry] }
26606
+ };
26607
+ byId.set(doc2.id, mergedDoc);
26608
+ merged.push(mergedDoc);
26609
+ }
26610
+ }
26611
+ return merged;
26612
+ }
26613
+ async function readDocList(fs2, path3) {
26614
+ if (!await fs2.exists(path3)) {
26615
+ echoError(`Input file does not exist: ${path3}`);
26616
+ return null;
26617
+ }
26618
+ let content;
26619
+ try {
26620
+ content = await fs2.readFile(path3);
26621
+ } catch (err) {
26622
+ echoError(`Failed to read input file ${path3}: ${err instanceof Error ? err.message : String(err)}`);
26623
+ return null;
26624
+ }
26625
+ let json2;
26626
+ try {
26627
+ json2 = JSON.parse(content);
26628
+ } catch (err) {
26629
+ echoError(`Input file ${path3} is invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
26630
+ return null;
26631
+ }
26632
+ const result = DocListSchema.safeParse(json2);
26633
+ if (!result.success) {
26634
+ const issues = result.error.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join("; ");
26635
+ echoError(`Input file ${path3} violates the Doc list schema: ${issues}`);
26636
+ return null;
26637
+ }
26638
+ return result.data;
26639
+ }
26640
+ function parseArgs(argv) {
26641
+ const inPaths = [];
26642
+ const sources = [];
26643
+ let outPath;
26644
+ let sourceTimeoutMs;
26645
+ const sourceTimeoutOverrides = {};
26646
+ for (let i = 0;i < argv.length; i++) {
26647
+ const arg = argv[i];
26648
+ if (arg === "--in") {
26649
+ const value = argv[++i];
26650
+ if (value === undefined) {
26651
+ throw new Error("--in requires a file path");
26652
+ }
26653
+ inPaths.push(value);
26654
+ } else if (arg === "--source") {
26655
+ const value = argv[++i];
26656
+ if (value === undefined) {
26657
+ throw new Error("--source requires <plugin>:<inputFile>");
26658
+ }
26659
+ const sep = value.indexOf(":");
26660
+ if (sep <= 0 || sep === value.length - 1) {
26661
+ throw new Error(`--source requires <plugin>:<inputFile>, got '${value}'`);
26662
+ }
26663
+ sources.push({ plugin: value.slice(0, sep), inputFile: value.slice(sep + 1) });
26664
+ } else if (arg === "--source-timeout") {
26665
+ const value = argv[++i];
26666
+ if (value === undefined) {
26667
+ throw new Error("--source-timeout requires a millisecond value");
26668
+ }
26669
+ if (sourceTimeoutMs !== undefined) {
26670
+ throw new Error("--source-timeout may be specified only once");
26671
+ }
26672
+ const parsed = Number(value);
26673
+ if (!Number.isInteger(parsed) || parsed <= 0) {
26674
+ throw new Error(`--source-timeout must be a positive integer (ms), got '${value}'`);
26675
+ }
26676
+ sourceTimeoutMs = parsed;
26677
+ } else if (arg === "--source-timeout-override") {
26678
+ const value = argv[++i];
26679
+ if (value === undefined) {
26680
+ throw new Error("--source-timeout-override requires <plugin>:<ms>");
26681
+ }
26682
+ const sep = value.indexOf(":");
26683
+ if (sep <= 0 || sep === value.length - 1) {
26684
+ throw new Error(`--source-timeout-override requires <plugin>:<ms>, got '${value}'`);
26685
+ }
26686
+ const plugin = value.slice(0, sep);
26687
+ if (plugin in sourceTimeoutOverrides) {
26688
+ throw new Error(`--source-timeout-override: plugin '${plugin}' may be specified only once`);
26689
+ }
26690
+ const parsed = Number(value.slice(sep + 1));
26691
+ if (!Number.isInteger(parsed) || parsed <= 0) {
26692
+ throw new Error(`--source-timeout-override ms must be a positive integer, got '${value.slice(sep + 1)}'`);
26693
+ }
26694
+ sourceTimeoutOverrides[plugin] = parsed;
26695
+ } else if (arg === "--out") {
26696
+ const value = argv[++i];
26697
+ if (value === undefined) {
26698
+ throw new Error("--out requires a file path");
26699
+ }
26700
+ if (outPath !== undefined) {
26701
+ throw new Error("--out may be specified only once");
26702
+ }
26703
+ outPath = value;
26704
+ } else {
26705
+ throw new Error(`Unknown argument: ${arg}`);
26706
+ }
26707
+ }
26708
+ if (inPaths.length === 0 && sources.length === 0) {
26709
+ throw new Error("at least one --in file or --source is required");
26710
+ }
26711
+ if (sourceTimeoutMs !== undefined && sources.length === 0) {
26712
+ throw new Error("--source-timeout requires at least one --source");
26713
+ }
26714
+ if (Object.keys(sourceTimeoutOverrides).length > 0 && sources.length === 0) {
26715
+ throw new Error("--source-timeout-override requires at least one --source");
26716
+ }
26717
+ for (const plugin of Object.keys(sourceTimeoutOverrides)) {
26718
+ if (!sources.some((candidate) => candidate.plugin === plugin)) {
26719
+ throw new Error(`--source-timeout-override names plugin '${plugin}' which is not among the --source plugins`);
26720
+ }
26721
+ }
26722
+ if (outPath === undefined) {
26723
+ throw new Error("--out file is required");
26724
+ }
26725
+ return {
26726
+ inPaths,
26727
+ outPath,
26728
+ ...sources.length > 0 ? { sources } : {},
26729
+ ...sourceTimeoutMs !== undefined ? { sourceTimeoutMs } : {},
26730
+ ...Object.keys(sourceTimeoutOverrides).length > 0 ? { sourceTimeoutOverrides } : {}
26731
+ };
26732
+ }
26733
+ async function collectSourceDocs(discovery, source, timeoutMs) {
26734
+ const record2 = getByName(discovery, source.plugin);
26735
+ if (!record2) {
26736
+ throw new Error(`Plugin '${source.plugin}' not found in the merged plugin registry`);
26737
+ }
26738
+ if (record2.kind !== "ingestion") {
26739
+ throw new Error(`Plugin '${source.plugin}' is kind '${record2.kind}', not 'ingestion'`);
26740
+ }
26741
+ const fs2 = createNodeFileSystem();
26742
+ const absInPath = resolve3(source.inputFile);
26743
+ if (!await fs2.exists(absInPath)) {
26744
+ throw new Error(`Input file does not exist: ${absInPath}`);
26745
+ }
26746
+ const entryPath = isAbsolute2(record2.entry) ? record2.entry : resolve3(record2.dir, record2.entry);
26747
+ if (!await fs2.exists(entryPath)) {
26748
+ throw new Error(`Plugin entry script does not exist: ${entryPath}`);
26749
+ }
26750
+ const outTmp = join3(tmpdir(), `kk-fanin-${source.plugin}-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
26751
+ try {
26752
+ const proc = await spawnPluginEntryAsync({
26753
+ entryPath,
26754
+ cwd: record2.dir,
26755
+ inPath: absInPath,
26756
+ outPath: outTmp,
26757
+ pluginKind: record2.kind,
26758
+ pluginName: record2.name,
26759
+ timeoutMs
26760
+ });
26761
+ if (proc.exitCode !== 0) {
26762
+ const why = proc.timedOut === true ? `timed out after ${timeoutMs}ms` : `exited with code ${proc.exitCode}`;
26763
+ throw new Error(`Plugin '${source.plugin}' ${why}: ${proc.stderr || proc.stdout || "no output"}`);
26764
+ }
26765
+ if (!await fs2.exists(outTmp)) {
26766
+ throw new Error(`Plugin '${source.plugin}' succeeded but did not write an output file`);
26767
+ }
26768
+ let json2;
26769
+ try {
26770
+ json2 = JSON.parse(await fs2.readFile(outTmp));
26771
+ } catch (err) {
26772
+ throw new Error(`Plugin '${source.plugin}' wrote invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
26773
+ }
26774
+ const parsed = DocListSchema.safeParse(json2);
26775
+ if (!parsed.success) {
26776
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join("; ");
26777
+ throw new Error(`Plugin '${source.plugin}' output violates the Doc list schema: ${issues}`);
26778
+ }
26779
+ return parsed.data;
26780
+ } finally {
26781
+ await fs2.deleteFile(outTmp);
26782
+ }
26783
+ }
26784
+ async function runFanin(argv) {
26785
+ let opts;
26786
+ try {
26787
+ opts = parseArgs(argv);
26788
+ } catch (err) {
26789
+ echoError(`fanin: ${err instanceof Error ? err.message : String(err)}`);
26790
+ return EXIT_USAGE;
26791
+ }
26792
+ const fs2 = createNodeFileSystem();
26793
+ const inputs = [];
26794
+ const sources = [];
26795
+ const sourceErrors = [];
26796
+ for (const inPath of opts.inPaths) {
26797
+ const docList = await readDocList(fs2, inPath);
26798
+ if (docList === null) {
26799
+ return EXIT_INPUT;
26800
+ }
26801
+ inputs.push(docList);
26802
+ sources.push(inPath);
26803
+ }
26804
+ if (opts.sources !== undefined && opts.sources.length > 0) {
26805
+ const discovery = discoverAllPlugins({});
26806
+ const settled = await Promise.allSettled(opts.sources.map(async (source) => {
26807
+ const started = Date.now();
26808
+ try {
26809
+ const timeoutMs = opts.sourceTimeoutOverrides?.[source.plugin] ?? opts.sourceTimeoutMs ?? DEFAULT_SOURCE_TIMEOUT_MS;
26810
+ return { ok: true, docs: await collectSourceDocs(discovery, source, timeoutMs) };
26811
+ } catch (err) {
26812
+ return {
26813
+ ok: false,
26814
+ error: err instanceof Error ? err.message : String(err),
26815
+ durationMs: Date.now() - started
26816
+ };
26817
+ }
26818
+ }));
26819
+ for (const [i, outcome] of settled.entries()) {
26820
+ const source = opts.sources[i];
26821
+ if (source === undefined) {
26822
+ continue;
26823
+ }
26824
+ const result = outcome.status === "fulfilled" ? outcome.value : { ok: false, error: String(outcome.reason), durationMs: 0 };
26825
+ if (result.ok) {
26826
+ inputs.push(result.docs);
26827
+ sources.push(source.plugin);
26828
+ } else {
26829
+ sourceErrors.push({
26830
+ plugin: source.plugin,
26831
+ error: capSourceError(result.error),
26832
+ durationMs: result.durationMs
26833
+ });
26834
+ }
26835
+ }
26836
+ for (const err of sourceErrors) {
26837
+ echoError(`fanin: source '${err.plugin}' failed after ${err.durationMs}ms: ${err.error}`);
26838
+ }
26839
+ if (sourceErrors.length === opts.sources.length) {
26840
+ echoError(`fanin: all ${opts.sources.length} source(s) failed; no output written`);
26841
+ return EXIT_INPUT;
26842
+ }
26843
+ }
26844
+ const merged = mergeDocLists(inputs, sources);
26845
+ if (sourceErrors.length > 0) {
26846
+ for (const doc2 of merged) {
26847
+ doc2.metadata = { ...doc2.metadata ?? {}, sourceErrors };
26848
+ }
26849
+ }
26850
+ try {
26851
+ await fs2.writeFile(resolve3(opts.outPath), `${JSON.stringify(merged, null, 2)}
26852
+ `);
26853
+ } catch (err) {
26854
+ echoError(`Failed to write output file ${resolve3(opts.outPath)}: ${err instanceof Error ? err.message : String(err)}`);
26855
+ return EXIT_INPUT;
26856
+ }
26857
+ return 0;
26858
+ }
26859
+
26656
26860
  // src/fanout.ts
26861
+ import { tmpdir as tmpdir2 } from "os";
26862
+ import { isAbsolute as isAbsolute3, join as join4, resolve as resolve4 } from "path";
26863
+ init_dist();
26657
26864
  var EXIT_FAILURE = 1;
26658
26865
  var EXIT_INPUT2 = 2;
26659
26866
  function aggregate(entries) {
@@ -26683,6 +26890,7 @@ async function fanout(content, targets, dispatch) {
26683
26890
  status: "published",
26684
26891
  ...result.url !== undefined ? { url: result.url } : {},
26685
26892
  ...result.id !== undefined ? { id: result.id } : {},
26893
+ ...result.metadata !== undefined ? { metadata: result.metadata } : {},
26686
26894
  durationMs
26687
26895
  } : {
26688
26896
  target,
@@ -26887,11 +27095,11 @@ async function pluginDispatch(target, contentPath, discovery) {
26887
27095
  throw new Error(`Publish plugin '${target}' not found in the merged plugin registry`);
26888
27096
  }
26889
27097
  const fs2 = createNodeFileSystem();
26890
- const entryPath = isAbsolute2(record2.entry) ? record2.entry : resolve4(record2.dir, record2.entry);
27098
+ const entryPath = isAbsolute3(record2.entry) ? record2.entry : resolve4(record2.dir, record2.entry);
26891
27099
  if (!await fs2.exists(entryPath)) {
26892
27100
  throw new Error(`Plugin entry script does not exist: ${entryPath}`);
26893
27101
  }
26894
- const outTmp = join3(tmpdir(), `kk-fanout-${target}-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
27102
+ const outTmp = join4(tmpdir2(), `kk-fanout-${target}-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
26895
27103
  try {
26896
27104
  const proc = spawnPluginEntry({
26897
27105
  entryPath,
@@ -27012,11 +27220,24 @@ function collectIn(value, acc) {
27012
27220
  acc.push(value);
27013
27221
  return acc;
27014
27222
  }
27223
+ function collectSource(value, acc) {
27224
+ acc.push(value);
27225
+ return acc;
27226
+ }
27015
27227
  async function runFaninAction(options) {
27016
27228
  const argv = [];
27017
27229
  for (const inPath of options.in) {
27018
27230
  argv.push("--in", inPath);
27019
27231
  }
27232
+ for (const source of options.source ?? []) {
27233
+ argv.push("--source", source);
27234
+ }
27235
+ if (options.sourceTimeout !== undefined) {
27236
+ argv.push("--source-timeout", options.sourceTimeout);
27237
+ }
27238
+ for (const override of options.sourceTimeoutOverride ?? []) {
27239
+ argv.push("--source-timeout-override", override);
27240
+ }
27020
27241
  argv.push("--out", options.out);
27021
27242
  const code = await runFanin(argv);
27022
27243
  if (code !== 0) {
@@ -27027,6 +27248,10 @@ function collectTarget(value, acc) {
27027
27248
  acc.push(value);
27028
27249
  return acc;
27029
27250
  }
27251
+ function collectOverride(value, acc) {
27252
+ acc.push(value);
27253
+ return acc;
27254
+ }
27030
27255
  async function runFanoutAction(options) {
27031
27256
  const argv = ["--content", options.content, "--out", options.out];
27032
27257
  for (const target of options.target) {
@@ -27049,10 +27274,10 @@ async function runFanoutAction(options) {
27049
27274
  function registerExecutorCommand(program2) {
27050
27275
  const executor = program2.command("executor").description("Run knowledge-kit plugin steps (invoke, fan-in, fan-out)");
27051
27276
  executor.command("run").description("Invoke a plugin with input/output files and contract validation").argument("<plugin>", "Name of the plugin to invoke").requiredOption("--in <path>", "Input file path (or source path for ingestion)").requiredOption("--out <path>", "Output file path").option("--plugins-root <dir>", "Exclusive root containing plugins (isolation/tests only)").option("--plugins-path <dir>", "Extra plugin root to union with defaults (repeatable)", collectPath, []).action(runInvokeAction);
27052
- executor.command("fan-in").alias("fanin").description("Merge N Doc[] JSON inputs into one deduplicated Doc[] output").requiredOption("--in <path>", "Input Doc[] JSON file (repeatable)", collectIn, []).requiredOption("--out <path>", "Output merged Doc[] JSON file path").action(runFaninAction);
27277
+ executor.command("fan-in").alias("fanin").description("Merge N Doc[] JSON inputs into one deduplicated Doc[] output").option("--in <path>", "Input Doc[] JSON file (repeatable)", collectIn, []).option("--source <plugin>:<file>", "Ingestion source to spawn concurrently: plugin name and its input file (repeatable)", collectSource, []).option("--source-timeout <ms>", "Per-source timeout in milliseconds (default 1800000)").option("--source-timeout-override <plugin>:<ms>", "Per-source timeout override for one named --source plugin, e.g. horizon-ingest:2700000 (repeatable; wins over --source-timeout/default)", collectOverride, []).requiredOption("--out <path>", "Output merged Doc[] JSON file path").action(runFaninAction);
27053
27278
  executor.command("fan-out").alias("fanout").description("Publish one Content JSON to N targets, aggregating per-target results").requiredOption("--content <path>", "Source ContentSchema JSON file path").option("--target <plugin>", "Target publish plugin name (repeatable)", collectTarget, []).requiredOption("--out <path>", "Aggregate Result JSON file path").option("--retry-from <path>", "Prior Result JSON file to retry only previously failed targets").option("--plugins-root <dir>", "Exclusive root containing plugins (isolation/tests only)").option("--plugins-path <dir>", "Extra plugin root to union with defaults (repeatable)", collectPath, []).action(runFanoutAction);
27054
27279
  program2.command("invoke", { hidden: true }).description("Alias for `executor run` (deprecated)").argument("<plugin>", "Name of the plugin to invoke").requiredOption("--in <path>", "Input file path (or source path for ingestion)").requiredOption("--out <path>", "Output file path").option("--plugins-root <dir>", "Exclusive root containing plugins (isolation/tests only)").option("--plugins-path <dir>", "Extra plugin root to union with defaults (repeatable)", collectPath, []).action(runInvokeAction);
27055
- program2.command("fanin", { hidden: true }).description("Alias for `executor fan-in` (deprecated)").requiredOption("--in <path>", "Input Doc[] JSON file (repeatable)", collectIn, []).requiredOption("--out <path>", "Output merged Doc[] JSON file path").action(runFaninAction);
27280
+ program2.command("fanin", { hidden: true }).description("Alias for `executor fan-in` (deprecated)").option("--in <path>", "Input Doc[] JSON file (repeatable)", collectIn, []).option("--source <plugin>:<file>", "Ingestion source to spawn concurrently: plugin name and its input file (repeatable)", collectSource, []).option("--source-timeout <ms>", "Per-source timeout in milliseconds (default 1800000)").option("--source-timeout-override <plugin>:<ms>", "Per-source timeout override for one named --source plugin, e.g. horizon-ingest:2700000 (repeatable; wins over --source-timeout/default)", collectOverride, []).requiredOption("--out <path>", "Output merged Doc[] JSON file path").action(runFaninAction);
27056
27281
  program2.command("fanout", { hidden: true }).description("Alias for `executor fan-out` (deprecated)").requiredOption("--content <path>", "Source ContentSchema JSON file path").option("--target <plugin>", "Target publish plugin name (repeatable)", collectTarget, []).requiredOption("--out <path>", "Aggregate Result JSON file path").option("--retry-from <path>", "Prior Result JSON file to retry only previously failed targets").option("--plugins-root <dir>", "Exclusive root containing plugins (isolation/tests only)").option("--plugins-path <dir>", "Extra plugin root to union with defaults (repeatable)", collectPath, []).action(runFanoutAction);
27057
27282
  }
27058
27283
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/knowledge-kit",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "An ingest → create → publish content pipeline CLI (Bun).",
5
5
  "repository": {
6
6
  "type": "git",