@ozzylabs/feedradar 0.2.0 → 0.2.2

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 (164) hide show
  1. package/README.ja.md +51 -13
  2. package/README.md +51 -13
  3. package/dist/agents/_boundary.d.ts +21 -0
  4. package/dist/agents/_boundary.d.ts.map +1 -1
  5. package/dist/agents/_boundary.js +34 -0
  6. package/dist/agents/_boundary.js.map +1 -1
  7. package/dist/agents/claude-code.d.ts.map +1 -1
  8. package/dist/agents/claude-code.js +14 -6
  9. package/dist/agents/claude-code.js.map +1 -1
  10. package/dist/agents/codex-cli.d.ts.map +1 -1
  11. package/dist/agents/codex-cli.js +13 -7
  12. package/dist/agents/codex-cli.js.map +1 -1
  13. package/dist/agents/copilot.d.ts.map +1 -1
  14. package/dist/agents/copilot.js +13 -6
  15. package/dist/agents/copilot.js.map +1 -1
  16. package/dist/agents/gemini-cli.d.ts.map +1 -1
  17. package/dist/agents/gemini-cli.js +13 -6
  18. package/dist/agents/gemini-cli.js.map +1 -1
  19. package/dist/agents/types.d.ts +26 -0
  20. package/dist/agents/types.d.ts.map +1 -1
  21. package/dist/claude-skills/dismiss/SKILL.md +4 -4
  22. package/dist/claude-skills/research/SKILL.md +2 -3
  23. package/dist/claude-skills/review/SKILL.md +2 -2
  24. package/dist/claude-skills/update/SKILL.md +7 -7
  25. package/dist/cli/_locale.d.ts +96 -0
  26. package/dist/cli/_locale.d.ts.map +1 -0
  27. package/dist/cli/_locale.js +130 -0
  28. package/dist/cli/_locale.js.map +1 -0
  29. package/dist/cli/_progress.d.ts +30 -1
  30. package/dist/cli/_progress.d.ts.map +1 -1
  31. package/dist/cli/_progress.js +9 -1
  32. package/dist/cli/_progress.js.map +1 -1
  33. package/dist/cli/dismiss.d.ts.map +1 -1
  34. package/dist/cli/dismiss.js +61 -54
  35. package/dist/cli/dismiss.js.map +1 -1
  36. package/dist/cli/doctor.d.ts +8 -0
  37. package/dist/cli/doctor.d.ts.map +1 -1
  38. package/dist/cli/doctor.js +91 -60
  39. package/dist/cli/doctor.js.map +1 -1
  40. package/dist/cli/index.d.ts +36 -1
  41. package/dist/cli/index.d.ts.map +1 -1
  42. package/dist/cli/index.js +79 -18
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/cli/init.d.ts +15 -0
  45. package/dist/cli/init.d.ts.map +1 -1
  46. package/dist/cli/init.js +149 -51
  47. package/dist/cli/init.js.map +1 -1
  48. package/dist/cli/items.d.ts.map +1 -1
  49. package/dist/cli/items.js +51 -30
  50. package/dist/cli/items.js.map +1 -1
  51. package/dist/cli/research.d.ts.map +1 -1
  52. package/dist/cli/research.js +138 -109
  53. package/dist/cli/research.js.map +1 -1
  54. package/dist/cli/review.d.ts.map +1 -1
  55. package/dist/cli/review.js +114 -92
  56. package/dist/cli/review.js.map +1 -1
  57. package/dist/cli/routine/fire.d.ts +3 -2
  58. package/dist/cli/routine/fire.d.ts.map +1 -1
  59. package/dist/cli/routine/fire.js +30 -25
  60. package/dist/cli/routine/fire.js.map +1 -1
  61. package/dist/cli/routine/generate-pipeline.d.ts +70 -1
  62. package/dist/cli/routine/generate-pipeline.d.ts.map +1 -1
  63. package/dist/cli/routine/generate-pipeline.js +273 -44
  64. package/dist/cli/routine/generate-pipeline.js.map +1 -1
  65. package/dist/cli/routine/generate-watch.d.ts +10 -1
  66. package/dist/cli/routine/generate-watch.d.ts.map +1 -1
  67. package/dist/cli/routine/generate-watch.js +49 -37
  68. package/dist/cli/routine/generate-watch.js.map +1 -1
  69. package/dist/cli/routine.d.ts.map +1 -1
  70. package/dist/cli/routine.js +28 -24
  71. package/dist/cli/routine.js.map +1 -1
  72. package/dist/cli/source.d.ts.map +1 -1
  73. package/dist/cli/source.js +206 -182
  74. package/dist/cli/source.js.map +1 -1
  75. package/dist/cli/triage.d.ts.map +1 -1
  76. package/dist/cli/triage.js +146 -130
  77. package/dist/cli/triage.js.map +1 -1
  78. package/dist/cli/undismiss.d.ts.map +1 -1
  79. package/dist/cli/undismiss.js +32 -25
  80. package/dist/cli/undismiss.js.map +1 -1
  81. package/dist/cli/update.d.ts.map +1 -1
  82. package/dist/cli/update.js +77 -61
  83. package/dist/cli/update.js.map +1 -1
  84. package/dist/cli/watch.d.ts.map +1 -1
  85. package/dist/cli/watch.js +71 -31
  86. package/dist/cli/watch.js.map +1 -1
  87. package/dist/cli/workflow/generate-combined-with-triage.d.ts +9 -2
  88. package/dist/cli/workflow/generate-combined-with-triage.d.ts.map +1 -1
  89. package/dist/cli/workflow/generate-combined-with-triage.js +120 -71
  90. package/dist/cli/workflow/generate-combined-with-triage.js.map +1 -1
  91. package/dist/cli/workflow/generate-combined.d.ts +8 -1
  92. package/dist/cli/workflow/generate-combined.d.ts.map +1 -1
  93. package/dist/cli/workflow/generate-combined.js +39 -33
  94. package/dist/cli/workflow/generate-combined.js.map +1 -1
  95. package/dist/cli/workflow/generate-watch.d.ts +10 -1
  96. package/dist/cli/workflow/generate-watch.d.ts.map +1 -1
  97. package/dist/cli/workflow/generate-watch.js +37 -30
  98. package/dist/cli/workflow/generate-watch.js.map +1 -1
  99. package/dist/cli/workflow.d.ts.map +1 -1
  100. package/dist/cli/workflow.js +28 -23
  101. package/dist/cli/workflow.js.map +1 -1
  102. package/dist/core/config.d.ts +2 -1
  103. package/dist/core/config.d.ts.map +1 -1
  104. package/dist/core/config.js +14 -4
  105. package/dist/core/config.js.map +1 -1
  106. package/dist/core/feeds/html-js.d.ts.map +1 -1
  107. package/dist/core/feeds/html-js.js +16 -9
  108. package/dist/core/feeds/html-js.js.map +1 -1
  109. package/dist/core/feeds/types.d.ts +9 -0
  110. package/dist/core/feeds/types.d.ts.map +1 -1
  111. package/dist/core/locale.d.ts +69 -0
  112. package/dist/core/locale.d.ts.map +1 -0
  113. package/dist/core/locale.js +74 -0
  114. package/dist/core/locale.js.map +1 -0
  115. package/dist/core/watcher.d.ts +11 -0
  116. package/dist/core/watcher.d.ts.map +1 -1
  117. package/dist/core/watcher.js +21 -5
  118. package/dist/core/watcher.js.map +1 -1
  119. package/dist/i18n/index.d.ts +57 -0
  120. package/dist/i18n/index.d.ts.map +1 -0
  121. package/dist/i18n/index.js +49 -0
  122. package/dist/i18n/index.js.map +1 -0
  123. package/dist/i18n/messages/en.d.ts +993 -0
  124. package/dist/i18n/messages/en.d.ts.map +1 -0
  125. package/dist/i18n/messages/en.js +1096 -0
  126. package/dist/i18n/messages/en.js.map +1 -0
  127. package/dist/i18n/messages/ja.d.ts +13 -0
  128. package/dist/i18n/messages/ja.d.ts.map +1 -0
  129. package/dist/i18n/messages/ja.js +970 -0
  130. package/dist/i18n/messages/ja.js.map +1 -0
  131. package/dist/schemas/config.d.ts +7 -0
  132. package/dist/schemas/config.d.ts.map +1 -1
  133. package/dist/schemas/config.js +5 -0
  134. package/dist/schemas/config.js.map +1 -1
  135. package/dist/schemas/recipe.d.ts +1 -1
  136. package/dist/schemas/source.d.ts +3 -3
  137. package/dist/skills/research/SKILL.md +13 -12
  138. package/dist/skills/review/SKILL.md +13 -12
  139. package/dist/skills/update/SKILL.md +19 -19
  140. package/dist/templates/en/agents/AGENTS.md +284 -0
  141. package/dist/templates/en/claude/CLAUDE.md +5 -0
  142. package/dist/templates/en/default.md +16 -0
  143. package/dist/templates/en/digest.md +66 -0
  144. package/dist/templates/en/feedradar.md +235 -0
  145. package/dist/templates/{routines → en/routines}/pipeline.yaml.tmpl +30 -41
  146. package/dist/templates/{routines → en/routines}/watch-daily.yaml +12 -15
  147. package/dist/templates/{routines → en/routines}/watch.yaml.tmpl +11 -14
  148. package/dist/templates/{workflows → en/workflows}/combined-with-triage.template.yaml.tmpl +3 -3
  149. package/dist/templates/{workflows → en/workflows}/combined.template.yaml.tmpl +6 -6
  150. package/dist/templates/{workflows → en/workflows}/watch.template.yaml.tmpl +8 -8
  151. package/dist/templates/{workflows → en/workflows}/watch.yaml +3 -3
  152. package/dist/templates/{agents → ja/agents}/AGENTS.md +16 -16
  153. package/dist/templates/{digest.md → ja/digest.md} +5 -6
  154. package/dist/templates/{feedradar.md → ja/feedradar.md} +12 -12
  155. package/dist/templates/ja/routines/pipeline.yaml.tmpl +211 -0
  156. package/dist/templates/ja/routines/watch-daily.yaml +151 -0
  157. package/dist/templates/ja/routines/watch.yaml.tmpl +145 -0
  158. package/dist/templates/ja/workflows/combined-with-triage.template.yaml.tmpl +123 -0
  159. package/dist/templates/ja/workflows/combined.template.yaml.tmpl +109 -0
  160. package/dist/templates/ja/workflows/watch.template.yaml.tmpl +100 -0
  161. package/dist/templates/ja/workflows/watch.yaml +73 -0
  162. package/package.json +1 -1
  163. /package/dist/templates/{claude → ja/claude}/CLAUDE.md +0 -0
  164. /package/dist/templates/{default.md → ja/default.md} +0 -0
