@gobing-ai/knowledge-kit 0.0.11 → 0.0.13

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 (99) hide show
  1. package/dist/index.js +451 -152
  2. package/package.json +1 -1
  3. package/plugins/generations/content-gen/dist/index.js +22167 -0
  4. package/plugins/generations/content-gen/plugin.json +1 -1
  5. package/plugins/generations/content-gen/src/storm.ts +99 -50
  6. package/plugins/generations/core-facts-gen/dist/index.js +22048 -0
  7. package/plugins/generations/core-facts-gen/package.json +17 -0
  8. package/plugins/generations/core-facts-gen/plugin.json +7 -0
  9. package/plugins/generations/core-facts-gen/src/index.ts +116 -0
  10. package/plugins/generations/core-facts-gen/tsconfig.json +4 -0
  11. package/plugins/generations/daily-article-gen/dist/index.js +22022 -0
  12. package/plugins/generations/daily-article-gen/package.json +17 -0
  13. package/plugins/generations/daily-article-gen/plugin.json +7 -0
  14. package/plugins/generations/daily-article-gen/src/index.ts +91 -0
  15. package/plugins/generations/daily-article-gen/tsconfig.json +4 -0
  16. package/plugins/generations/dailynews-gen/dist/index.js +22324 -0
  17. package/plugins/generations/dailynews-gen/plugin.json +1 -1
  18. package/plugins/generations/dailynews-gen/src/index.ts +11 -0
  19. package/plugins/generations/dailynews-gen/src/script-builder.ts +1 -1
  20. package/plugins/generations/episode-plan-gen/dist/index.js +22479 -0
  21. package/plugins/generations/episode-plan-gen/package.json +17 -0
  22. package/plugins/generations/episode-plan-gen/plugin.json +7 -0
  23. package/plugins/generations/episode-plan-gen/src/index.ts +726 -0
  24. package/plugins/generations/episode-plan-gen/tsconfig.json +4 -0
  25. package/plugins/generations/image-gen/config.example.yaml +75 -0
  26. package/plugins/generations/image-gen/dist/index.js +22619 -0
  27. package/plugins/generations/image-gen/package.json +17 -0
  28. package/plugins/generations/image-gen/plugin.json +7 -0
  29. package/plugins/generations/image-gen/presets/formats/cover.yaml +57 -0
  30. package/plugins/generations/image-gen/presets/formats/free.yaml +46 -0
  31. package/plugins/generations/image-gen/presets/formats/illustration.yaml +48 -0
  32. package/plugins/generations/image-gen/presets/styles/clean-webapp-ui.yaml +28 -0
  33. package/plugins/generations/image-gen/presets/styles/cute.yaml +3 -0
  34. package/plugins/generations/image-gen/presets/styles/editorial.yaml +3 -0
  35. package/plugins/generations/image-gen/presets/styles/fresh.yaml +3 -0
  36. package/plugins/generations/image-gen/presets/styles/minimalist.yaml +3 -0
  37. package/plugins/generations/image-gen/presets/styles/photorealistic.yaml +3 -0
  38. package/plugins/generations/image-gen/presets/styles/sketch.yaml +3 -0
  39. package/plugins/generations/image-gen/presets/styles/technical-diagram.yaml +3 -0
  40. package/plugins/generations/image-gen/presets/styles/vibrant.yaml +3 -0
  41. package/plugins/generations/image-gen/presets/styles/warm.yaml +3 -0
  42. package/plugins/generations/image-gen/src/bytes.ts +19 -0
  43. package/plugins/generations/image-gen/src/index.ts +319 -0
  44. package/plugins/generations/image-gen/src/job.ts +143 -0
  45. package/plugins/generations/image-gen/src/paths.ts +31 -0
  46. package/plugins/generations/image-gen/src/presets.ts +344 -0
  47. package/plugins/generations/image-gen/src/providers/seedream.ts +128 -0
  48. package/plugins/generations/image-gen/src/providers/types.ts +285 -0
  49. package/plugins/generations/image-gen/tsconfig.json +8 -0
  50. package/plugins/generations/voice-gen/dist/index.js +23027 -0
  51. package/plugins/generations/voice-gen/plugin.json +1 -1
  52. package/plugins/generations/voice-gen/src/index.ts +102 -11
  53. package/plugins/generations/voice-gen/src/qc.ts +154 -9
  54. package/plugins/ingestions/aihot-ingest/dist/index.js +22358 -0
  55. package/plugins/ingestions/aihot-ingest/plugin.json +2 -2
  56. package/plugins/ingestions/aihot-ingest/src/index.ts +72 -13
  57. package/plugins/ingestions/aihot-ingest/src/mapper.ts +1 -0
  58. package/plugins/ingestions/aihot-ingest/src/rss.ts +151 -0
  59. package/plugins/ingestions/horizon-ingest/dist/index.js +22105 -0
  60. package/plugins/ingestions/horizon-ingest/package.json +17 -0
  61. package/plugins/ingestions/horizon-ingest/plugin.json +7 -0
  62. package/plugins/ingestions/horizon-ingest/src/index.ts +205 -0
  63. package/plugins/ingestions/horizon-ingest/tsconfig.json +4 -0
  64. package/plugins/ingestions/karakeep-local/dist/index.js +24204 -0
  65. package/plugins/ingestions/karakeep-local/plugin.json +1 -1
  66. package/plugins/ingestions/last30days-ingest/dist/index.js +22050 -0
  67. package/plugins/ingestions/last30days-ingest/package.json +17 -0
  68. package/plugins/ingestions/last30days-ingest/plugin.json +7 -0
  69. package/plugins/ingestions/last30days-ingest/src/index.ts +148 -0
  70. package/plugins/ingestions/last30days-ingest/tsconfig.json +4 -0
  71. package/plugins/ingestions/web-search/dist/index.js +24379 -0
  72. package/plugins/ingestions/web-search/plugin.json +1 -1
  73. package/plugins/kk/commands/image-generate.md +15 -0
  74. package/plugins/kk/config.example.yaml +80 -0
  75. package/plugins/kk/skills/image-authoring/SKILL.md +250 -0
  76. package/plugins/kk/skills/image-authoring/references/illustration-positions.md +87 -0
  77. package/plugins/kk/skills/image-authoring/references/migrating-from-wt.md +31 -0
  78. package/plugins/kk/skills/image-authoring/references/providers.md +52 -0
  79. package/plugins/kk/skills/image-authoring/references/style-extraction.md +134 -0
  80. package/plugins/kk/skills/taste-unslop/SKILL.md +12 -6
  81. package/plugins/kk/skills/taste-unslop/references/pattern-guide.md +128 -48
  82. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +432 -19
  83. package/plugins/publishings/emdash-pub/dist/index.js +22243 -0
  84. package/plugins/publishings/emdash-pub/plugin.json +1 -1
  85. package/plugins/publishings/podcast-pub/dist/index.js +22596 -0
  86. package/plugins/publishings/podcast-pub/package.json +17 -0
  87. package/plugins/publishings/podcast-pub/plugin.json +13 -0
  88. package/plugins/publishings/podcast-pub/src/index.ts +538 -0
  89. package/plugins/publishings/podcast-pub/src/map.ts +165 -0
  90. package/plugins/publishings/podcast-pub/src/microfeed-client.ts +196 -0
  91. package/plugins/publishings/podcast-pub/src/show-notes.ts +132 -0
  92. package/plugins/publishings/podcast-pub/tsconfig.json +4 -0
  93. package/plugins/publishings/qiita-pub/dist/index.js +22081 -0
  94. package/plugins/publishings/qiita-pub/plugin.json +1 -1
  95. package/plugins/publishings/surfdash-pub/dist/index.js +22233 -0
  96. package/plugins/publishings/surfdash-pub/plugin.json +1 -1
  97. package/plugins/publishings/surfdash-pub/src/index.ts +328 -62
  98. package/plugins/publishings/zenn-pub/dist/index.js +22122 -0
  99. package/plugins/publishings/zenn-pub/plugin.json +1 -1
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();
@@ -26093,12 +26094,14 @@ function isPluginKind(value) {
26093
26094
  }
