unicommand 0.0.1 → 0.0.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.
package/dist/index.js CHANGED
@@ -346,15 +346,23 @@ var quoteFish = (value) => {
346
346
  };
347
347
 
348
348
  // src/cli/help.ts
349
- var programExamples = /* @__PURE__ */ new WeakMap();
350
- var setProgramExamples = (program, examples) => {
351
- programExamples.set(program, examples);
349
+ var COMMAND_COLUMN_WIDTH = 36;
350
+ var ANSI_CYAN = "\x1B[36m";
351
+ var ANSI_GRAY = "\x1B[90m";
352
+ var ANSI_RESET = "\x1B[0m";
353
+ var programHelpData = /* @__PURE__ */ new WeakMap();
354
+ var setProgramHelpData = (program, data) => {
355
+ programHelpData.set(program, data);
352
356
  };
353
357
  var isGlobalHelpRequest = (args) => args.length === 0 || args.length === 1 && ["--help", "-h"].includes(args[0] ?? "");
354
358
  var printProgramHelp = async (program) => {
355
- const help = cleanHelp(await captureHelp(() => program.run(["--help"])));
356
- const examples = programExamples.get(program) ?? [];
357
- console.log([help.trimEnd(), formatExamples(examples)].filter(Boolean).join("\n\n"));
359
+ const data = programHelpData.get(program);
360
+ if (!data) throw new Error("Program help data was not registered");
361
+ const capturedHelp = cleanHelp(await captureHelp(() => program.run(["--help"])));
362
+ const capturedRows = parseCapturedCommandRows(capturedHelp);
363
+ const output = [formatHelp(data, capturedRows), formatExamples(data.examples)].filter(Boolean).join("\n");
364
+ console.log(`${output}
365
+ `);
358
366
  };
359
367
  async function captureHelp(callback) {
360
368
  const originalLog = console.log;
@@ -369,15 +377,130 @@ async function captureHelp(callback) {
369
377
  }
370
378
  return lines.join("\n");
371
379
  }
380
+ function formatHelp(data, capturedRows) {
381
+ return [
382
+ "",
383
+ ` ${data.binName} ${data.version} \u2014 ${data.description}`,
384
+ "",
385
+ heading("USAGE"),
386
+ " ",
387
+ ` \u25B8 ${data.binName} <command> [ARGUMENTS...] [OPTIONS...]`,
388
+ "",
389
+ heading("COMMANDS"),
390
+ "",
391
+ ...formatCommandRows(
392
+ mergeCommandRows(data.commands.filter(isVisibleCommand).map(commandHelpRow), capturedRows)
393
+ ),
394
+ "",
395
+ heading("GLOBAL OPTIONS"),
396
+ "",
397
+ " -h, --help Display global help or command-related help. ",
398
+ " -v, --version Show version"
399
+ ].join("\n");
400
+ }
401
+ function mergeCommandRows(primaryRows, fallbackRows) {
402
+ const primaryNames = new Set(primaryRows.map((row) => row.name));
403
+ return [...primaryRows, ...fallbackRows.filter((row) => !primaryNames.has(row.name))];
404
+ }
405
+ function parseCapturedCommandRows(help) {
406
+ const lines = help.split("\n");
407
+ const commandsIndex = lines.findIndex((line) => line.trim() === "COMMANDS");
408
+ const globalOptionsIndex = lines.findIndex((line) => line.trim() === "GLOBAL OPTIONS");
409
+ const hasCommandSection = commandsIndex >= 0 && globalOptionsIndex > commandsIndex;
410
+ if (!hasCommandSection) return [];
411
+ return parseCommandRows(lines.slice(commandsIndex + 1, globalOptionsIndex));
412
+ }
413
+ function parseCommandRows(lines) {
414
+ return lines.reduce((rows, line) => {
415
+ const row = parseCommandRow(line);
416
+ if (row) {
417
+ rows.push(row);
418
+ return rows;
419
+ }
420
+ const previous = rows.at(-1);
421
+ const continuation = line.trim();
422
+ if (!previous || !continuation) return rows;
423
+ previous.description = `${previous.description} ${continuation}`;
424
+ return rows;
425
+ }, []);
426
+ }
427
+ function parseCommandRow(line) {
428
+ const match = line.match(/^ {4}(.+?) {2,}(.+?)\s*$/);
429
+ if (!match?.[1] || !match[2]) return void 0;
430
+ return { name: match[1].trim(), description: match[2].trim() };
431
+ }
372
432
  function cleanHelp(help) {
373
433
  return help.replace(
374
434
  /COMMANDS\s+—\s+Type '[^']+ help <command>' to get some help about a command/g,
375
435
  "COMMANDS"
376
436
  );
377
437
  }