@@ -5,7 +5,9 @@ import { createProgressReporter } from "../core/progress.js";
5
5
  import { listRecipes, loadRecipe, mergeRecipeWithOverrides, } from "../core/recipes.js";
6
6
  import { loadSourceState } from "../core/state.js";
7
7
  import { watchRun } from "../core/watcher.js";
8
+ import { createTranslator } from "../i18n/index.js";
8
9
  import { SourceKindSchema, SourceSchema, SourceSelectorsSchema } from "../schemas/source.js";
10
+ import { LangFlagError, parseLangFlag, resolveWorkspaceLocale } from "./_locale.js";
9
11
  async function pathExists(p) {
10
12
  try {
11
13
  await access(p);
@@ -282,112 +284,23 @@ function parseRemoveArgs(args) {
282
284
  }
283
285
  return out;
284
286
  }
285
- function printAddHelp(log) {
286
- log("Usage: radar source add <id> --kind <kind> --url <url> [options]");
287
- log(" radar source add <id> --recipe <name> [overrides]");
288
- log("");
289
- log("Options:");
290
- log(" --kind <kind> rss | html | html-js | github-releases | npm-registry | json-feed | json-api");
291
- log(" --url <url> fetch target URL");
292
- log(" --recipe <name> apply a bundled recipe (see `radar source recipes`).");
293
- log(" Mutually exclusive with --kind / --url / --selector-* /");
294
- log(" --pagination-*; --name / --tags / --keywords /");
295
- log(" --exclude-keywords still override the recipe defaults.");
296
- log(" --name <name> display name (defaults to <id>)");
297
- log(" --tags <a,b> comma-separated tags");
298
- log(" --keywords <a,b> comma-separated include keywords");
299
- log(" (required for useful output — empty = match nothing)");
300
- log(" --exclude-keywords <a,b> comma-separated exclude keywords");
301
- log(" --selector-<field> <css> CSS selector for kind=html / html-js (required: item, title, link)");
302
- log(" optional: summary, publishedAt, body, tags");
303
- log(" For kind=html-js, selectors evaluate against the post-JS DOM.");
304
- log(" The `js:` block (waitFor / timeout / userAgent) cannot be set");
305
- log(" via flags; edit sources/<id>.yaml after add. See ADR-0010.");
306
- log("");
307
- log(" For kind=json-api (ADR-0012 / #174):");
308
- log(" --pagination-strategy <s> page | offset | cursor | link-header | token | none (default: page)");
309
- log(" --pagination-param <name> query param name for the page/offset/cursor value");
310
- log(" --pagination-start N initial page/offset value (default: 0)");
311
- log(" --page-size N items per page");
312
- log(" --page-size-param <name> query param name for the page-size value");
313
- log(" --max-pages N hard cap on pages traversed (default: 20)");
314
- log(" --next-cursor-path <jp> JSONPath-lite to the next-cursor value (cursor/token strategy)");
315
- log(" --total-path <jp> JSONPath-lite to the total-count value (backfill early-stop hint)");
316
- log("");
317
- log(" Selector fields (`jsonSelectors.*`) for kind=json-api cannot be set via flags;");
318
- log(" the schema has a default fallback chain (items / title / link / publishedAt / summary),");
319
- log(" so simple APIs work without selectors. Edit sources/<id>.yaml directly when explicit");
320
- log(" selectors are needed (nested fields, non-standard envelopes).");
321
- log("");
322
- log(" Facet sweep (e.g. year-by-year sweep) cannot be configured via flags; see ADR-0017");
323
- log(" and bundle the year sweep through `--recipe aws-whats-new`. Recipe-only structural field.");
287
+ function printAddHelp(t, log) {
288
+ log(t("cli.source.addHelp"));
324
289
  }
325
- function printListHelp(log) {
326
- log("Usage: radar source list [--enabled-only] [-v|--verbose]");
327
- log("");
328
- log("Lists sources/*.yaml in tabular form: id / kind / url / tags.");
329
- log("");
330
- log("Options:");
331
- log(" --enabled-only Reserved for forward compatibility (currently a no-op).");
332
- log(" -v, --verbose Print a detailed block per source including keywords,");
333
- log(" trustLevel, and lastFetchedAt (from state/<id>.yaml).");
290
+ function printListHelp(t, log) {
291
+ log(t("cli.source.listHelp"));
334
292
  }
335
- function printRemoveHelp(log) {
336
- log("Usage: radar source remove <id>");
337
- log("");
338
- log("Deletes sources/<id>.yaml. state/<id>.yaml and items/ are preserved.");
293
+ function printRemoveHelp(t, log) {
294
+ log(t("cli.source.removeHelp"));
339
295
  }
340
- function printTestHelp(log) {
341
- log("Usage: radar source test <id> [--limit N] [--show-content]");
342
- log("");
343
- log("Dry-run a single source: fetch, filter, and print matched items.");
344
- log("state/ and items/ are not touched (no persistence). Useful for tuning");
345
- log("keywords when adding a new source.");
346
- log("");
347
- log("For kind=json-api (ADR-0012 / #174), `source test` fetches PAGE 0 ONLY.");
348
- log("Pagination is NOT walked even when the recipe declares multiple pages —");
349
- log("`--limit N` caps how many matched items are PRINTED, it does not change");
350
- log("the page budget. Use `radar watch run --backfill` for full-history ingest.");
351
- log("Page 0's `Link` header / `nextCursor` extraction is surfaced via");
352
- log("`--show-content` for pagination tuning without state mutation.");
353
- log("");
354
- log("For facet-sweep recipes (ADR-0017 / #256), `source test` probes a SINGLE");
355
- log("facet value: range facets use the upper bound (latest year), enum facets");
356
- log("use the first listed value. A warning names which value was tested so");
357
- log("keyword tuning is not silently scoped to one slice. Run `radar watch run");
358
- log("--backfill` to sweep every facet value.");
359
- log("");
360
- log("Options:");
361
- log(" --limit N Maximum number of matched items to print (default 10)");
362
- log(" --show-content Also print the first 200 chars of each item's body, plus");
363
- log(" (kind=json-api) the selector adoption table and pagination");
364
- log(" preview (would-be next URL / Link header / nextCursor).");
365
- log(" -v, --verbose Enable progress-reporter raw() pass-through (adapter stdout).");
366
- log(" Most useful with kind=html-js (Playwright phase markers).");
367
- log(" -q, --quiet Suppress the progress reporter entirely. RADAR_NO_PROGRESS=1");
368
- log(" has the same effect.");
296
+ function printTestHelp(t, log) {
297
+ log(t("cli.source.testHelp"));
369
298
  }
370
- function printRecipesHelp(log) {
371
- log("Usage: radar source recipes");
372
- log("");
373
- log("List bundled recipes (recipes/*.yaml in the radar package — ADR-0012 §D3).");
374
- log("Each recipe can be applied via:");
375
- log(" radar source add <id> --recipe <name> [--keywords <kw>] [--tags <t>] [--name <display>]");
376
- log("");
377
- log("Bundled recipes ship with the radar npm package; user-authored recipes are");
378
- log("not yet supported. To add a new bundled recipe, contribute a YAML to the");
379
- log("radar repo's recipes/ directory.");
299
+ function printRecipesHelp(t, log) {
300
+ log(t("cli.source.recipesHelp"));
380
301
  }
381
- function printSourceHelp(log) {
382
- log("Usage: radar source <add|list|recipes|remove|test> [...]");
383
- log("");
384
- log("Subcommands:");
385
- log(" add <id> --kind <kind> --url <url> [...]");
386
- log(" add <id> --recipe <name> [--keywords <kw>] [--tags <t>] [--name <display>]");
387
- log(" list [--enabled-only]");
388
- log(" recipes");
389
- log(" remove <id>");
390
- log(" test <id> [--limit N] [--show-content]");
302
+ function printSourceHelp(t, log) {
303
+ log(t("cli.source.help"));
391
304
  }
392
305
  /**
393
306
  * Implementation of `source add`.
@@ -402,25 +315,38 @@ export async function addSource(args, options = {}) {
402
315
  const log = options.io?.log ?? ((m) => console.log(m));
403
316
  const warn = options.io?.warn ?? ((m) => console.warn(m));
404
317
  const error = options.io?.error ?? ((m) => console.error(m));
318
+ let langState;
319
+ try {
320
+ langState = parseLangFlag(args);
321
+ }
322
+ catch (e) {
323
+ if (e instanceof LangFlagError) {
324
+ error(`source add: ${e.message}`);
325
+ return 2;
326
+ }
327
+ throw e;
328
+ }
329
+ const locale = await resolveWorkspaceLocale({ flag: langState.flag, cwd, warn: error });
330
+ const t = createTranslator(locale);
405
331
  let parsed;
406
332
  try {
407
- parsed = parseAddArgs(args);
333
+ parsed = parseAddArgs(langState.rest);
408
334
  }
409
335
  catch (e) {
410
336
  error(`source add: ${e instanceof Error ? e.message : String(e)}`);
411
337
  return 2;
412
338
  }
413
339
  if (parsed.help) {
414
- printAddHelp(log);
340
+ printAddHelp(t, log);
415
341
  return 0;
416
342
  }
417
343
  if (!parsed.id) {
418
- error("source add: missing <id>");
419
- printAddHelp(error);
344
+ error(t("cli.source.missingId", { sub: "add" }));
345
+ printAddHelp(t, error);
420
346
  return 2;
421
347
  }
422
348
  if (!isSafeSourceId(parsed.id)) {
423
- error(`source add: invalid <id> '${parsed.id}' (must match [A-Za-z0-9][A-Za-z0-9._-]*)`);
349
+ error(t("cli.source.invalidId", { sub: "add", id: parsed.id }));
424
350
  return 2;
425
351
  }
426
352
  // `--recipe <name>` short-circuits the flag-based composition: the
@@ -431,19 +357,19 @@ export async function addSource(args, options = {}) {
431
357
  // `--pagination-*`) is rejected so the user gets an immediate, targeted
432
358
  // error instead of silently-ignored flags. ADR-0012 §D3.
433
359
  if (parsed.recipe !== undefined) {
434
- return addSourceFromRecipe(parsed, cwd, options, log, warn, error);
360
+ return addSourceFromRecipe(parsed, cwd, options, log, warn, error, t);
435
361
  }
436
362
  if (!parsed.kind) {
437
- error("source add: --kind is required");
363
+ error(t("cli.source.kindRequired"));
438
364
  return 2;
439
365
  }
440
366
  if (!parsed.url) {
441
- error("source add: --url is required");
367
+ error(t("cli.source.urlRequired"));
442
368
  return 2;
443
369
  }
444
370
  const kindResult = SourceKindSchema.safeParse(parsed.kind);
445
371
  if (!kindResult.success) {
446
- error(`source add: invalid --kind '${parsed.kind}' (expected: rss | html | html-js | github-releases | npm-registry | json-feed | json-api)`);
372
+ error(t("cli.source.invalidKind", { kind: parsed.kind }));
447
373
  return 2;
448
374
  }
449
375
  // Compose the object before schema validation so url-format errors et al.
@@ -474,7 +400,7 @@ export async function addSource(args, options = {}) {
474
400
  const selectorsResult = SourceSelectorsSchema.safeParse(parsed.selectors);
475
401
  if (!selectorsResult.success) {
476
402
  const issues = selectorsResult.error.issues.map((i) => `selectors.${i.path.join(".") || "<root>"}: ${i.message}`);
477
- error(`source add: validation failed`);
403
+ error(t("cli.source.validationFailed"));
478
404
  for (const issue of issues) {
479
405
  error(` - ${issue}`);
480
406
  }
@@ -525,13 +451,13 @@ export async function addSource(args, options = {}) {
525
451
  // Reject pagination flags on non-json-api kinds early so the user sees
526
452
  // a targeted hint instead of a deep schema refinement error ("pagination
527
453
  // is required when kind is 'json-api'" makes no sense for `kind: rss`).
528
- error(`source add: --pagination-* flags are only valid with --kind json-api (got --kind '${kindResult.data}')`);
454
+ error(t("cli.source.paginationOnlyJsonApi", { kind: kindResult.data }));
529
455
  return 2;
530
456
  }
531
457
  const validated = SourceSchema.safeParse(candidate);
532
458
  if (!validated.success) {
533
459
  const issues = validated.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`);
534
- error(`source add: validation failed`);
460
+ error(t("cli.source.validationFailed"));
535
461
  for (const issue of issues) {
536
462
  error(` - ${issue}`);
537
463
  }
@@ -539,11 +465,11 @@ export async function addSource(args, options = {}) {
539
465
  }
540
466
  const file = sourceFile(cwd, validated.data.id);
541
467
  if (await pathExists(file)) {
542
- error(`source add: '${validated.data.id}' already exists (sources/${validated.data.id}.yaml)`);
468
+ error(t("cli.source.alreadyExists", { id: validated.data.id }));
543
469
  return 1;
544
470
  }
545
471
  await writeFile(file, stringifyYaml(validated.data), "utf8");
546
- log(`source add: created sources/${validated.data.id}.yaml`);
472
+ log(t("cli.source.created", { id: validated.data.id }));
547
473
  // ADR-0006 / src/core/filter.ts treats an empty include-keyword list as
548
474
  // "match nothing" (firehose guard). A source with no keywords is therefore
549
475
  // valid YAML but inert — `watch run` will fetch it and drop every item
@@ -552,7 +478,7 @@ export async function addSource(args, options = {}) {
552
478
  // (stderr) so scripts that parse stdout are unaffected and the exit code
553
479
  // stays 0.
554
480
  if (validated.data.filters.keywords.length === 0) {
555
- warn(`source add: warning — '${validated.data.id}' has no keywords; all fetched items will be filtered out. Edit sources/${validated.data.id}.yaml or re-add with --keywords to start ingesting.`);
481
+ warn(t("cli.source.noKeywordsWarn", { id: validated.data.id }));
556
482
  }
557
483
  return 0;
558
484
  }
@@ -572,7 +498,7 @@ export async function addSource(args, options = {}) {
572
498
  * structurally edit `sources/<id>.yaml` after generation, same as for
573
499
  * any other source.
574
500
  */
575
- async function addSourceFromRecipe(parsed, cwd, options, log, warn, error) {
501
+ async function addSourceFromRecipe(parsed, cwd, options, log, warn, error, t) {
576
502
  const recipeName = parsed.recipe;
577
503
  // Defensive — should be guaranteed by the caller, but `parsed.recipe`
578
504
  // is typed as `string | undefined` so a quick narrow here keeps the
@@ -602,7 +528,7 @@ async function addSourceFromRecipe(parsed, cwd, options, log, warn, error) {
602
528
  forbidden.push("--pagination-*");
603
529
  }
604
530
  if (forbidden.length > 0) {
605
- error(`source add: --recipe '${recipeName}' supplies kind / url / structural fields; the following flags are not allowed with --recipe: ${forbidden.join(", ")}`);
531
+ error(t("cli.source.recipeForbiddenFlags", { recipe: recipeName, flags: forbidden.join(", ") }));
606
532
  return 2;
607
533
  }
608
534
  let loaded;
@@ -632,7 +558,7 @@ async function addSourceFromRecipe(parsed, cwd, options, log, warn, error) {
632
558
  // illegal combination). Surface every issue verbatim so recipe
633
559
  // authors and end users can both diagnose.
634
560
  const issues = validated.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`);
635
- error(`source add: recipe '${recipeName}' produced an invalid source`);
561
+ error(t("cli.source.recipeInvalidSource", { recipe: recipeName }));
636
562
  for (const issue of issues) {
637
563
  error(` - ${issue}`);
638
564
  }
@@ -640,17 +566,17 @@ async function addSourceFromRecipe(parsed, cwd, options, log, warn, error) {
640
566
  }
641
567
  const file = sourceFile(cwd, validated.data.id);
642
568
  if (await pathExists(file)) {
643
- error(`source add: '${validated.data.id}' already exists (sources/${validated.data.id}.yaml)`);
569
+ error(t("cli.source.alreadyExists", { id: validated.data.id }));
644
570
  return 1;
645
571
  }
646
572
  await writeFile(file, stringifyYaml(validated.data), "utf8");
647
- log(`source add: created sources/${validated.data.id}.yaml from recipe '${recipeName}'`);
573
+ log(t("cli.source.createdFromRecipe", { id: validated.data.id, recipe: recipeName }));
648
574
  // Same firehose-guard hint as the flag-based path: an empty
649
575
  // include-keyword list silently drops every fetched item, which
650
576
  // surprises users when they thought the recipe came with sensible
651
577
  // defaults.
652
578
  if (validated.data.filters.keywords.length === 0) {
653
- warn(`source add: warning — '${validated.data.id}' has no keywords; all fetched items will be filtered out. Re-add with --keywords or edit sources/${validated.data.id}.yaml to start ingesting.`);
579
+ warn(t("cli.source.noKeywordsWarnRecipe", { id: validated.data.id }));
654
580
  }
655
581
  return 0;
656
582
  }
@@ -701,16 +627,29 @@ export async function listSources(args, options = {}) {
701
627
  const cwd = options.cwd ?? process.cwd();
702
628
  const log = options.io?.log ?? ((m) => console.log(m));
703
629
  const error = options.io?.error ?? ((m) => console.error(m));
630
+ let langState;
631
+ try {
632
+ langState = parseLangFlag(args);
633
+ }
634
+ catch (e) {
635
+ if (e instanceof LangFlagError) {
636
+ error(`source list: ${e.message}`);
637
+ return 2;
638
+ }
639
+ throw e;
640
+ }
641
+ const locale = await resolveWorkspaceLocale({ flag: langState.flag, cwd, warn: error });
642
+ const t = createTranslator(locale);
704
643
  let parsed;
705
644
  try {
706
- parsed = parseListArgs(args);
645
+ parsed = parseListArgs(langState.rest);
707
646
  }
708
647
  catch (e) {
709
648
  error(`source list: ${e instanceof Error ? e.message : String(e)}`);
710
649
  return 2;
711
650
  }
712
651
  if (parsed.help) {
713
- printListHelp(log);
652
+ printListHelp(t, log);
714
653
  return 0;
715
654
  }
716
655
  // --enabled-only is wired through for forward compatibility; the schema does
@@ -719,7 +658,7 @@ export async function listSources(args, options = {}) {
719
658
  // surface stable.
720
659
  const dir = sourcesDir(cwd);
721
660
  if (!(await pathExists(dir))) {
722
- log("source list: no sources directory (run `radar init` first)");
661
+ log(t("cli.source.listNoDir"));
723
662
  return 0;
724
663
  }
725
664
  let entries;
@@ -732,7 +671,7 @@ export async function listSources(args, options = {}) {
732
671
  }
733
672
  const yamlFiles = entries.filter((f) => f.endsWith(".yaml")).sort();
734
673
  if (yamlFiles.length === 0) {
735
- log("source list: no sources defined (use `radar source add ...`)");
674
+ log(t("cli.source.listNoSources"));
736
675
  return 0;
737
676
  }
738
677
  const sources = [];
@@ -758,22 +697,29 @@ export async function listSources(args, options = {}) {
758
697
  if (i > 0)
759
698
  log("");
760
699
  const lastFetchedAt = await readLastFetchedAt(cwd, s.id, error);
700
+ const none = t("cli.source.valueNone");
761
701
  log(`${s.id}`);
762
- log(` kind: ${s.kind}`);
763
- log(` url: ${s.url}`);
764
- log(` name: ${s.name ?? "-"}`);
765
- log(` tags: ${s.tags.length > 0 ? s.tags.join(",") : "-"}`);
766
- log(` keywords: ${s.filters.keywords.length > 0 ? s.filters.keywords.join(",") : "(none — items will be filtered out)"}`);
767
- log(` excludeKeywords: ${s.filters.excludeKeywords.length > 0 ? s.filters.excludeKeywords.join(",") : "-"}`);
768
- log(` trustLevel: ${s.trustLevel}`);
769
- log(` lastFetchedAt: ${lastFetchedAt}`);
702
+ log(t("cli.source.fieldKind", { value: s.kind }));
703
+ log(t("cli.source.fieldUrl", { value: s.url }));
704
+ log(t("cli.source.fieldName", { value: s.name ?? none }));
705
+ log(t("cli.source.fieldTags", { value: s.tags.length > 0 ? s.tags.join(",") : none }));
706
+ log(t("cli.source.fieldKeywords", {
707
+ value: s.filters.keywords.length > 0
708
+ ? s.filters.keywords.join(",")
709
+ : t("cli.source.keywordsEmpty"),
710
+ }));
711
+ log(t("cli.source.fieldExcludeKeywords", {
712
+ value: s.filters.excludeKeywords.length > 0 ? s.filters.excludeKeywords.join(",") : none,
713
+ }));
714
+ log(t("cli.source.fieldTrustLevel", { value: s.trustLevel }));
715
+ log(t("cli.source.fieldLastFetchedAt", { value: lastFetchedAt }));
770
716
  }
771
717
  return 0;
772
718
  }
773
719
  const idWidth = Math.max(2, ...sources.map((s) => s.id.length));
774
720
  const kindWidth = Math.max(4, ...sources.map((s) => s.kind.length));
775
721
  const urlWidth = Math.max(3, ...sources.map((s) => s.url.length));
776
- log(`${pad("ID", idWidth)} ${pad("KIND", kindWidth)} ${pad("URL", urlWidth)} TAGS`);
722
+ log(`${pad(t("cli.source.listHeaderId"), idWidth)} ${pad(t("cli.source.listHeaderKind"), kindWidth)} ${pad(t("cli.source.listHeaderUrl"), urlWidth)} ${t("cli.source.listHeaderTags")}`);
777
723
  for (const s of sources) {
778
724
  log(`${pad(s.id, idWidth)} ${pad(s.kind, kindWidth)} ${pad(s.url, urlWidth)} ${s.tags.join(",")}`);
779
725
  }
@@ -810,34 +756,47 @@ export async function removeSource(args, options = {}) {
810
756
  const cwd = options.cwd ?? process.cwd();
811
757
  const log = options.io?.log ?? ((m) => console.log(m));
812
758
  const error = options.io?.error ?? ((m) => console.error(m));
759
+ let langState;
760
+ try {
761
+ langState = parseLangFlag(args);
762
+ }
763
+ catch (e) {
764
+ if (e instanceof LangFlagError) {
765
+ error(`source remove: ${e.message}`);
766
+ return 2;
767
+ }
768
+ throw e;
769
+ }
770
+ const locale = await resolveWorkspaceLocale({ flag: langState.flag, cwd, warn: error });
771
+ const t = createTranslator(locale);
813
772
  let parsed;
814
773
  try {
815
- parsed = parseRemoveArgs(args);
774
+ parsed = parseRemoveArgs(langState.rest);
816
775
  }
817
776
  catch (e) {
818
777
  error(`source remove: ${e instanceof Error ? e.message : String(e)}`);
819
778
  return 2;
820
779
  }
821
780
  if (parsed.help) {
822
- printRemoveHelp(log);
781
+ printRemoveHelp(t, log);
823
782
  return 0;
824
783
  }
825
784
  if (!parsed.id) {
826
- error("source remove: missing <id>");
827
- printRemoveHelp(error);
785
+ error(t("cli.source.missingId", { sub: "remove" }));
786
+ printRemoveHelp(t, error);
828
787
  return 2;
829
788
  }
830
789
  if (!isSafeSourceId(parsed.id)) {
831
- error(`source remove: invalid <id> '${parsed.id}' (must match [A-Za-z0-9][A-Za-z0-9._-]*)`);
790
+ error(t("cli.source.invalidId", { sub: "remove", id: parsed.id }));
832
791
  return 2;
833
792
  }
834
793
  const file = sourceFile(cwd, parsed.id);
835
794
  if (!(await pathExists(file))) {
836
- error(`source remove: '${parsed.id}' not found (sources/${parsed.id}.yaml)`);
795
+ error(t("cli.source.removeNotFound", { id: parsed.id }));
837
796
  return 1;
838
797
  }
839
798
  await unlink(file);
840
- log(`source remove: deleted sources/${parsed.id}.yaml`);
799
+ log(t("cli.source.deleted", { id: parsed.id }));
841
800
  return 0;
842
801
  }
843
802
  /**
@@ -869,25 +828,38 @@ export async function testSource(args, options = {}) {
869
828
  const log = options.io?.log ?? ((m) => console.log(m));
870
829
  const warn = options.io?.warn ?? ((m) => console.warn(m));
871
830
  const error = options.io?.error ?? ((m) => console.error(m));
831
+ let langState;
832
+ try {
833
+ langState = parseLangFlag(args);
834
+ }
835
+ catch (e) {
836
+ if (e instanceof LangFlagError) {
837
+ error(`source test: ${e.message}`);
838
+ return 2;
839
+ }
840
+ throw e;
841
+ }
842
+ const locale = await resolveWorkspaceLocale({ flag: langState.flag, cwd, warn: error });
843
+ const t = createTranslator(locale);
872
844
  let parsed;
873
845
  try {
874
- parsed = parseTestArgs(args);
846
+ parsed = parseTestArgs(langState.rest);
875
847
  }
876
848
  catch (e) {
877
849
  error(`source test: ${e instanceof Error ? e.message : String(e)}`);
878
850
  return 2;
879
851
  }
880
852
  if (parsed.help) {
881
- printTestHelp(log);
853
+ printTestHelp(t, log);
882
854
  return 0;
883
855
  }
884
856
  if (!parsed.id) {
885
- error("source test: missing <id>");
886
- printTestHelp(error);
857
+ error(t("cli.source.missingId", { sub: "test" }));
858
+ printTestHelp(t, error);
887
859
  return 2;
888
860
  }
889
861
  if (!isSafeSourceId(parsed.id)) {
890
- error(`source test: invalid <id> '${parsed.id}' (must match [A-Za-z0-9][A-Za-z0-9._-]*)`);
862
+ error(t("cli.source.invalidId", { sub: "test", id: parsed.id }));
891
863
  return 2;
892
864
  }
893
865
  // Check the YAML exists *before* delegating to watchRun. `watchRun` would
@@ -896,7 +868,7 @@ export async function testSource(args, options = {}) {
896
868
  // the user typed the id explicitly.
897
869
  const file = sourceFile(cwd, parsed.id);
898
870
  if (!(await pathExists(file))) {
899
- error(`source test: '${parsed.id}' not found (sources/${parsed.id}.yaml)`);
871
+ error(t("cli.source.testNotFound", { id: parsed.id }));
900
872
  return 1;
901
873
  }
902
874
  const limit = parsed.limit ?? 10;
@@ -918,6 +890,9 @@ export async function testSource(args, options = {}) {
918
890
  warn,
919
891
  error,
920
892
  progress,
893
+ // Localize the watch-flow progress markers on the same path as the
894
+ // reporter (#337).
895
+ translate: t,
921
896
  });
922
897
  }
923
898
  catch (e) {
@@ -934,8 +909,8 @@ export async function testSource(args, options = {}) {
934
909
  const fetched = stats?.fetched ?? 0;
935
910
  const filtered = stats?.filtered ?? matched.length;
936
911
  log("");
937
- log(`source test: ${parsed.id}`);
938
- log(` fetched: ${fetched} / filtered: ${filtered} / matched: ${matched.length}`);
912
+ log(t("cli.source.testHeading", { id: parsed.id }));
913
+ log(t("cli.source.testCounts", { fetched, filtered, matched: matched.length }));
939
914
  // Facet sweep notice (#256). A dry-run `source test` probes exactly ONE
940
915
  // facet value, so keyword verification only reflects that single slice. We
941
916
  // surface this as a non-fatal warning regardless of --show-content because
@@ -945,8 +920,11 @@ export async function testSource(args, options = {}) {
945
920
  // facet values were not walked.
946
921
  const facetSweep = result.diag[parsed.id]?.facetSweep;
947
922
  if (facetSweep) {
948
- warn(`source test: facet sweep 有効: ${facetSweep.facet}=${facetSweep.testedValue} のみ test 中(全 ${facetSweep.totalValues} 件の facet 値は walk しない)。` +
949
- `range facet は上端(最新値)を test します。全 facet 値を確認するには \`radar watch run --backfill\` を使用してください。`);
923
+ warn(t("cli.source.facetSweepNotice", {
924
+ facet: facetSweep.facet,
925
+ testedValue: facetSweep.testedValue,
926
+ totalValues: facetSweep.totalValues,
927
+ }));
950
928
  }
951
929
  // Render the adapter diag for `kind: json-api` when --show-content is on.
952
930
  // The diag block is intentionally gated behind --show-content so the
@@ -958,58 +936,69 @@ export async function testSource(args, options = {}) {
958
936
  if (diag) {
959
937
  if (diag.selectorAdoption) {
960
938
  log("");
961
- log(" selector adoption:");
939
+ log(t("cli.source.selectorAdoptionHeading"));
962
940
  for (const [field, path] of Object.entries(diag.selectorAdoption)) {
963
941
  if (path === null) {
964
- log(` ${field}: (no candidate matched)`);
942
+ log(t("cli.source.selectorNoCandidate", { field }));
965
943
  }
966
944
  else {
967
- log(` ${field} ${path} を採用`);
945
+ log(t("cli.source.selectorAdopted", { field, path }));
968
946
  }
969
947
  }
970
948
  }
971
949
  if (diag.paginationPreview) {
972
950
  const p = diag.paginationPreview;
973
951
  log("");
974
- log(" pagination preview (page 0 only — state not mutated):");
975
- log(` strategy: ${p.strategy}`);
976
- log(` nextUrl: ${p.nextUrl ?? "(end of pagination)"}`);
952
+ log(t("cli.source.paginationPreviewHeading"));
953
+ log(t("cli.source.paginationStrategy", { strategy: p.strategy }));
954
+ log(t("cli.source.paginationNextUrl", {
955
+ nextUrl: p.nextUrl ?? t("cli.source.paginationEndOfPagination"),
956
+ }));
977
957
  if (p.linkHeaderNext !== undefined) {
978
- log(` Link rel=next: ${p.linkHeaderNext ?? "(absent)"}`);
958
+ log(t("cli.source.paginationLinkNext", {
959
+ value: p.linkHeaderNext ?? t("cli.source.paginationAbsent"),
960
+ }));
979
961
  }
980
962
  if (p.nextCursor !== undefined) {
981
- log(` nextCursor: ${p.nextCursor ?? "(absent)"}`);
963
+ log(t("cli.source.paginationNextCursor", {
964
+ value: p.nextCursor ?? t("cli.source.paginationAbsent"),
965
+ }));
982
966
  }
983
967
  }
984
968
  }
985
969
  }
986
970
  if (matched.length === 0) {
987
- log(" (no matched items)");
971
+ log(t("cli.source.testNoMatched"));
988
972
  return 0;
989
973
  }
990
974
  const shown = matched.slice(0, limit);
975
+ const none = t("cli.source.valueNone");
991
976
  log("");
992
- log(`Showing ${shown.length} of ${matched.length} matched item(s):`);
977
+ log(t("cli.source.testShowing", { shown: shown.length, total: matched.length }));
993
978
  for (let i = 0; i < shown.length; i++) {
994
979
  const item = shown[i];
995
980
  if (!item)
996
981
  continue;
997
982
  log("");
998
- log(` ${i + 1}. ${item.title}`);
999
- log(` url: ${item.url}`);
1000
- log(` matchedKeywords: ${item.matchedKeywords.length > 0 ? item.matchedKeywords.join(",") : "-"}`);
983
+ log(t("cli.source.testItemTitle", { index: i + 1, title: item.title }));
984
+ log(t("cli.source.testItemUrl", { url: item.url }));
985
+ log(t("cli.source.testItemMatchedKeywords", {
986
+ value: item.matchedKeywords.length > 0 ? item.matchedKeywords.join(",") : none,
987
+ }));
1001
988
  if (parsed.showContent) {
1002
989
  const body = item.summary && item.summary.length > 0
1003
990
  ? item.summary
1004
991
  : typeof item.raw === "string"
1005
992
  ? item.raw
1006
993
  : "";
1007
- log(` content: ${body.length > 0 ? truncatePreview(body, 200) : "-"}`);
994
+ log(t("cli.source.testItemContent", {
995
+ value: body.length > 0 ? truncatePreview(body, 200) : none,
996
+ }));
1008
997
  }
1009
998
  }
1010
999
  if (matched.length > shown.length) {
1011
1000
  log("");
1012
- log(` … ${matched.length - shown.length} more (raise --limit to see them)`);
1001
+ log(t("cli.source.testMoreItems", { count: matched.length - shown.length }));
1013
1002
  }
1014
1003
  return 0;
1015
1004
  }
@@ -1027,14 +1016,28 @@ export async function testSource(args, options = {}) {
1027
1016
  * the recipe library is empty.
1028
1017
  */
1029
1018
  export async function recipesSubcommand(args, options = {}) {
1019
+ const cwd = options.cwd ?? process.cwd();
1030
1020
  const log = options.io?.log ?? ((m) => console.log(m));
1031
1021
  const error = options.io?.error ?? ((m) => console.error(m));
1032
- // The only flag accepted today is `-h` / `--help`. Keep the parser
1033
- // tiny rather than introducing a typed args struct for a single
1034
- // option — easier to extend if/when filters land.
1035
- for (const a of args) {
1022
+ let langState;
1023
+ try {
1024
+ langState = parseLangFlag(args);
1025
+ }
1026
+ catch (e) {
1027
+ if (e instanceof LangFlagError) {
1028
+ error(`source recipes: ${e.message}`);
1029
+ return 2;
1030
+ }
1031
+ throw e;
1032
+ }
1033
+ const locale = await resolveWorkspaceLocale({ flag: langState.flag, cwd, warn: error });
1034
+ const t = createTranslator(locale);
1035
+ // The only flag accepted today is `-h` / `--help` (plus the already-stripped
1036
+ // `--lang`). Keep the parser tiny rather than introducing a typed args struct
1037
+ // for a single option — easier to extend if/when filters land.
1038
+ for (const a of langState.rest) {
1036
1039
  if (a === "-h" || a === "--help") {
1037
- printRecipesHelp(log);
1040
+ printRecipesHelp(t, log);
1038
1041
  return 0;
1039
1042
  }
1040
1043
  if (a.startsWith("--")) {
@@ -1053,7 +1056,7 @@ export async function recipesSubcommand(args, options = {}) {
1053
1056
  return 1;
1054
1057
  }
1055
1058
  if (entries.length === 0) {
1056
- log("source recipes: no recipes bundled (recipes/ is empty or absent)");
1059
+ log(t("cli.source.recipesNone"));
1057
1060
  return 0;
1058
1061
  }
1059
1062
  const valid = entries.filter((e) => e.recipe !== null);
@@ -1061,7 +1064,7 @@ export async function recipesSubcommand(args, options = {}) {
1061
1064
  if (valid.length > 0) {
1062
1065
  const nameWidth = Math.max(4, ...valid.map((e) => e.name.length));
1063
1066
  const kindWidth = Math.max(4, ...valid.map((e) => (e.recipe ? e.recipe.kind.length : 0)));
1064
- log(`${pad("NAME", nameWidth)} ${pad("KIND", kindWidth)} DESCRIPTION`);
1067
+ log(`${pad(t("cli.source.recipesHeaderName"), nameWidth)} ${pad(t("cli.source.recipesHeaderKind"), kindWidth)} ${t("cli.source.recipesHeaderDescription")}`);
1065
1068
  for (const e of valid) {
1066
1069
  if (!e.recipe)
1067
1070
  continue;
@@ -1070,18 +1073,21 @@ export async function recipesSubcommand(args, options = {}) {
1070
1073
  }
1071
1074
  }
1072
1075
  else {
1073
- log("source recipes: no valid recipes found (all bundled entries failed to load)");
1076
+ log(t("cli.source.recipesNoValid"));
1074
1077
  }
1075
1078
  if (invalid.length > 0) {
1076
1079
  log("");
1077
- log("Recipes with errors:");
1080
+ log(t("cli.source.recipesErrorsHeading"));
1078
1081
  for (const e of invalid) {
1079
- log(` ${e.name}: ${e.error ?? "(unknown error)"}`);
1082
+ log(t("cli.source.recipesErrorRow", {
1083
+ name: e.name,
1084
+ error: e.error ?? t("cli.source.recipesErrorUnknown"),
1085
+ }));
1080
1086
  }
1081
1087
  }
1082
1088
  log("");
1083
- log("Apply a recipe with:");
1084
- log(" radar source add <id> --recipe <name> [--keywords <kw>] [--tags <t>] [--name <display>]");
1089
+ log(t("cli.source.recipesApplyHeading"));
1090
+ log(t("cli.source.recipesApplyExample"));
1085
1091
  // Returning 0 even when individual recipes have errors keeps the
1086
1092
  // listing useful in CI: a single malformed recipe should not break
1087
1093
  // the discovery command for the rest of the bundle.
@@ -1095,11 +1101,28 @@ export async function recipesSubcommand(args, options = {}) {
1095
1101
  * IO sinks without spawning the full CLI.
1096
1102
  */
1097
1103
  export async function runSource(args, options = {}) {
1104
+ const cwd = options.cwd ?? process.cwd();
1098
1105
  const log = options.io?.log ?? ((m) => console.log(m));
1099
1106
  const error = options.io?.error ?? ((m) => console.error(m));
1107
+ // Resolve the dispatcher help locale from any leading `--lang` (read-only;
1108
+ // each subcommand strips and resolves its own from `rest`).
1109
+ const dispatcherLangFlag = (() => {
1110
+ try {
1111
+ return parseLangFlag(args).flag;
1112
+ }
1113
+ catch {
1114
+ return undefined;
1115
+ }
1116
+ })();
1117
+ const dispatcherLocale = await resolveWorkspaceLocale({
1118
+ flag: dispatcherLangFlag,
1119
+ cwd,
1120
+ warn: error,
1121
+ });
1122
+ const t = createTranslator(dispatcherLocale);
1100
1123
  const [sub, ...rest] = args;
1101
1124
  if (!sub || sub === "-h" || sub === "--help" || sub === "help") {
1102
- printSourceHelp(log);
1125
+ printSourceHelp(t, log);
1103
1126
  return sub ? 0 : 2;
1104
1127
  }
1105
1128
  switch (sub) {
@@ -1114,14 +1137,15 @@ export async function runSource(args, options = {}) {
1114
1137
  case "test":
1115
1138
  return testSource(rest, options);
1116
1139
  default:
1117
- error(`source: unknown subcommand '${sub}'`);
1118
- printSourceHelp(error);
1140
+ error(t("cli.source.unknownSubcommand", { sub }));
1141
+ printSourceHelp(t, error);
1119
1142
  return 2;
1120
1143
  }
1121
1144
  }
1122
1145
  export const sourceCommand = {
1123
1146
  name: "source",
1124
1147
  summary: "Manage feed sources (add | list | recipes | remove | test)",
1148
+ summaryKey: "cli.summary.source",
1125
1149
  run: (args) => runSource(args),
1126
1150
  };
1127
1151
  //# sourceMappingURL=source.js.map