padrone 1.3.0 → 1.5.0

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 (82) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +105 -284
  3. package/dist/{args-DFEI7_G_.mjs → args-D5PNDyNu.mjs} +46 -21
  4. package/dist/args-D5PNDyNu.mjs.map +1 -0
  5. package/dist/chunk-CjcI7cDX.mjs +15 -0
  6. package/dist/codegen/index.d.mts +28 -3
  7. package/dist/codegen/index.d.mts.map +1 -1
  8. package/dist/codegen/index.mjs +169 -19
  9. package/dist/codegen/index.mjs.map +1 -1
  10. package/dist/command-utils-B1D-HqCd.mjs +1117 -0
  11. package/dist/command-utils-B1D-HqCd.mjs.map +1 -0
  12. package/dist/completion.d.mts +1 -1
  13. package/dist/completion.d.mts.map +1 -1
  14. package/dist/completion.mjs +77 -29
  15. package/dist/completion.mjs.map +1 -1
  16. package/dist/docs/index.d.mts +22 -2
  17. package/dist/docs/index.d.mts.map +1 -1
  18. package/dist/docs/index.mjs +94 -7
  19. package/dist/docs/index.mjs.map +1 -1
  20. package/dist/errors-BiVrBgi6.mjs +114 -0
  21. package/dist/errors-BiVrBgi6.mjs.map +1 -0
  22. package/dist/{formatter-XroimS3Q.d.mts → formatter-DtHzbP22.d.mts} +35 -5
  23. package/dist/formatter-DtHzbP22.d.mts.map +1 -0
  24. package/dist/help-bbmu9-qd.mjs +735 -0
  25. package/dist/help-bbmu9-qd.mjs.map +1 -0
  26. package/dist/index.d.mts +32 -3
  27. package/dist/index.d.mts.map +1 -1
  28. package/dist/index.mjs +495 -267
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/mcp-mLWIdUIu.mjs +379 -0
  31. package/dist/mcp-mLWIdUIu.mjs.map +1 -0
  32. package/dist/serve-B0u43DK7.mjs +404 -0
  33. package/dist/serve-B0u43DK7.mjs.map +1 -0
  34. package/dist/stream-BcC146Ud.mjs +56 -0
  35. package/dist/stream-BcC146Ud.mjs.map +1 -0
  36. package/dist/test.d.mts +1 -1
  37. package/dist/test.mjs +4 -15
  38. package/dist/test.mjs.map +1 -1
  39. package/dist/{types-BS7RP5Ls.d.mts → types-Ch8Mk6Qb.d.mts} +311 -63
  40. package/dist/types-Ch8Mk6Qb.d.mts.map +1 -0
  41. package/dist/{update-check-EbNDkzyV.mjs → update-check-CFX1FV3v.mjs} +2 -2
  42. package/dist/{update-check-EbNDkzyV.mjs.map → update-check-CFX1FV3v.mjs.map} +1 -1
  43. package/dist/zod.d.mts +32 -0
  44. package/dist/zod.d.mts.map +1 -0
  45. package/dist/zod.mjs +50 -0
  46. package/dist/zod.mjs.map +1 -0
  47. package/package.json +10 -2
  48. package/src/args.ts +76 -44
  49. package/src/cli/docs.ts +1 -7
  50. package/src/cli/doctor.ts +195 -10
  51. package/src/cli/index.ts +1 -1
  52. package/src/cli/init.ts +2 -3
  53. package/src/cli/link.ts +2 -2
  54. package/src/codegen/discovery.ts +80 -28
  55. package/src/codegen/index.ts +2 -1
  56. package/src/codegen/parsers/bash.ts +179 -0
  57. package/src/codegen/schema-to-code.ts +2 -1
  58. package/src/colorizer.ts +126 -13
  59. package/src/command-utils.ts +401 -23
  60. package/src/completion.ts +120 -47
  61. package/src/create.ts +483 -130
  62. package/src/docs/index.ts +122 -8
  63. package/src/formatter.ts +173 -125
  64. package/src/help.ts +46 -12
  65. package/src/index.ts +29 -1
  66. package/src/interactive.ts +45 -4
  67. package/src/mcp.ts +390 -0
  68. package/src/repl-loop.ts +16 -3
  69. package/src/runtime.ts +195 -2
  70. package/src/serve.ts +442 -0
  71. package/src/stream.ts +75 -0
  72. package/src/test.ts +7 -16
  73. package/src/type-utils.ts +28 -4
  74. package/src/types.ts +212 -30
  75. package/src/wrap.ts +23 -25
  76. package/src/zod.ts +50 -0
  77. package/dist/args-DFEI7_G_.mjs.map +0 -1
  78. package/dist/chunk-y_GBKt04.mjs +0 -5
  79. package/dist/formatter-XroimS3Q.d.mts.map +0 -1
  80. package/dist/help-CgGP7hQU.mjs +0 -1229
  81. package/dist/help-CgGP7hQU.mjs.map +0 -1
  82. package/dist/types-BS7RP5Ls.d.mts.map +0 -1