438
+ function commandHelpRow(command) {
439
+ return { name: command.name, description: command.description };
440
+ }
441
+ function isVisibleCommand(command) {
442
+ return command.config?.visible !== false;
443
+ }
444
+ function formatCommandRows(rows) {
445
+ const groups = commandGroups(rows);
446
+ return groups.flatMap((group) => formatCommandGroup(group));
447
+ }
448
+ function commandGroups(rows) {
449
+ const visibleRows = rows.filter((row) => !isHiddenCommand(row));
450
+ const parentRows = new Map(
451
+ visibleRows.filter((row) => !row.name.includes(" ")).map((row) => [row.name, row])
452
+ );
453
+ const childRows = visibleRows.filter((row) => row.name.includes(" "));
454
+ const childParents = new Set(childRows.map((row) => row.name.split(" ")[0]).filter(Boolean));
455
+ const groupedParents = [...childParents].map((parent) => ({
456
+ children: childRows.filter((row) => row.name.startsWith(`${parent} `)),
457
+ parent: parentRows.get(parent) ?? { name: parent, description: `${parent} commands` }
458
+ }));
459
+ const standaloneRows = visibleRows.filter(
460
+ (row) => !childParents.has(row.name.split(" ")[0] ?? row.name)
461
+ );
462
+ return [...groupedParents, ...standaloneRows.map((parent) => ({ children: [], parent }))];
463
+ }
464
+ function isHiddenCommand(row) {
465
+ return row.name === "on-checkout" || row.name === "on-commit";
466
+ }
467
+ function formatCommandGroup({
468
+ children,
469
+ parent
470
+ }) {
471
+ if (children.length === 0) return [formatCommandLine(parent.name, parent.description)];
472
+ return [
473
+ formatCommandLine(parent.name, parentDescription(parent), 4, isSyntheticParent(parent)),
474
+ ...children.map(
475
+ (child) => formatCommandLine(child.name.split(" ").slice(1).join(" "), child.description, 6)
476
+ )
477
+ ];
478
+ }
479
+ function parentDescription(parent) {
480
+ return isSyntheticParent(parent) ? "" : parent.description;
481
+ }
482
+ function isSyntheticParent(parent) {
483
+ return parent.description === `${parent.name} commands`;
484
+ }
485
+ function formatCommandLine(name, description, indent = 4, muted = false) {
486
+ const paddedName = name.padEnd(COMMAND_COLUMN_WIDTH - indent);
487
+ const displayName = muted ? gray(paddedName) : paddedName;
488
+ return `${" ".repeat(indent)}${displayName}${description}`.trimEnd();
489
+ }
490
+ function heading(text) {
491
+ return ` ${color(text)}`;
492
+ }
493
+ function color(text) {
494
+ if (!process.stdout.isTTY || process.env.NO_COLOR) return text;
495
+ return `${ANSI_CYAN}${text}${ANSI_RESET}`;
496
+ }
497
+ function gray(text) {
498
+ if (!process.stdout.isTTY || process.env.NO_COLOR) return text;
499
+ return `${ANSI_GRAY}${text}${ANSI_RESET}`;
500
+ }
378
501
  function formatExamples(examples) {
379
502
  if (examples.length === 0) return "";
380
- return [" EXAMPLES", "", ...examples.map((example) => ` ${example}`)].join("\n");
503
+ return [heading("EXAMPLES"), "", ...examples.map((example) => ` ${example}`), ""].join("\n");
381
504
  }