26094
26095
  // ../../packages/kk-core/src/manifest.ts
26095
26096
  init_zod();
26097
+ var OperationIOSchema = exports_external.enum(["doc-list", "markdown"]);
26096
26098
  var PluginManifestSchema = exports_external.object({
26097
26099
  name: exports_external.string().min(1),
26098
26100
  kind: PluginKindSchema,
26099
26101
  entry: exports_external.string().min(1),
26100
26102
  version: exports_external.string().optional(),
26101
- description: exports_external.string().optional()
26103
+ description: exports_external.string().optional(),
26104
+ operations: exports_external.record(exports_external.string(), exports_external.object({ input: OperationIOSchema, output: OperationIOSchema })).optional()
26102
26105
  }).passthrough();
26103
26106
  // ../../packages/kk-core/src/registry.ts
26104
26107
  import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "fs";
@@ -26152,7 +26155,7 @@ function resolvePluginRoots(opts = {}) {
26152
26155
  if (builtinRoot !== undefined) {
26153
26156
  roots.push({ origin: "builtin", root: builtinRoot });
26154
26157
  }
26155
- const userRoot = join(opts.homeDir ?? homedir(), ".kk", "plugins");
26158
+ const userRoot = join(opts.homeDir ?? homedir(), ".config", "kk", "plugins");
26156
26159
  if (isDirectory(userRoot)) {
26157
26160
  roots.push({ origin: "user", root: userRoot });
26158
26161
  }
@@ -26330,7 +26333,8 @@ function consumeManifest(dirPath, expectedKind, origin, plugins, diagnostics, se
26330
26333
  manifestPath,
26331
26334
  origin,
26332
26335
  version: manifest.version,
26333
- description: manifest.description
26336
+ description: manifest.description,
26337
+ ...manifest.operations !== undefined ? { operations: manifest.operations } : {}
26334
26338
  });
26335
26339
  }