package/src/docs/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, join, resolve } from 'node:path';
3
- import { commandSymbol } from '../command-utils.ts';
3
+ import { getCommand } from '../command-utils.ts';
4
4
  import type { HelpArgumentInfo, HelpInfo, HelpPositionalInfo, HelpSubcommandInfo } from '../formatter.ts';
5
5
  import { getHelpInfo } from '../help.ts';
6
6
  import type { AnyPadroneCommand } from '../types.ts';
@@ -220,6 +220,18 @@ function generateMarkdownPage(info: HelpInfo, depth: number, frontmatterFn?: Doc
220
220
  lines.push('```');
221
221
  lines.push('');
222
222
 
223
+ // Examples
224
+ if (info.examples?.length) {
225
+ lines.push('## Examples');
226
+ lines.push('');
227
+ lines.push('```');
228
+ for (const ex of info.examples) {
229
+ lines.push(`$ ${ex}`);
230
+ }
231
+ lines.push('```');
232
+ lines.push('');
233
+ }
234
+
223
235
  // Subcommands
224
236
  if (info.subcommands?.length) {
225
237
  const visibleSubs = info.subcommands.filter((s) => !s.hidden);
@@ -307,6 +319,12 @@ function generateHtmlPage(info: HelpInfo, depth: number): string {
307
319
  sections.push(' <h2>Usage</h2>');
308
320
  sections.push(` <pre><code>${escapeHtml(usageParts.join(' '))}</code></pre>`);
309
321
 
322
+ // Examples
323
+ if (info.examples?.length) {
324
+ sections.push(' <h2>Examples</h2>');
325
+ sections.push(` <pre><code>${info.examples.map((ex) => `$ ${escapeHtml(ex)}`).join('\n')}</code></pre>`);
326
+ }
327
+
310
328
  // Subcommands
311
329
  if (info.subcommands?.length) {
312
330
  const visibleSubs = info.subcommands.filter((s) => !s.hidden);
@@ -381,7 +399,7 @@ function generateHtmlPage(info: HelpInfo, depth: number): string {
381
399
  }
382
400
 
383
401
  // ============================================================================
384
- // Man Page Generator
402
+ // Man Page Generator (experimental)
385
403
  // ============================================================================
386
404
 
387
405
  function escapeMan(text: string): string {
@@ -418,6 +436,15 @@ function generateManPage(info: HelpInfo, _depth: number, programName: string): s
418
436
  lines.push(escapeMan(info.description));
419
437
  }
420
438
 
439
+ // EXAMPLES
440
+ if (info.examples?.length) {
441
+ lines.push('.SH EXAMPLES');
442
+ for (const ex of info.examples) {
443
+ lines.push('.PP');
444
+ lines.push(`.nf\n$ ${escapeMan(ex)}\n.fi`);
445
+ }
446
+ }
447
+
421
448
  // COMMANDS
422
449
  if (info.subcommands?.length) {
423
450
  const visibleSubs = info.subcommands.filter((s) => !s.hidden);
@@ -517,11 +544,6 @@ function generateMarkdownIndex(rootInfo: HelpInfo, allInfos: HelpInfo[]): string
517
544
  // Main Entry Point
518
545
  // ============================================================================
519
546
 
520
- function resolveCommand(programOrCommand: object): AnyPadroneCommand {
521
- if (commandSymbol in programOrCommand) return (programOrCommand as any)[commandSymbol] as AnyPadroneCommand;
522
- return programOrCommand as AnyPadroneCommand;
523
- }
524
-
525
547
  /**
526
548
  * Generate documentation for a Padrone CLI program or command tree.
527
549
  * Accepts either a PadroneProgram (from createPadrone()) or a raw AnyPadroneCommand.
@@ -529,7 +551,7 @@ function resolveCommand(programOrCommand: object): AnyPadroneCommand {
529
551
  export function generateDocs(program: object, options: DocsOptions = {}): DocsResult {
530
552
  const { format = 'markdown', output, includeHidden = false, frontmatter, overwrite = true, dryRun = false } = options;
531
553
 
532
- const cmd = resolveCommand(program);
554
+ const cmd = getCommand(program);
533
555
  const allInfos = collectAllHelpInfo(cmd, includeHidden);
534
556
  const rootInfo = allInfos[0]!;
535
557
  const programName = cmd.name || 'program';
@@ -605,3 +627,95 @@ export function generateDocs(program: object, options: DocsOptions = {}): DocsRe
605
627
 
606
628
  return result;
607
629
  }
630
+
631
+ // ============================================================================
632
+ // Man Page Installation
633
+ // ============================================================================
634
+
635
+ export type SetupManPagesResult = {
636
+ /** Directory where man pages were written. */
637
+ dir: string;
638
+ /** Man page files that were written. */
639
+ written: string[];
640
+ /** Whether existing pages were overwritten (true) or newly created (false). */
641
+ updated: boolean;
642
+ };
643
+
644
+ /**
645
+ * Returns the local man page directory for the given section.
646
+ * Uses `~/.local/share/man/man<section>` (XDG convention).
647
+ */
648
+ function getManPageDir(section = 1): string {
649
+ const { homedir } = require('node:os') as typeof import('node:os');
650
+ const { join: joinPath } = require('node:path') as typeof import('node:path');
651
+ return joinPath(process.env.XDG_DATA_HOME || joinPath(homedir(), '.local', 'share'), 'man', `man${section}`);
652
+ }
653
+
654
+ /**
655
+ * Converts a command name to a man page filename.
656
+ * "myapp" → "myapp.1", "myapp deploy" → "myapp-deploy.1"
657
+ */
658
+ function manPageFilename(commandName: string, section = 1): string {
659
+ return `${commandName.replace(/\s+/g, '-')}.${section}`;
660
+ }
661
+
662
+ /**
663
+ * Installs man pages for a Padrone CLI program into the local man directory.
664
+ * Generates man pages for all commands and writes them to `~/.local/share/man/man1/`.
665
+ *
666
+ * After installation, `man <program>` and `man <program>-<subcommand>` should work
667
+ * (assuming `~/.local/share/man` is in `MANPATH` or `manpath` picks it up).
668
+ */
669
+ export function setupManPages(program: object): SetupManPagesResult {
670
+ const cmd = getCommand(program);
671
+ const allInfos = collectAllHelpInfo(cmd, false);
672
+ const programName = cmd.name || 'program';
673
+ const manDir = getManPageDir(1);
674
+
675
+ mkdirSync(manDir, { recursive: true });
676
+
677
+ const written: string[] = [];
678
+ let updated = false;
679
+
680
+ for (let i = 0; i < allInfos.length; i++) {
681
+ const info = allInfos[i]!;
682
+ const depth = i === 0 ? 0 : info.name.split(/\s+/).length;
683
+ const commandName = info.name === '<root>' || !info.name ? programName : info.name;
684
+ const filename = manPageFilename(commandName);
685
+ const fullPath = join(manDir, filename);
686
+
687
+ if (existsSync(fullPath)) updated = true;
688
+
689
+ const content = generateManPage(info, depth, programName);
690
+ writeFileSync(fullPath, content, 'utf-8');
691
+ written.push(filename);
692
+ }
693
+
694
+ return { dir: manDir, written, updated };
695
+ }
696
+
697
+ /**
698
+ * Removes installed man pages for a Padrone CLI program.
699
+ */
700
+ export function removeManPages(program: object): { dir: string; removed: string[] } {
701
+ const { unlinkSync } = require('node:fs') as typeof import('node:fs');
702
+ const cmd = getCommand(program);
703
+ const allInfos = collectAllHelpInfo(cmd, false);
704
+ const programName = cmd.name || 'program';
705
+ const manDir = getManPageDir(1);
706
+ const removed: string[] = [];
707
+
708
+ for (let i = 0; i < allInfos.length; i++) {
709
+ const info = allInfos[i]!;
710
+ const commandName = info.name === '<root>' || !info.name ? programName : info.name;
711
+ const filename = manPageFilename(commandName);
712
+ const fullPath = join(manDir, filename);
713
+
714
+ if (existsSync(fullPath)) {
715
+ unlinkSync(fullPath);
716
+ removed.push(filename);
717
+ }
718
+ }
719
+
720
+ return { dir: manDir, removed };
721
+ }