382
505
 
383
506
  // src/cli/program.ts
@@ -433,6 +556,134 @@ var isCommandAdapterExport = (value) => {
433
556
  return typeof command.cli === "function";
434
557
  };
435
558
 
559
+ // src/command/metadata.ts
560
+ import { z } from "zod";
561
+ var isCommandMcpToolMetadata = (metadata) => metadata !== void 0 && metadata !== false && metadata.kind === "tool";
562
+ var isCommandMcpResourceMetadata = (metadata) => metadata !== void 0 && metadata !== false && metadata.kind === "resource";
563
+ var defineCommand = (definition) => {
564
+ const command = {
565
+ outputSchema: z.unknown(),
566
+ ...definition
567
+ };
568
+ return {
569
+ ...command,
570
+ inputSchema: definition.inputSchema ?? commandDefinitionToZodInputSchema(command)
571
+ };
572
+ };
573
+ var registerCommand = (program, definition) => {
574
+ const command = definition.arguments?.reduce(
575
+ (currentCommand, argument) => currentCommand.argument(argument.synopsis, argument.description),
576
+ program.command(definition.name, definition.description, definition.config)
577
+ ) ?? program.command(definition.name, definition.description, definition.config);
578
+ const commandWithOptions = definition.options?.reduce(
579
+ (currentCommand, option) => currentCommand.option(commandOptionSynopsis(option), option.description, option.config),
580
+ command
581
+ ) ?? command;
582
+ const registeredCommand = definition.aliases?.reduce(
583
+ (currentCommand, alias) => currentCommand.alias(alias),
584
+ commandWithOptions
585
+ ) ?? commandWithOptions;
586
+ setCommandMetadata(registeredCommand, definition);
587
+ return registeredCommand;
588
+ };
589
+ var commandInputValue = (args, name) => args[name];
590
+ var commandInputString = (args, name) => {
591
+ const value = commandInputValue(args, name);
592
+ return value === void 0 ? "" : String(value);
593
+ };
594
+ var commandActionInput = (definition, args, options) => normalizeCommandInputAliases(definition, {
595
+ ...normalizeArgumentInput(definition, args),
596
+ ...options
597
+ });
598
+ var normalizeCommandInputAliases = (definition, input) => ({
599
+ ...input,
600
+ ...Object.fromEntries(
601
+ (definition.options ?? []).flatMap((option) => {
602
+ const name = optionInputName(option);
603
+ if (input[name] !== void 0) return [];
604
+ const alias = optionAliasInputNames(option).find(
605
+ (candidate) => input[candidate] !== void 0
606
+ );
607
+ return alias ? [[name, input[alias]]] : [];
608
+ })
609
+ )
610
+ });
611
+ var normalizeArgumentInput = (definition, args) => ({
612
+ ...args,
613
+ ...Object.fromEntries(
614
+ (definition.arguments ?? []).map((argument) => {
615
+ const name = argumentName(argument.synopsis);
616
+ const value = commandInputValue(args, name);
617
+ return value === void 0 ? void 0 : [name, value];
618
+ }).filter((entry) => entry !== void 0)
619
+ )
620
+ });
621
+ var commandDefinitionToZodInputSchema = (definition) => {
622
+ const jsonSchema = commandDefinitionToInputSchema(definition);
623
+ const shape = Object.fromEntries(
624
+ Object.entries(jsonSchema.properties).map(([name, property]) => [
625
+ name,
626
+ zodInputProperty(property, jsonSchema.required.includes(name))
627
+ ])
628
+ );
629
+ return z.object(shape);
630
+ };
631
+ var commandDefinitionToInputSchema = (definition) => ({
632
+ type: "object",
633
+ properties: Object.fromEntries([
634
+ ...(definition.arguments ?? []).map((argument) => [
635
+ argumentName(argument.synopsis),
636
+ argumentSchema(argument)
637
+ ]),
638
+ ...(definition.options ?? []).map((option) => [
639
+ optionInputName(option),
640
+ option.schema ?? optionSchema(option)
641
+ ])
642
+ ]),
643
+ required: [
644
+ ...(definition.arguments ?? []).filter((argument) => isRequiredSynopsis(argument.synopsis)).map((argument) => argumentName(argument.synopsis)),
645
+ ...(definition.options ?? []).filter((option) => option.config?.required).map(optionInputName)
646
+ ],
647
+ additionalProperties: false
648
+ });
649
+ var zodInputProperty = (property, required) => {
650
+ const schema = zodProperty(property);
651
+ const describedSchema = property.description ? schema.describe(property.description) : schema;
652
+ return required ? describedSchema : describedSchema.optional();
653
+ };
654
+ var zodProperty = (property) => {
655
+ if (property.type === "array") return z.array(z.string());
656
+ if (property.type === "boolean") return z.boolean();
657
+ if (property.type === "number") return z.number();
658
+ return z.string();
659
+ };
660
+ var argumentSchema = (argument) => ({
661
+ type: isVariadicSynopsis(argument.synopsis) ? "array" : "string",
662
+ description: argument.description,
663
+ ...isVariadicSynopsis(argument.synopsis) ? { items: { type: "string" } } : {}
664
+ });
665
+ var optionSchema = (option) => ({
666
+ type: option.value ? "string" : "boolean",
667
+ description: option.description
668
+ });
669
+ var argumentName = (synopsis) => {
670
+ return kebabToCamel(
671
+ synopsis.replace(/[<>[\].]/g, "").replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase())
672
+ );
673
+ };
674
+ var commandOptionSynopsis = (option) => {
675
+ const longName = `${option.negated ? "--no-" : "--"}${option.name}`;
676
+ const aliases = (option.aliases ?? []).map(
677
+ (alias) => alias.length === 1 ? `-${alias}` : `--${alias}`
678
+ );
679
+ return [...aliases, `${longName}${option.value ? ` ${option.value}` : ""}`].join(", ");
680
+ };
681
+ var optionInputName = (option) => kebabToCamel(option.name);
682
+ var optionAliasInputNames = (option) => (option.aliases ?? []).map(kebabToCamel);
683
+ var kebabToCamel = (value) => value.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
684
+ var isRequiredSynopsis = (synopsis) => synopsis.trim().startsWith("<");
685
+ var isVariadicSynopsis = (synopsis) => synopsis.includes("...");
686
+
436
687
  // src/constants.ts