26336
26340
  function listSortedDir(absDir) {
@@ -26365,134 +26369,11 @@ init_config();
26365
26369
  if (false) {}
26366
26370
  // src/fanin.ts
26367
26371
  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
26372
 
26492
26373
  // src/invoke.ts
26493
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
26374
+ import { existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync3, unlinkSync } from "fs";
26494
26375
  import { homedir as homedir2 } from "os";
26495
- import { isAbsolute, join as join2, resolve as resolve3 } from "path";
26376
+ import { isAbsolute, join as join2, resolve as resolve2 } from "path";
26496
26377
  init_dist();
26497
26378
  function resolveKkConfigPath() {
26498
26379
  return process.env.KK_CONFIG || join2(homedir2(), ".config/kk/config.yaml");
@@ -26546,12 +26427,21 @@ function pluginEnvFromConfig(kind, name, configFile = resolveKkConfigPath()) {
26546
26427
  }
26547
26428
  return out;
26548
26429
  }
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];
26430
+ function pluginEntryCmd(opts) {
26431
+ 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];
26432
+ }
26433
+ function pluginEntryEnv(opts) {
26551
26434
  const fromConfig = opts.pluginKind !== undefined && opts.pluginName !== undefined ? pluginEnvFromConfig(opts.pluginKind, opts.pluginName) : {};
26552
- const proc = Bun.spawnSync(cmd, {
26435
+ return {
26436
+ ...fromConfig,
26437
+ ...process.env,
26438
+ KNOWLEDGE_KIT_PROJECT_ROOT: process.env.KNOWLEDGE_KIT_PROJECT_ROOT ?? process.cwd()
26439
+ };
26440
+ }
26441
+ function spawnPluginEntry(opts) {
26442
+ const proc = Bun.spawnSync(pluginEntryCmd(opts), {
26553
26443
  cwd: opts.cwd,
26554
- env: { ...fromConfig, ...process.env },
26444
+ env: pluginEntryEnv(opts),
26555
26445
  stdout: "pipe",
26556
26446
  stderr: "pipe"
26557
26447
  });
@@ -26561,6 +26451,51 @@ function spawnPluginEntry(opts) {
26561
26451
  stderr: proc.stderr.toString().trim()
26562
26452
  };
26563
26453
  }
26454
+ function killProcessTree(pid) {
26455
+ let stdout;
26456
+ try {
26457
+ stdout = Bun.spawnSync(["pgrep", "-P", String(pid)]).stdout.toString();
26458
+ } catch {
26459
+ stdout = "";
26460
+ }
26461
+ for (const line of stdout.trim().split(`
26462
+ `)) {
26463
+ const child = Number(line);
26464
+ if (Number.isInteger(child) && child > 0) {
26465
+ killProcessTree(child);
26466
+ }
26467
+ }
26468
+ try {
26469
+ process.kill(pid, "SIGKILL");
26470
+ } catch {}
26471
+ }
26472
+ async function spawnPluginEntryAsync(opts) {
26473
+ let timedOut = false;
26474
+ let timer;
26475
+ const proc = Bun.spawn(pluginEntryCmd(opts), {
26476
+ cwd: opts.cwd,
26477
+ env: pluginEntryEnv(opts),
26478
+ stdout: "pipe",
26479
+ stderr: "pipe"
26480
+ });
26481
+ if (opts.timeoutMs !== undefined) {
26482
+ timer = setTimeout(() => {
26483
+ timedOut = true;
26484
+ killProcessTree(proc.pid);
26485
+ }, opts.timeoutMs);
26486
+ }
26487
+ const [stdout, stderr] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
26488
+ await proc.exited;
26489
+ if (timer !== undefined) {
26490
+ clearTimeout(timer);
26491
+ }
26492
+ return {
26493
+ exitCode: proc.exitCode === null ? 1 : proc.exitCode,
26494
+ stdout: stdout.trim(),
26495
+ stderr: stderr.trim(),
26496
+ ...timedOut ? { timedOut: true } : {}
26497
+ };
26498
+ }
26564
26499
  async function runInvoke(opts) {
26565
26500
  const discovery = discoverAllPlugins({ pluginsRoot: opts.pluginsRoot, extraPaths: opts.pluginsPath });
26566
26501
  const record2 = getByName(discovery, opts.plugin);
@@ -26574,8 +26509,13 @@ async function runInvoke(opts) {
26574
26509
  return 1;
26575
26510
  }
26576
26511
  const contract = kindContract(record2.kind);
26577
- const absInPath = resolve3(opts.inPath);
26578
- const absOutPath = resolve3(opts.outPath);
26512
+ const absInPath = resolve2(opts.inPath);
26513
+ const absOutPath = resolve2(opts.outPath);
26514
+ if (existsSync3(absInPath) && absInPath === absOutPath) {
26515
+ echoError(`--in and --out alias the same path (${absInPath}); refusing to overwrite the input`);
26516
+ return 1;
26517
+ }
26518
+ let artifactOutput = false;
26579
26519
  if (contract.input !== null) {
26580
26520
  if (!existsSync3(absInPath)) {
26581
26521
  echoError(`Input file does not exist: ${absInPath}`);
@@ -26595,11 +26535,26 @@ async function runInvoke(opts) {
26595
26535
  echoError(`Input file ${absInPath} is invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
26596
26536
  return 1;
26597
26537
  }
26598
- const parseResult = contract.input.safeParse(json2);
26599
- if (!parseResult.success) {
26600
- const issues = parseResult.error.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join("; ");
26601
- echoError(`Input schema violation for kind '${record2.kind}': ${issues}`);
26602
- return 1;
26538
+ if (record2.kind === "publish" && Array.isArray(json2)) {
26539
+ const op = Object.values(record2.operations ?? {}).find((o) => o.input === "doc-list");
26540
+ if (!op) {
26541
+ echoError(`Publish plugin '${record2.name}' received a Doc[] array but declares no doc-list operation; a Content object is required`);
26542
+ return 1;
26543
+ }
26544
+ const docs = DocListSchema.safeParse(json2);
26545
+ if (!docs.success) {
26546
+ const issues = docs.error.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join("; ");
26547
+ echoError(`Input schema violation for the declared doc-list operation of '${record2.name}': ${issues}`);
26548
+ return 1;
26549
+ }
26550
+ artifactOutput = op.output === "markdown";
26551
+ } else {
26552
+ const parseResult = contract.input.safeParse(json2);
26553
+ if (!parseResult.success) {
26554
+ const issues = parseResult.error.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join("; ");
26555
+ echoError(`Input schema violation for kind '${record2.kind}': ${issues}`);
26556
+ return 1;
26557
+ }
26603
26558
  }
26604
26559
  } else {
26605
26560
  if (!existsSync3(absInPath)) {
@@ -26607,11 +26562,19 @@ async function runInvoke(opts) {
26607
26562
  return 1;
26608
26563
  }
26609
26564
  }
26610
- const entryPath = isAbsolute(record2.entry) ? record2.entry : resolve3(record2.dir, record2.entry);
26565
+ const entryPath = isAbsolute(record2.entry) ? record2.entry : resolve2(record2.dir, record2.entry);
26611
26566
  if (!existsSync3(entryPath)) {
26612
26567
  echoError(`Plugin entry script does not exist: ${entryPath}`);
26613
26568
  return 1;
26614
26569
  }
26570
+ if (existsSync3(absOutPath)) {
26571
+ try {
26572
+ unlinkSync(absOutPath);
26573
+ } catch (err) {
26574
+ echoError(`Failed to clear stale output ${absOutPath}: ${err instanceof Error ? err.message : String(err)}`);
26575
+ return 1;
26576
+ }
26577
+ }
26615
26578
  const proc = spawnPluginEntry({
26616
26579
  entryPath,
26617
26580
  cwd: record2.dir,
@@ -26630,6 +26593,13 @@ async function runInvoke(opts) {
26630
26593
  echoError(`Plugin entry succeeded but did not write output file: ${absOutPath}`);
26631
26594
  return 1;
26632
26595
  }
26596
+ if (artifactOutput) {
26597
+ if (statSync3(absOutPath).size === 0) {
26598
+ echoError(`Plugin entry succeeded but wrote an empty output file: ${absOutPath}`);
26599
+ return 1;
26600
+ }
26601
+ return 0;
26602
+ }
26633
26603
  let outContent;
26634
26604
  try {
26635
26605
  outContent = readFileSync3(absOutPath, "utf-8");
@@ -26650,10 +26620,305 @@ async function runInvoke(opts) {
26650
26620
  echoError(`Output schema violation for kind '${record2.kind}': ${issues}`);
26651
26621
  return 1;
26652
26622
  }
26623
+ const parsed = outResult.data;
26624
+ if (record2.kind === "publish" && parsed.ok === false) {
26625
+ const reason = parsed.error ?? parsed.message ?? "publish failed";
26626
+ echoError(`Plugin '${record2.name}' reported failure: ${reason}`);
26627
+ return 1;
26628
+ }
26629
+ return 0;
26630
+ }
26631
+
26632
+ // src/fanin.ts
26633
+ var DEFAULT_SOURCE_TIMEOUT_MS = 30 * 60 * 1000;
26634
+ var SOURCE_ERROR_MAX_CHARS = 2048;
26635
+ function capSourceError(error51) {
26636
+ if (error51.length <= SOURCE_ERROR_MAX_CHARS) {
26637
+ return error51;
26638
+ }
26639
+ return `${error51.slice(0, SOURCE_ERROR_MAX_CHARS)}... [truncated; ${error51.length} chars total]`;
26640
+ }
26641
+ var EXIT_USAGE = 2;
26642
+ var EXIT_INPUT = 1;
26643
+ function mergeDocLists(inputs, sources) {
26644
+ const merged = [];
26645
+ const byId = new Map;
26646
+ for (const [i, docList] of inputs.entries()) {
26647
+ const file2 = sources[i] ?? `input[${i}]`;
26648
+ for (const doc2 of docList) {
26649
+ const entry = { file: file2 };
26650
+ if (doc2.sourceUri !== undefined) {
26651
+ entry.sourceUri = doc2.sourceUri;
26652
+ }
26653
+ const existing = byId.get(doc2.id);
26654
+ if (existing) {
26655
+ const list = existing.metadata?.sources;
26656
+ if (Array.isArray(list)) {
26657
+ list.push(entry);
26658
+ }
26659
+ continue;
26660
+ }
26661
+ const mergedDoc = {
26662
+ ...doc2,
26663
+ metadata: { ...doc2.metadata ?? {}, sources: [entry] }
26664
+ };
26665
+ byId.set(doc2.id, mergedDoc);
26666
+ merged.push(mergedDoc);
26667
+ }
26668
+ }
26669
+ return merged;
26670
+ }
26671
+ async function readDocList(fs2, path3) {
26672
+ if (!await fs2.exists(path3)) {
26673
+ echoError(`Input file does not exist: ${path3}`);
26674
+ return null;
26675
+ }
26676
+ let content;
26677
+ try {
26678
+ content = await fs2.readFile(path3);
26679
+ } catch (err) {
26680
+ echoError(`Failed to read input file ${path3}: ${err instanceof Error ? err.message : String(err)}`);
26681
+ return null;
26682
+ }
26683
+ let json2;
26684
+ try {
26685
+ json2 = JSON.parse(content);
26686
+ } catch (err) {
26687
+ echoError(`Input file ${path3} is invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
26688
+ return null;
26689
+ }
26690
+ const result = DocListSchema.safeParse(json2);
26691
+ if (!result.success) {
26692
+ const issues = result.error.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join("; ");
26693
+ echoError(`Input file ${path3} violates the Doc list schema: ${issues}`);
26694
+ return null;
26695
+ }
26696
+ return result.data;
26697
+ }
26698
+ function parseArgs(argv) {
26699
+ const inPaths = [];
26700
+ const sources = [];
26701
+ let outPath;
26702
+ let sourceTimeoutMs;
26703
+ const sourceTimeoutOverrides = {};
26704
+ for (let i = 0;i < argv.length; i++) {
26705
+ const arg = argv[i];
26706
+ if (arg === "--in") {
26707
+ const value = argv[++i];
26708
+ if (value === undefined) {
26709
+ throw new Error("--in requires a file path");
26710
+ }
26711
+ inPaths.push(value);
26712
+ } else if (arg === "--source") {
26713
+ const value = argv[++i];
26714
+ if (value === undefined) {
26715
+ throw new Error("--source requires <plugin>:<inputFile>");
26716
+ }
26717
+ const sep = value.indexOf(":");
26718
+ if (sep <= 0 || sep === value.length - 1) {
26719
+ throw new Error(`--source requires <plugin>:<inputFile>, got '${value}'`);
26720
+ }
26721
+ sources.push({ plugin: value.slice(0, sep), inputFile: value.slice(sep + 1) });
26722
+ } else if (arg === "--source-timeout") {
26723
+ const value = argv[++i];
26724
+ if (value === undefined) {
26725
+ throw new Error("--source-timeout requires a millisecond value");
26726
+ }
26727
+ if (sourceTimeoutMs !== undefined) {
26728
+ throw new Error("--source-timeout may be specified only once");
26729
+ }
26730
+ const parsed = Number(value);
26731
+ if (!Number.isInteger(parsed) || parsed <= 0) {
26732
+ throw new Error(`--source-timeout must be a positive integer (ms), got '${value}'`);
26733
+ }
26734
+ sourceTimeoutMs = parsed;
26735
+ } else if (arg === "--source-timeout-override") {
26736
+ const value = argv[++i];
26737
+ if (value === undefined) {
26738
+ throw new Error("--source-timeout-override requires <plugin>:<ms>");
26739
+ }
26740
+ const sep = value.indexOf(":");
26741
+ if (sep <= 0 || sep === value.length - 1) {
26742
+ throw new Error(`--source-timeout-override requires <plugin>:<ms>, got '${value}'`);
26743
+ }
26744
+ const plugin = value.slice(0, sep);
26745
+ if (plugin in sourceTimeoutOverrides) {
26746
+ throw new Error(`--source-timeout-override: plugin '${plugin}' may be specified only once`);
26747
+ }
26748
+ const parsed = Number(value.slice(sep + 1));
26749
+ if (!Number.isInteger(parsed) || parsed <= 0) {
26750
+ throw new Error(`--source-timeout-override ms must be a positive integer, got '${value.slice(sep + 1)}'`);
26751
+ }
26752
+ sourceTimeoutOverrides[plugin] = parsed;
26753
+ } else if (arg === "--out") {
26754
+ const value = argv[++i];
26755
+ if (value === undefined) {
26756
+ throw new Error("--out requires a file path");
26757
+ }
26758
+ if (outPath !== undefined) {
26759
+ throw new Error("--out may be specified only once");
26760
+ }
26761
+ outPath = value;
26762
+ } else {
26763
+ throw new Error(`Unknown argument: ${arg}`);
26764
+ }
26765
+ }
26766
+ if (inPaths.length === 0 && sources.length === 0) {
26767
+ throw new Error("at least one --in file or --source is required");
26768
+ }
26769
+ if (sourceTimeoutMs !== undefined && sources.length === 0) {
26770
+ throw new Error("--source-timeout requires at least one --source");
26771
+ }
26772
+ if (Object.keys(sourceTimeoutOverrides).length > 0 && sources.length === 0) {
26773
+ throw new Error("--source-timeout-override requires at least one --source");
26774
+ }
26775
+ for (const plugin of Object.keys(sourceTimeoutOverrides)) {
26776
+ if (!sources.some((candidate) => candidate.plugin === plugin)) {
26777
+ throw new Error(`--source-timeout-override names plugin '${plugin}' which is not among the --source plugins`);
26778
+ }
26779
+ }
26780
+ if (outPath === undefined) {
26781
+ throw new Error("--out file is required");
26782
+ }
26783
+ return {
26784
+ inPaths,
26785
+ outPath,
26786
+ ...sources.length > 0 ? { sources } : {},
26787
+ ...sourceTimeoutMs !== undefined ? { sourceTimeoutMs } : {},
26788
+ ...Object.keys(sourceTimeoutOverrides).length > 0 ? { sourceTimeoutOverrides } : {}
26789
+ };
26790
+ }
26791
+ async function collectSourceDocs(discovery, source, timeoutMs) {
26792
+ const record2 = getByName(discovery, source.plugin);
26793
+ if (!record2) {
26794
+ throw new Error(`Plugin '${source.plugin}' not found in the merged plugin registry`);
26795
+ }
26796
+ if (record2.kind !== "ingestion") {
26797
+ throw new Error(`Plugin '${source.plugin}' is kind '${record2.kind}', not 'ingestion'`);
26798
+ }
26799
+ const fs2 = createNodeFileSystem();
26800
+ const absInPath = resolve3(source.inputFile);
26801
+ if (!await fs2.exists(absInPath)) {
26802
+ throw new Error(`Input file does not exist: ${absInPath}`);
26803
+ }
26804
+ const entryPath = isAbsolute2(record2.entry) ? record2.entry : resolve3(record2.dir, record2.entry);
26805
+ if (!await fs2.exists(entryPath)) {
26806
+ throw new Error(`Plugin entry script does not exist: ${entryPath}`);
26807
+ }
26808
+ const outTmp = join3(tmpdir(), `kk-fanin-${source.plugin}-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
26809
+ try {
26810
+ const proc = await spawnPluginEntryAsync({
26811
+ entryPath,
26812
+ cwd: record2.dir,
26813
+ inPath: absInPath,
26814
+ outPath: outTmp,
26815
+ pluginKind: record2.kind,
26816
+ pluginName: record2.name,
26817
+ timeoutMs
26818
+ });
26819
+ if (proc.exitCode !== 0) {
26820
+ const why = proc.timedOut === true ? `timed out after ${timeoutMs}ms` : `exited with code ${proc.exitCode}`;
26821
+ throw new Error(`Plugin '${source.plugin}' ${why}: ${proc.stderr || proc.stdout || "no output"}`);
26822
+ }
26823
+ if (!await fs2.exists(outTmp)) {
26824
+ throw new Error(`Plugin '${source.plugin}' succeeded but did not write an output file`);
26825
+ }
26826
+ let json2;
26827
+ try {
26828
+ json2 = JSON.parse(await fs2.readFile(outTmp));
26829
+ } catch (err) {
26830
+ throw new Error(`Plugin '${source.plugin}' wrote invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
26831
+ }
26832
+ const parsed = DocListSchema.safeParse(json2);
26833
+ if (!parsed.success) {
26834
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "root"}: ${i.message}`).join("; ");
26835
+ throw new Error(`Plugin '${source.plugin}' output violates the Doc list schema: ${issues}`);
26836
+ }
26837
+ return parsed.data;
26838
+ } finally {
26839
+ await fs2.deleteFile(outTmp);
26840
+ }
26841
+ }
26842
+ async function runFanin(argv) {
26843
+ let opts;
26844
+ try {
26845
+ opts = parseArgs(argv);
26846
+ } catch (err) {
26847
+ echoError(`fanin: ${err instanceof Error ? err.message : String(err)}`);
26848
+ return EXIT_USAGE;
26849
+ }
26850
+ const fs2 = createNodeFileSystem();
26851
+ const inputs = [];
26852
+ const sources = [];
26853
+ const sourceErrors = [];
26854
+ for (const inPath of opts.inPaths) {
26855
+ const docList = await readDocList(fs2, inPath);
26856
+ if (docList === null) {
26857
+ return EXIT_INPUT;
26858
+ }
26859
+ inputs.push(docList);
26860
+ sources.push(inPath);
26861
+ }
26862
+ if (opts.sources !== undefined && opts.sources.length > 0) {
26863
+ const discovery = discoverAllPlugins({});
26864
+ const settled = await Promise.allSettled(opts.sources.map(async (source) => {
26865
+ const started = Date.now();
26866
+ try {
26867
+ const timeoutMs = opts.sourceTimeoutOverrides?.[source.plugin] ?? opts.sourceTimeoutMs ?? DEFAULT_SOURCE_TIMEOUT_MS;
26868
+ return { ok: true, docs: await collectSourceDocs(discovery, source, timeoutMs) };
26869
+ } catch (err) {
26870
+ return {
26871
+ ok: false,
26872
+ error: err instanceof Error ? err.message : String(err),
26873
+ durationMs: Date.now() - started
26874
+ };
26875
+ }
26876
+ }));
26877
+ for (const [i, outcome] of settled.entries()) {
26878
+ const source = opts.sources[i];
26879
+ if (source === undefined) {
26880
+ continue;
26881
+ }
26882
+ const result = outcome.status === "fulfilled" ? outcome.value : { ok: false, error: String(outcome.reason), durationMs: 0 };
26883
+ if (result.ok) {
26884
+ inputs.push(result.docs);
26885
+ sources.push(source.plugin);
26886
+ } else {
26887
+ sourceErrors.push({
26888
+ plugin: source.plugin,
26889
+ error: capSourceError(result.error),
26890
+ durationMs: result.durationMs
26891
+ });
26892
+ }
26893
+ }
26894
+ for (const err of sourceErrors) {
26895
+ echoError(`fanin: source '${err.plugin}' failed after ${err.durationMs}ms: ${err.error}`);
26896
+ }
26897
+ if (sourceErrors.length === opts.sources.length) {
26898
+ echoError(`fanin: all ${opts.sources.length} source(s) failed; no output written`);
26899
+ return EXIT_INPUT;
26900
+ }
26901
+ }
26902
+ const merged = mergeDocLists(inputs, sources);
26903
+ if (sourceErrors.length > 0) {
26904
+ for (const doc2 of merged) {
26905
+ doc2.metadata = { ...doc2.metadata ?? {}, sourceErrors };
26906
+ }
26907
+ }
26908
+ try {
26909
+ await fs2.writeFile(resolve3(opts.outPath), `${JSON.stringify(merged, null, 2)}
26910
+ `);
26911
+ } catch (err) {
26912
+ echoError(`Failed to write output file ${resolve3(opts.outPath)}: ${err instanceof Error ? err.message : String(err)}`);
26913
+ return EXIT_INPUT;
26914
+ }
26653
26915
  return 0;
26654
26916
  }
26655
26917
 
26656
26918
  // src/fanout.ts
26919
+ import { tmpdir as tmpdir2 } from "os";
26920
+ import { isAbsolute as isAbsolute3, join as join4, resolve as resolve4 } from "path";
26921
+ init_dist();
26657
26922
  var EXIT_FAILURE = 1;
26658
26923
  var EXIT_INPUT2 = 2;
26659
26924
  function aggregate(entries) {
@@ -26683,6 +26948,7 @@ async function fanout(content, targets, dispatch) {
26683
26948
  status: "published",
26684
26949
  ...result.url !== undefined ? { url: result.url } : {},
26685
26950
  ...result.id !== undefined ? { id: result.id } : {},
26951
+ ...result.metadata !== undefined ? { metadata: result.metadata } : {},
26686
26952
  durationMs
26687
26953
  } : {
26688
26954
  target,
@@ -26704,9 +26970,6 @@ async function fanout(content, targets, dispatch) {
26704
26970
  }
26705
26971
  return aggregate(entries);
26706
26972
  }
26707
- function selectRetryTargets(prior) {
26708
- return prior.targets.filter((t) => t.status === "failed").map((t) => t.target);
26709
- }
26710
26973
  async function readContent(fs2, path3) {
26711
26974
  if (!await fs2.exists(path3)) {
26712
26975
  echoError(`Content file does not exist: ${path3}`);
@@ -26781,6 +27044,14 @@ async function readAggregateResult(fs2, path3) {
26781
27044
  echoError(`Result file ${path3} violates the aggregate result schema (target ${String(t.target)})`);
26782
27045
  return null;
26783
27046
  }
27047
+ let metadata;
27048
+ if (t.metadata !== undefined) {
27049
+ if (typeof t.metadata !== "object" || t.metadata === null || Array.isArray(t.metadata)) {
27050
+ echoError(`Result file ${path3} violates the aggregate result schema: target '${String(t.target)}' metadata must be an object`);
27051
+ return null;
27052
+ }
27053
+ metadata = t.metadata;
27054
+ }
26784
27055
  targets.push({
26785
27056
  target: t.target,
26786
27057
  ok: t.ok,
@@ -26788,7 +27059,8 @@ async function readAggregateResult(fs2, path3) {
26788
27059
  ...typeof t.url === "string" ? { url: t.url } : {},
26789
27060
  ...typeof t.id === "string" ? { id: t.id } : {},
26790
27061
  ...typeof t.error === "string" ? { error: t.error } : {},
26791
- ...typeof t.durationMs === "number" ? { durationMs: t.durationMs } : {}
27062
+ ...typeof t.durationMs === "number" ? { durationMs: t.durationMs } : {},
27063
+ ...metadata !== undefined ? { metadata } : {}
26792
27064
  });
26793
27065
  }
26794
27066
  return {
@@ -26887,11 +27159,11 @@ async function pluginDispatch(target, contentPath, discovery) {
26887
27159
  throw new Error(`Publish plugin '${target}' not found in the merged plugin registry`);
26888
27160
  }
26889
27161
  const fs2 = createNodeFileSystem();
26890
- const entryPath = isAbsolute2(record2.entry) ? record2.entry : resolve4(record2.dir, record2.entry);
27162
+ const entryPath = isAbsolute3(record2.entry) ? record2.entry : resolve4(record2.dir, record2.entry);
26891
27163
  if (!await fs2.exists(entryPath)) {
26892
27164
  throw new Error(`Plugin entry script does not exist: ${entryPath}`);
26893
27165
  }
26894
- const outTmp = join3(tmpdir(), `kk-fanout-${target}-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
27166
+ const outTmp = join4(tmpdir2(), `kk-fanout-${target}-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
26895
27167
  try {
26896
27168
  const proc = spawnPluginEntry({
26897
27169
  entryPath,
@@ -26956,6 +27228,7 @@ async function runFanout(argv, deps = {}) {
26956
27228
  let prior;
26957
27229
  let skipped = [];
26958
27230
  let targets = opts.targets;
27231
+ let retainedFailed = [];
26959
27232
  if (opts.retryFromPath !== undefined) {
26960
27233
  const parsed = await readAggregateResult(fs2, opts.retryFromPath);
26961
27234
  if (parsed === null) {
@@ -26967,17 +27240,26 @@ async function runFanout(argv, deps = {}) {
26967
27240
  ok: true,
26968
27241
  status: "skipped",
26969
27242
  ...t.url !== undefined ? { url: t.url } : {},
26970
- ...t.id !== undefined ? { id: t.id } : {}
27243
+ ...t.id !== undefined ? { id: t.id } : {},
27244
+ ...t.metadata !== undefined ? { metadata: t.metadata } : {}
26971
27245
  }));
26972
27246
  const carried = new Set(skipped.map((t) => t.target));
26973
- targets = opts.targets.length > 0 ? opts.targets.filter((t) => !carried.has(t)) : selectRetryTargets(prior);
27247
+ const priorFailed = prior.targets.filter((t) => t.status === "failed");
27248
+ if (opts.targets.length > 0) {
27249
+ const attempt = [...new Set(opts.targets.filter((t) => !carried.has(t)))];
27250
+ const attemptSet = new Set(attempt);
27251
+ targets = attempt;
27252
+ retainedFailed = priorFailed.filter((t) => !attemptSet.has(t.target));
27253
+ } else {
27254
+ targets = priorFailed.map((t) => t.target);
27255
+ }
26974
27256
  }
26975
27257
  const dispatch = deps.dispatch ?? ((t) => pluginDispatch(t, opts.contentPath, discovery));
26976
27258
  if (deps.dispatch === undefined && !validateTargets(targets, discovery)) {
26977
27259
  return EXIT_INPUT2;
26978
27260
  }
26979
27261
  const fresh = await fanout(content, targets, dispatch);
26980
- const result = prior !== undefined ? aggregate([...skipped, ...fresh.targets]) : fresh;
27262
+ const result = prior !== undefined ? aggregate([...skipped, ...retainedFailed, ...fresh.targets]) : fresh;
26981
27263
  try {
26982
27264
  await fs2.writeFile(resolve4(opts.outPath), `${JSON.stringify(result, null, 2)}
26983
27265
  `);
@@ -27012,11 +27294,24 @@ function collectIn(value, acc) {
27012
27294
  acc.push(value);
27013
27295
  return acc;
27014
27296
  }
27297
+ function collectSource(value, acc) {
27298
+ acc.push(value);
27299
+ return acc;
27300
+ }
27015
27301
  async function runFaninAction(options) {
27016
27302
  const argv = [];
27017
27303
  for (const inPath of options.in) {
27018
27304
  argv.push("--in", inPath);
27019
27305
  }
27306
+ for (const source of options.source ?? []) {
27307
+ argv.push("--source", source);
27308
+ }
27309
+ if (options.sourceTimeout !== undefined) {
27310
+ argv.push("--source-timeout", options.sourceTimeout);
27311
+ }
27312
+ for (const override of options.sourceTimeoutOverride ?? []) {
27313
+ argv.push("--source-timeout-override", override);
27314
+ }
27020
27315
  argv.push("--out", options.out);
27021
27316
  const code = await runFanin(argv);
27022
27317
  if (code !== 0) {
@@ -27027,6 +27322,10 @@ function collectTarget(value, acc) {
27027
27322
  acc.push(value);
27028
27323
  return acc;
27029
27324
  }
27325
+ function collectOverride(value, acc) {
27326
+ acc.push(value);
27327
+ return acc;
27328
+ }
27030
27329
  async function runFanoutAction(options) {
27031
27330
  const argv = ["--content", options.content, "--out", options.out];
27032
27331
  for (const target of options.target) {
@@ -27049,10 +27348,10 @@ async function runFanoutAction(options) {
27049
27348
  function registerExecutorCommand(program2) {
27050
27349
  const executor = program2.command("executor").description("Run knowledge-kit plugin steps (invoke, fan-in, fan-out)");
27051
27350
  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);
27351
+ 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
27352
  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
27353
  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);
27354
+ 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
27355
  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
27356
  }
27058
27357