437
688
  var DEFAULT_COMMANDS_DIR = "commands";
438
689
  var PROGRAM_NAME_ENV_SUFFIX = "_PROG_NAME";
@@ -574,10 +825,23 @@ var createCommandProgram = async ({
574
825
  });
575
826
  registerUpdateCommand(program, { isDevRun, packageName: packageName ?? binName, version });
576
827
  registerCompletionCommand(program, { binNames });
577
- setProgramExamples(
578
- program,
579
- commandDefinitions.flatMap((command) => command.examples?.(binName) ?? [])
580
- );
828
+ setProgramHelpData(program, {
829
+ binName,
830
+ commands: [
831
+ ...commandDefinitions,
832
+ defineCommand({
833
+ name: "update",
834
+ description: `Update ${packageName ?? binName} to latest version`
835
+ }),
836
+ defineCommand({
837
+ name: "completion",
838
+ description: "Generate shell completion scripts"
839
+ })
840
+ ],
841
+ description,
842
+ examples: commandDefinitions.flatMap((command) => command.examples?.(binName) ?? []),
843
+ version
844
+ });
581
845
  return program;
582
846
  };
583
847
  var registerParentCommandsFromDefinitions = (program, definitions, options = {}) => {
@@ -817,134 +1081,6 @@ var createCommandCliRunner = ({
817
1081
  // src/mcp/router.ts
818
1082
  import { z as z3 } from "zod";
819
1083
 
820
- // src/command/metadata.ts
821
- import { z } from "zod";
822
- var isCommandMcpToolMetadata = (metadata) => metadata !== void 0 && metadata !== false && metadata.kind === "tool";
823
- var isCommandMcpResourceMetadata = (metadata) => metadata !== void 0 && metadata !== false && metadata.kind === "resource";
824
- var defineCommand = (definition) => {
825
- const command = {
826
- outputSchema: z.unknown(),
827
- ...definition
828
- };
829
- return {
830
- ...command,
831
- inputSchema: definition.inputSchema ?? commandDefinitionToZodInputSchema(command)
832
- };
833
- };
834
- var registerCommand = (program, definition) => {
835
- const command = definition.arguments?.reduce(
836
- (currentCommand, argument) => currentCommand.argument(argument.synopsis, argument.description),
837
- program.command(definition.name, definition.description, definition.config)
838
- ) ?? program.command(definition.name, definition.description, definition.config);
839
- const commandWithOptions = definition.options?.reduce(
840
- (currentCommand, option) => currentCommand.option(commandOptionSynopsis(option), option.description, option.config),
841
- command
842
- ) ?? command;
843
- const registeredCommand = definition.aliases?.reduce(
844
- (currentCommand, alias) => currentCommand.alias(alias),
845
- commandWithOptions
846
- ) ?? commandWithOptions;
847
- setCommandMetadata(registeredCommand, definition);
848
- return registeredCommand;
849
- };
850
- var commandInputValue = (args, name) => args[name];
851
- var commandInputString = (args, name) => {
852
- const value = commandInputValue(args, name);
853
- return value === void 0 ? "" : String(value);
854
- };
855
- var commandActionInput = (definition, args, options) => normalizeCommandInputAliases(definition, {
856
- ...normalizeArgumentInput(definition, args),
857
- ...options
858
- });
859
- var normalizeCommandInputAliases = (definition, input) => ({
860
- ...input,
861
- ...Object.fromEntries(
862
- (definition.options ?? []).flatMap((option) => {
863
- const name = optionInputName(option);
864
- if (input[name] !== void 0) return [];
865
- const alias = optionAliasInputNames(option).find(
866
- (candidate) => input[candidate] !== void 0
867
- );
868
- return alias ? [[name, input[alias]]] : [];
869
- })
870
- )
871
- });
872
- var normalizeArgumentInput = (definition, args) => ({
873
- ...args,
874
- ...Object.fromEntries(
875
- (definition.arguments ?? []).map((argument) => {
876
- const name = argumentName(argument.synopsis);
877
- const value = commandInputValue(args, name);
878
- return value === void 0 ? void 0 : [name, value];
879
- }).filter((entry) => entry !== void 0)
880
- )
881
- });
882
- var commandDefinitionToZodInputSchema = (definition) => {
883
- const jsonSchema = commandDefinitionToInputSchema(definition);
884
- const shape = Object.fromEntries(
885
- Object.entries(jsonSchema.properties).map(([name, property]) => [
886
- name,
887
- zodInputProperty(property, jsonSchema.required.includes(name))
888
- ])
889
- );
890
- return z.object(shape);
891
- };
892
- var commandDefinitionToInputSchema = (definition) => ({
893
- type: "object",
894
- properties: Object.fromEntries([
895
- ...(definition.arguments ?? []).map((argument) => [
896
- argumentName(argument.synopsis),
897
- argumentSchema(argument)
898
- ]),
899
- ...(definition.options ?? []).map((option) => [
900
- optionInputName(option),
901
- option.schema ?? optionSchema(option)
902
- ])
903
- ]),
904
- required: [
905
- ...(definition.arguments ?? []).filter((argument) => isRequiredSynopsis(argument.synopsis)).map((argument) => argumentName(argument.synopsis)),
906
- ...(definition.options ?? []).filter((option) => option.config?.required).map(optionInputName)
907
- ],
908
- additionalProperties: false
909
- });
910
- var zodInputProperty = (property, required) => {
911
- const schema = zodProperty(property);
912
- const describedSchema = property.description ? schema.describe(property.description) : schema;
913
- return required ? describedSchema : describedSchema.optional();
914
- };
915
- var zodProperty = (property) => {
916
- if (property.type === "array") return z.array(z.string());
917
- if (property.type === "boolean") return z.boolean();
918
- if (property.type === "number") return z.number();
919
- return z.string();
920
- };
921
- var argumentSchema = (argument) => ({
922
- type: isVariadicSynopsis(argument.synopsis) ? "array" : "string",
923
- description: argument.description,
924
- ...isVariadicSynopsis(argument.synopsis) ? { items: { type: "string" } } : {}
925
- });
926
- var optionSchema = (option) => ({
927
- type: option.value ? "string" : "boolean",
928
- description: option.description
929
- });
930
- var argumentName = (synopsis) => {
931
- return kebabToCamel(
932
- synopsis.replace(/[<>[\].]/g, "").replace(/_([a-z])/g, (_match, letter) => letter.toUpperCase())
933
- );
934
- };
935
- var commandOptionSynopsis = (option) => {
936
- const longName = `${option.negated ? "--no-" : "--"}${option.name}`;
937
- const aliases = (option.aliases ?? []).map(
938
- (alias) => alias.length === 1 ? `-${alias}` : `--${alias}`
939
- );
940
- return [...aliases, `${longName}${option.value ? ` ${option.value}` : ""}`].join(", ");
941
- };
942
- var optionInputName = (option) => kebabToCamel(option.name);
943
- var optionAliasInputNames = (option) => (option.aliases ?? []).map(kebabToCamel);
944
- var kebabToCamel = (value) => value.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
945
- var isRequiredSynopsis = (synopsis) => synopsis.trim().startsWith("<");
946
- var isVariadicSynopsis = (synopsis) => synopsis.includes("...");
947
-
948
1084
  // src/mcp/constants.ts
949
1085
  var DEFAULT_MCP_SERVER_VERSION = "0.0.0";
950
1086
  var DEFAULT_RESOURCE_MIME_TYPE = "application/json";
@@ -1265,7 +1401,7 @@ var generateReadmeCommandDocs = async ({
1265
1401
  writeFileSync(readmePath, replaceMarker(readme, markerName, generated), "utf8");
1266
1402
  };
1267
1403
  var formatCommandDocs = (commands, binName) => {
1268
- const visibleCommands = commands.filter(isVisibleCommand).sort(commandSort);
1404
+ const visibleCommands = commands.filter(isVisibleCommand2).sort(commandSort);
1269
1405
  const groups = [...parentCommands2(visibleCommands)].map(
1270
1406
  ([parent, subcommands]) => parentCommandDocs(parent, subcommands, binName)
1271
1407
  );
@@ -1276,7 +1412,7 @@ var formatCommandDocs = (commands, binName) => {
1276
1412
  );
1277
1413
  return ["```sh", ...lines, "```"].join("\n");
1278
1414
  };
1279
- var isVisibleCommand = (command) => command.config?.visible !== false;
1415
+ var isVisibleCommand2 = (command) => command.config?.visible !== false;
1280
1416
  var commandSort = (left, right) => (left.order ?? 0) - (right.order ?? 0) || left.name.localeCompare(right.name);
1281
1417
  var parentCommands2 = (commands) => {
1282
1418
  const groups = /* @__PURE__ */ new Map();
@@ -1658,7 +1794,7 @@ export {
1658
1794
  registerParentCommandsFromDefinitions,
1659
1795
  registerUpdateCommand,
1660
1796
  resourceResult,
1661
- setProgramExamples,
1797
+ setProgramHelpData,
1662
1798
  shortPackageName,
1663
1799
  startCommandMcpServer,
1664
1800
  startPackageCommandMcpServer,