goke 6.9.0 → 6.11.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 (40) hide show
  1. package/dist/__test__/completions.test.d.ts +9 -0
  2. package/dist/__test__/completions.test.d.ts.map +1 -0
  3. package/dist/__test__/completions.test.js +774 -0
  4. package/dist/__test__/index.test.js +436 -308
  5. package/dist/__test__/just-bash.test.js +7 -7
  6. package/dist/__test__/readme-examples.test.js +149 -13
  7. package/dist/__test__/types.test-d.js +27 -0
  8. package/dist/agents.d.ts +38 -0
  9. package/dist/agents.d.ts.map +1 -0
  10. package/dist/agents.js +63 -0
  11. package/dist/completions.d.ts +88 -0
  12. package/dist/completions.d.ts.map +1 -0
  13. package/dist/completions.js +315 -0
  14. package/dist/goke.d.ts +95 -5
  15. package/dist/goke.d.ts.map +1 -1
  16. package/dist/goke.js +487 -4
  17. package/dist/index.d.ts +9 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +8 -1
  20. package/dist/just-bash.d.ts.map +1 -1
  21. package/dist/just-bash.js +1 -2
  22. package/dist/runtime-browser.d.ts +1 -1
  23. package/dist/runtime-browser.d.ts.map +1 -1
  24. package/dist/runtime-browser.js +1 -1
  25. package/dist/runtime-node.d.ts +1 -1
  26. package/dist/runtime-node.d.ts.map +1 -1
  27. package/dist/runtime-node.js +22 -13
  28. package/package.json +1 -1
  29. package/src/__test__/completions.test.ts +902 -0
  30. package/src/__test__/index.test.ts +471 -308
  31. package/src/__test__/just-bash.test.ts +7 -7
  32. package/src/__test__/readme-examples.test.ts +161 -13
  33. package/src/__test__/types.test-d.ts +27 -0
  34. package/src/agents.ts +101 -0
  35. package/src/completions.ts +363 -0
  36. package/src/goke.ts +540 -8
  37. package/src/index.ts +11 -2
  38. package/src/just-bash.ts +1 -2
  39. package/src/runtime-browser.ts +1 -1
  40. package/src/runtime-node.ts +19 -11
package/dist/goke.js CHANGED
@@ -13,6 +13,7 @@ import pc from './picocolors.js';
13
13
  import mri from "./mri.js";
14
14
  import { GokeError, coerceBySchema, extractJsonSchema, extractSchemaMetadata, isStandardSchema } from "./coerce.js";
15
15
  import { createJustBashCommand as createJustBashCommandBridge } from './just-bash.js';
16
+ import { COMPLETION_FLAG, generateCompletionScript, installCompletions, uninstallCompletions, detectShell, detectCompletionShell, validateShell } from './completions.js';
16
17
  import { EventEmitter, fs as runtimeFs, openInBrowser, process } from '#runtime';
17
18
  // ─── Node.js platform constants ───
18
19
  const processArgs = process.argv;
@@ -322,9 +323,41 @@ class Command {
322
323
  * instance before `.command()` was called.
323
324
  */
324
325
  action(callback) {
326
+ // Give anonymous functions a name derived from the command so stack traces
327
+ // show e.g. "command:deploy" instead of "<anonymous>"
328
+ if (!callback.name) {
329
+ const label = this.name ? `command:${this.name}` : 'command:default';
330
+ Object.defineProperty(callback, 'name', { value: label });
331
+ }
325
332
  this.commandAction = callback;
326
333
  return this;
327
334
  }
335
+ /**
336
+ * Return the registered action callback with full type safety.
337
+ *
338
+ * Use this in tests to call the action directly without parsing argv.
339
+ * The returned function has the same typed signature as the `.action()` callback:
340
+ * `(..positionalArgs, options, executionContext) => unknown | Promise<unknown>`
341
+ *
342
+ * Throws if no action has been registered on this command.
343
+ *
344
+ * @example
345
+ * ```ts
346
+ * const cmd = cli
347
+ * .command('deploy', 'Deploy')
348
+ * .option('--env <env>', z.enum(['staging', 'production']))
349
+ * .action((options, { console }) => console.log(options.env))
350
+ *
351
+ * const action = cmd.getAction()
352
+ * action({ env: 'staging', '--': [] }, cli.createExecutionContext({ stdout }))
353
+ * ```
354
+ */
355
+ getAction() {
356
+ if (!this.commandAction) {
357
+ throw new GokeError(`No action registered on command "${this.name || '(default)'}"`);
358
+ }
359
+ return this.commandAction;
360
+ }
328
361
  isMatched(args) {
329
362
  const nameParts = this.name.split(' ').filter(Boolean);
330
363
  if (nameParts.length === 0) {
@@ -938,15 +971,316 @@ class Goke extends EventEmitter {
938
971
  }
939
972
  }
940
973
  /**
941
- * Parse argv
974
+ * Register shell completion commands: `completions install` and `completions uninstall`.
975
+ *
976
+ * Also wires the hidden `--get-goke-completions` flag that shell scripts call
977
+ * on each Tab press. When this flag is detected during `parse()`, the CLI
978
+ * prints matching completions to stdout and exits immediately.
979
+ *
980
+ * @example
981
+ * ```ts
982
+ * await goke('mycli')
983
+ * .help()
984
+ * .completions()
985
+ * .command('deploy', 'Deploy the app')
986
+ * .parse(process.argv)
987
+ *
988
+ * // Then the user runs:
989
+ * // mycli completions install
990
+ * // mycli dep<TAB> → mycli deploy
991
+ * ```
942
992
  */
943
- parse(argv = this.#defaultArgv, {
993
+ completions() {
994
+ this.command('completions install', 'Install shell completions')
995
+ .option('--shell [shell]', 'Target shell (zsh or bash). Auto-detected if omitted.')
996
+ .action(async (options, { console, process: proc }) => {
997
+ const shell = validateShell(options.shell);
998
+ const cliPath = proc.argv[1] ?? this.name;
999
+ const result = await installCompletions(this.name, cliPath, shell);
1000
+ console.log(`Wrote ${result.shell} completions to ${result.path}`);
1001
+ if (result.shell === 'zsh') {
1002
+ console.log('Restart your shell or run: autoload -Uz compinit && compinit');
1003
+ }
1004
+ else {
1005
+ console.log('Restart your shell to enable completions.');
1006
+ }
1007
+ });
1008
+ this.command('completions uninstall', 'Remove shell completions')
1009
+ .option('--shell [shell]', 'Target shell (zsh or bash). Auto-detected if omitted.')
1010
+ .action(async (options, { console }) => {
1011
+ const shell = validateShell(options.shell);
1012
+ const removed = await uninstallCompletions(this.name, shell);
1013
+ if (removed.length > 0) {
1014
+ for (const p of removed) {
1015
+ console.log(`Removed ${p}`);
1016
+ }
1017
+ }
1018
+ else {
1019
+ console.log('No completion files found to remove.');
1020
+ }
1021
+ });
1022
+ this.command('completions script', 'Print the completion script to stdout')
1023
+ .option('--shell [shell]', 'Target shell (zsh or bash). Auto-detected if omitted.')
1024
+ .action((options, { console, process: proc }) => {
1025
+ const shell = validateShell(options.shell) ?? detectShell();
1026
+ if (!shell) {
1027
+ throw new GokeError('Could not detect shell. Set the SHELL environment variable or pass --shell explicitly.');
1028
+ }
1029
+ const cliPath = proc.argv[1] ?? this.name;
1030
+ const script = generateCompletionScript(shell, this.name, cliPath);
1031
+ console.log(script);
1032
+ });
1033
+ return this;
1034
+ }
1035
+ /**
1036
+ * Compute completions for the given args (as received from the shell script).
1037
+ *
1038
+ * Returns an array of completion strings. For zsh, each entry is `name:description`.
1039
+ * For bash, each entry is just the name.
1040
+ *
1041
+ * @internal Used by parse() when --get-goke-completions is detected.
1042
+ */
1043
+ getCompletions(argv) {
1044
+ // argv comes from the shell: ["my-cli", "dep", ""] or ["my-cli", "deploy", "--"]
1045
+ // Strip the binary name (first element, which is the CLI name itself)
1046
+ const args = argv.slice(1);
1047
+ const current = args.length > 0 ? args[args.length - 1] : '';
1048
+ const previous = args.slice(0, -1);
1049
+ // Use GOKE_COMPLETION_SHELL (set by the shell shim) over $SHELL to avoid
1050
+ // format mismatch when e.g. a bash shim runs on a machine where $SHELL is zsh.
1051
+ const isZsh = detectCompletionShell() === 'zsh';
1052
+ const completions = [];
1053
+ const escapeColon = (s) => s.replace(/:/g, '\\:');
1054
+ // Extract the long --flag from an option's rawName string.
1055
+ // rawName is like "--dry-run", "-v, --verbose", "--port <port>"
1056
+ // Returns the original kebab-case flag including dashes.
1057
+ const getLongFlag = (option) => {
1058
+ const parts = removeBrackets(option.rawName).split(',').map((s) => s.trim());
1059
+ // Prefer the -- prefixed part; fall back to the last part (short-only flags like -x)
1060
+ const longPart = parts.find((p) => p.startsWith('--')) ?? parts[parts.length - 1];
1061
+ return longPart.startsWith('-') ? longPart : `--${longPart}`;
1062
+ };
1063
+ // Check if the previous token is a non-boolean option expecting a value.
1064
+ // In that case we should NOT suggest more flags or commands; let the shell
1065
+ // fall back to file completion or return nothing.
1066
+ const isAwaitingOptionValue = () => {
1067
+ if (previous.length === 0)
1068
+ return false;
1069
+ const lastToken = previous[previous.length - 1];
1070
+ if (!lastToken.startsWith('-'))
1071
+ return false;
1072
+ // Find the option matching this token across all registered options
1073
+ const allOptions = [
1074
+ ...this.globalCommand.options,
1075
+ ...this.commands.flatMap((c) => c.options),
1076
+ ];
1077
+ const tokenName = camelcaseOptionName(lastToken.replace(/^-{1,2}/, ''));
1078
+ for (const option of allOptions) {
1079
+ if (option.names.includes(tokenName)) {
1080
+ // If it takes a value (required or optional) and is not boolean, we're awaiting a value
1081
+ return !option.isBoolean && option.required !== undefined;
1082
+ }
1083
+ }
1084
+ return false;
1085
+ };
1086
+ // If the previous token is a non-boolean option, don't suggest anything.
1087
+ // Let the shell fall back to file completion.
1088
+ if (!current.startsWith('-') && isAwaitingOptionValue()) {
1089
+ return [];
1090
+ }
1091
+ // Helper to push an option as a completion entry
1092
+ const pushOption = (option) => {
1093
+ const flag = getLongFlag(option);
1094
+ if (isZsh && option.description) {
1095
+ completions.push(`${escapeColon(flag)}:${escapeColon(option.description)}`);
1096
+ }
1097
+ else {
1098
+ completions.push(flag);
1099
+ }
1100
+ };
1101
+ // Check if any alias of an option has already been used
1102
+ const isOptionUsed = (option, usedOptions) => {
1103
+ return option.names.some((name) => usedOptions.has(name));
1104
+ };
1105
+ // Try to match a command from the previous words
1106
+ let matchedCommand;
1107
+ let consumedArgs = 0;
1108
+ // Sort by name length (longest first) for greedy matching
1109
+ const sortedCommands = [...this.commands].sort((a, b) => {
1110
+ const aLen = a.name.split(' ').filter(Boolean).length;
1111
+ const bLen = b.name.split(' ').filter(Boolean).length;
1112
+ return bLen - aLen;
1113
+ });
1114
+ for (const command of sortedCommands) {
1115
+ const result = command.isMatched(previous);
1116
+ if (result.matched) {
1117
+ matchedCommand = command;
1118
+ consumedArgs = result.consumedArgs;
1119
+ break;
1120
+ }
1121
+ }
1122
+ if (matchedCommand) {
1123
+ // We matched a command, suggest its options
1124
+ const usedOptions = new Set(previous.slice(consumedArgs)
1125
+ .filter((a) => a.startsWith('-'))
1126
+ .map((a) => a.replace(/^-{1,2}/, ''))
1127
+ .map(camelcaseOptionName));
1128
+ const allOptions = [...(matchedCommand.globalCommand?.options ?? []), ...matchedCommand.options];
1129
+ for (const option of allOptions) {
1130
+ if (option.deprecated)
1131
+ continue;
1132
+ // Skip already-used options (check all aliases, not just the primary name)
1133
+ if (option.isBoolean && isOptionUsed(option, usedOptions))
1134
+ continue;
1135
+ const flag = getLongFlag(option);
1136
+ if (current.startsWith('-')) {
1137
+ if (!flag.startsWith(current))
1138
+ continue;
1139
+ }
1140
+ else if (current !== '') {
1141
+ continue;
1142
+ }
1143
+ pushOption(option);
1144
+ }
1145
+ // If current word doesn't start with -, also suggest subcommands that extend this one
1146
+ if (!current.startsWith('-')) {
1147
+ const prefix = matchedCommand.name ? matchedCommand.name + ' ' : '';
1148
+ for (const cmd of this.commands) {
1149
+ if (cmd._hidden)
1150
+ continue;
1151
+ if (cmd === matchedCommand)
1152
+ continue;
1153
+ if (cmd.name.startsWith(prefix) && cmd.name !== matchedCommand.name) {
1154
+ const sub = cmd.name.slice(prefix.length).split(' ')[0];
1155
+ if (sub.startsWith(current)) {
1156
+ if (isZsh) {
1157
+ const desc = cmd.description.split('\n')[0].trim();
1158
+ completions.push(desc ? `${escapeColon(sub)}:${escapeColon(desc)}` : sub);
1159
+ }
1160
+ else {
1161
+ completions.push(sub);
1162
+ }
1163
+ }
1164
+ }
1165
+ }
1166
+ }
1167
+ }
1168
+ else {
1169
+ // No command matched yet, suggest commands
1170
+ // Check if some previous words partially match a multi-word command prefix
1171
+ const prevJoined = previous.join(' ');
1172
+ for (const command of this.commands) {
1173
+ if (command._hidden)
1174
+ continue;
1175
+ if (command.isDefaultCommand)
1176
+ continue;
1177
+ const cmdName = command.name;
1178
+ const cmdParts = cmdName.split(' ').filter(Boolean);
1179
+ if (cmdParts.length === 0)
1180
+ continue;
1181
+ // For single-word commands, just check prefix against current
1182
+ if (cmdParts.length === 1) {
1183
+ if (previous.length === 0 && cmdParts[0].startsWith(current)) {
1184
+ if (isZsh) {
1185
+ const desc = command.description.split('\n')[0].trim();
1186
+ completions.push(desc ? `${escapeColon(cmdParts[0])}:${escapeColon(desc)}` : cmdParts[0]);
1187
+ }
1188
+ else {
1189
+ completions.push(cmdParts[0]);
1190
+ }
1191
+ }
1192
+ continue;
1193
+ }
1194
+ // Multi-word commands: check if previous matches the prefix parts
1195
+ const matchPrefix = cmdParts.slice(0, -1).join(' ');
1196
+ const lastPart = cmdParts[cmdParts.length - 1];
1197
+ if (prevJoined === matchPrefix && lastPart.startsWith(current)) {
1198
+ if (isZsh) {
1199
+ const desc = command.description.split('\n')[0].trim();
1200
+ completions.push(desc ? `${escapeColon(lastPart)}:${escapeColon(desc)}` : lastPart);
1201
+ }
1202
+ else {
1203
+ completions.push(lastPart);
1204
+ }
1205
+ }
1206
+ }
1207
+ // Also suggest first words of multi-word commands when at root level
1208
+ if (previous.length === 0) {
1209
+ const seenFirstWords = new Set();
1210
+ for (const command of this.commands) {
1211
+ if (command._hidden || command.isDefaultCommand)
1212
+ continue;
1213
+ const firstWord = command.name.split(' ')[0];
1214
+ if (!firstWord || seenFirstWords.has(firstWord))
1215
+ continue;
1216
+ // Skip if already added as a single-word command above
1217
+ if (completions.some((c) => {
1218
+ const name = c.split(':')[0].replace(/\\:/g, ':');
1219
+ return name === firstWord;
1220
+ }))
1221
+ continue;
1222
+ seenFirstWords.add(firstWord);
1223
+ if (firstWord.startsWith(current)) {
1224
+ // For first words of multi-word commands, no description (it's a prefix, not a full command)
1225
+ completions.push(firstWord);
1226
+ }
1227
+ }
1228
+ }
1229
+ // Also include default/root command options at root level
1230
+ // (commands with name '' that have their own options)
1231
+ const defaultCommands = this.commands.filter((c) => c.isDefaultCommand);
1232
+ const defaultOptions = defaultCommands.flatMap((c) => c.options);
1233
+ // Suggest global options + default command options when current starts with -
1234
+ if (current.startsWith('-') || current === '') {
1235
+ const globalAndDefaultOptions = [...this.globalCommand.options, ...defaultOptions];
1236
+ const seen = new Set();
1237
+ for (const option of globalAndDefaultOptions) {
1238
+ if (option.deprecated)
1239
+ continue;
1240
+ if (seen.has(option.name))
1241
+ continue;
1242
+ seen.add(option.name);
1243
+ const flag = getLongFlag(option);
1244
+ if (current.startsWith('-')) {
1245
+ if (!flag.startsWith(current))
1246
+ continue;
1247
+ }
1248
+ else if (current !== '') {
1249
+ continue;
1250
+ }
1251
+ // Only suggest options when current is - prefixed or empty and no commands matched
1252
+ if (current === '' && completions.length > 0 && !current.startsWith('-'))
1253
+ continue;
1254
+ pushOption(option);
1255
+ }
1256
+ }
1257
+ }
1258
+ // Deduplicate
1259
+ return [...new Set(completions)];
1260
+ }
1261
+ /**
1262
+ * Parse argv and await the matched command when run is enabled.
1263
+ */
1264
+ async parse(argv = this.#defaultArgv, {
944
1265
  /** Whether to run the action for matched command */
945
1266
  run = true, } = {}) {
946
1267
  this.rawArgs = argv;
947
1268
  if (!this.name) {
948
1269
  this.name = argv[1] ? getFileName(argv[1]) : 'cli';
949
1270
  }
1271
+ // Intercept --get-goke-completions before any command matching/validation.
1272
+ // The shell completion script passes this flag on every Tab press.
1273
+ const completionFlagIndex = argv.indexOf(`--${COMPLETION_FLAG}`);
1274
+ if (completionFlagIndex !== -1) {
1275
+ // Everything after the flag is the words typed so far
1276
+ const completionArgs = argv.slice(completionFlagIndex + 1);
1277
+ const completions = this.getCompletions(completionArgs);
1278
+ for (const c of completions) {
1279
+ this.stdout.write(c + '\n');
1280
+ }
1281
+ this.exit(0);
1282
+ return { args: [], options: {} };
1283
+ }
950
1284
  let shouldParse = true;
951
1285
  // Sort by name length (longest first) so "mcp login" matches before "mcp"
952
1286
  const sortedCommands = [...this.commands].sort((a, b) => {
@@ -990,6 +1324,11 @@ class Goke extends EventEmitter {
990
1324
  // Don't match default command - let it fall through to "unknown command"
991
1325
  continue;
992
1326
  }
1327
+ // Default command defines no positional args but user passed args;
1328
+ // skip matching so unknown args fall through to "unknown command"
1329
+ if (command.args.length === 0) {
1330
+ continue;
1331
+ }
993
1332
  }
994
1333
  shouldParse = false;
995
1334
  this.setParsedInfo(parsed, command);
@@ -1038,7 +1377,7 @@ class Goke extends EventEmitter {
1038
1377
  }
1039
1378
  const parsedArgv = { args: this.args, options: this.options };
1040
1379
  if (run) {
1041
- this.runMatchedCommand();
1380
+ await this.runMatchedCommand();
1042
1381
  }
1043
1382
  if (!this.matchedCommand && this.args[0] && !(this.options.help && this.showHelpOnExit)) {
1044
1383
  this.emit('command:*');
@@ -1294,5 +1633,149 @@ class Goke extends EventEmitter {
1294
1633
  }
1295
1634
  }
1296
1635
  }
1297
- export { createConsole, Command, GokeProcessExit, openInBrowser };
1636
+ /**
1637
+ * Generate markdown documentation pages for every command in a CLI.
1638
+ *
1639
+ * Returns one `DocPage` per non-hidden command, plus a root index page
1640
+ * that lists all available commands. Each page includes an arguments table,
1641
+ * options table, global options, and examples when available.
1642
+ *
1643
+ * @example
1644
+ * ```ts
1645
+ * import { goke, generateDocs } from 'goke'
1646
+ * import fs from 'node:fs'
1647
+ *
1648
+ * const cli = goke('mycli')
1649
+ * .command('deploy <env>', 'Deploy to an environment')
1650
+ * .option('--force', 'Skip confirmation')
1651
+ *
1652
+ * const pages = generateDocs({ cli })
1653
+ * for (const page of pages) {
1654
+ * fs.writeFileSync(`docs/${page.slug}.md`, page.content)
1655
+ * }
1656
+ * ```
1657
+ */
1658
+ function generateDocs({ cli }) {
1659
+ const pages = [];
1660
+ // Collect global options (from globalCommand), excluding deprecated
1661
+ const globalOptions = cli.globalCommand.options.filter((o) => !o.deprecated);
1662
+ // Root index page listing all commands
1663
+ const visibleCommands = cli.commands.filter((cmd) => !cmd._hidden);
1664
+ if (visibleCommands.length > 0) {
1665
+ const lines = [];
1666
+ lines.push(`# ${cli.name}`);
1667
+ lines.push('');
1668
+ const { versionNumber } = cli.globalCommand;
1669
+ if (versionNumber) {
1670
+ lines.push(`Version: ${versionNumber}`);
1671
+ lines.push('');
1672
+ }
1673
+ lines.push('## Commands');
1674
+ lines.push('');
1675
+ lines.push('| Command | Description |');
1676
+ lines.push('|---------|-------------|');
1677
+ for (const cmd of visibleCommands) {
1678
+ if (cmd.isDefaultCommand)
1679
+ continue;
1680
+ const desc = cmd.description.split('\n')[0].trim();
1681
+ const slug = cmd.name.replace(/\s+/g, '-');
1682
+ lines.push(`| [\`${cmd.name}\`](./${slug}.md) | ${desc} |`);
1683
+ }
1684
+ lines.push('');
1685
+ if (globalOptions.length > 0) {
1686
+ lines.push('## Global Options');
1687
+ lines.push('');
1688
+ lines.push(formatOptionsTable(globalOptions));
1689
+ lines.push('');
1690
+ }
1691
+ pages.push({ command: '', slug: 'index', content: lines.join('\n') });
1692
+ }
1693
+ // One page per command
1694
+ for (const cmd of visibleCommands) {
1695
+ if (cmd.isDefaultCommand)
1696
+ continue;
1697
+ const lines = [];
1698
+ const title = cmd.name;
1699
+ lines.push(`# ${title}`);
1700
+ lines.push('');
1701
+ if (cmd.description) {
1702
+ lines.push(cmd.description);
1703
+ lines.push('');
1704
+ }
1705
+ // Usage line
1706
+ const usage = cmd.usageText || cmd.rawName;
1707
+ lines.push('## Usage');
1708
+ lines.push('');
1709
+ lines.push('```sh');
1710
+ lines.push(`${cli.name} ${usage}`);
1711
+ lines.push('```');
1712
+ lines.push('');
1713
+ // Arguments table
1714
+ if (cmd.args.length > 0) {
1715
+ lines.push('## Arguments');
1716
+ lines.push('');
1717
+ lines.push('| Argument | Required | Description |');
1718
+ lines.push('|----------|----------|-------------|');
1719
+ for (const arg of cmd.args) {
1720
+ const bracket = arg.required
1721
+ ? `<${arg.variadic ? '...' : ''}${arg.value}>`
1722
+ : `[${arg.variadic ? '...' : ''}${arg.value}]`;
1723
+ const required = arg.required ? 'Yes' : 'No';
1724
+ const desc = arg.variadic ? `${arg.value} (variadic)` : arg.value;
1725
+ lines.push(`| \`${bracket}\` | ${required} | ${desc} |`);
1726
+ }
1727
+ lines.push('');
1728
+ }
1729
+ // Command-specific options
1730
+ const cmdOptions = cmd.options.filter((o) => !o.deprecated);
1731
+ if (cmdOptions.length > 0) {
1732
+ lines.push('## Options');
1733
+ lines.push('');
1734
+ lines.push(formatOptionsTable(cmdOptions));
1735
+ lines.push('');
1736
+ }
1737
+ // Global options section
1738
+ if (globalOptions.length > 0) {
1739
+ lines.push('## Global Options');
1740
+ lines.push('');
1741
+ lines.push(formatOptionsTable(globalOptions));
1742
+ lines.push('');
1743
+ }
1744
+ // Examples
1745
+ if (cmd.examples.length > 0) {
1746
+ lines.push('## Examples');
1747
+ lines.push('');
1748
+ for (const example of cmd.examples) {
1749
+ const text = typeof example === 'function' ? example(cli.name) : example;
1750
+ // Auto-wrap in ```sh if not already fenced
1751
+ if (text.trimStart().startsWith('```')) {
1752
+ lines.push(text);
1753
+ }
1754
+ else {
1755
+ lines.push('```sh');
1756
+ lines.push(text);
1757
+ lines.push('```');
1758
+ }
1759
+ lines.push('');
1760
+ }
1761
+ }
1762
+ const slug = cmd.name.replace(/\s+/g, '-');
1763
+ pages.push({ command: cmd.name, slug, content: lines.join('\n') });
1764
+ }
1765
+ return pages;
1766
+ }
1767
+ function formatOptionsTable(options) {
1768
+ const lines = [];
1769
+ lines.push('| Option | Default | Description |');
1770
+ lines.push('|--------|---------|-------------|');
1771
+ for (const opt of options) {
1772
+ const defaultVal = opt.default !== undefined ? `\`${String(opt.default)}\`` : '-';
1773
+ // Escape pipe characters in description for markdown tables
1774
+ const desc = opt.description.replace(/\|/g, '\\|').replace(/\n/g, ' ');
1775
+ lines.push(`| \`${opt.rawName}\` | ${defaultVal} | ${desc} |`);
1776
+ }
1777
+ return lines.join('\n');
1778
+ }
1779
+ export { createConsole, Command, GokeProcessExit, openInBrowser, generateDocs };
1780
+ export { generateCompletionScript, installCompletions, uninstallCompletions, detectShell, detectCompletionShell, validateShell };
1298
1781
  export default Goke;
package/dist/index.d.ts CHANGED
@@ -6,10 +6,17 @@ import { Command } from "./goke.js";
6
6
  * @param options Configuration for stdout, stderr, and argv
7
7
  */
8
8
  declare const goke: (name?: string, options?: GokeOptions) => Goke<{}>;
9
+ /**
10
+ * Vendored picocolors instance for terminal colors.
11
+ * Import this instead of installing picocolors, chalk, or any other color library.
12
+ */
13
+ export declare const colors: import("./picocolors.js").PicoColors;
9
14
  export default goke;
10
15
  export { goke, Goke, Command };
11
- export { createConsole, GokeProcessExit, openInBrowser } from "./goke.js";
12
- export type { GokeOutputStream, GokeConsole, GokeExecutionContext, GokeExecutionContextOverride, GokeFs, GokeOptions, GokeProcess } from "./goke.js";
16
+ export { createConsole, GokeProcessExit, openInBrowser, generateDocs, generateCompletionScript, installCompletions, uninstallCompletions, detectShell } from "./goke.js";
17
+ export type { GokeOutputStream, GokeConsole, GokeExecutionContext, GokeExecutionContextOverride, GokeFs, GokeOptions, GokeProcess, DocPage, GenerateDocsOptions, ShellType } from "./goke.js";
13
18
  export type { StandardTypedV1, StandardJSONSchemaV1, JsonSchema } from "./coerce.js";
14
19
  export { GokeError, coerceBySchema, extractJsonSchema, wrapJsonSchema, isStandardSchema, extractSchemaMetadata } from "./coerce.js";
20
+ export { detectAgent, agentInfo, agent, isAgent } from "./agents.js";
21
+ export type { AgentName, AgentInfo } from "./agents.js";
15
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC;;;GAGG;AACH,QAAA,MAAM,IAAI,GAAI,aAAS,EAAE,UAAU,WAAW,aAA4B,CAAA;AAE1E,eAAe,IAAI,CAAA;AACnB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;AAC9B,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACzE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACpJ,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACpF,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAGnC;;;GAGG;AACH,QAAA,MAAM,IAAI,GAAI,aAAS,EAAE,UAAU,WAAW,aAA4B,CAAA;AAE1E;;;GAGG;AACH,eAAO,MAAM,MAAM,sCAAK,CAAA;AAExB,eAAe,IAAI,CAAA;AACnB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;AAC9B,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACxK,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAC7L,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACpF,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACnI,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AACpE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA"}
package/dist/index.js CHANGED
@@ -1,11 +1,18 @@
1
1
  import Goke from "./goke.js";
2
2
  import { Command } from "./goke.js";
3
+ import pc from "./picocolors.js";
3
4
  /**
4
5
  * @param name The program name to display in help and version message
5
6
  * @param options Configuration for stdout, stderr, and argv
6
7
  */
7
8
  const goke = (name = '', options) => new Goke(name, options);
9
+ /**
10
+ * Vendored picocolors instance for terminal colors.
11
+ * Import this instead of installing picocolors, chalk, or any other color library.
12
+ */
13
+ export const colors = pc;
8
14
  export default goke;
9
15
  export { goke, Goke, Command };
10
- export { createConsole, GokeProcessExit, openInBrowser } from "./goke.js";
16
+ export { createConsole, GokeProcessExit, openInBrowser, generateDocs, generateCompletionScript, installCompletions, uninstallCompletions, detectShell } from "./goke.js";
11
17
  export { GokeError, coerceBySchema, extractJsonSchema, wrapJsonSchema, isStandardSchema, extractSchemaMetadata } from "./coerce.js";
18
+ export { detectAgent, agentInfo, agent, isAgent } from "./agents.js";
@@ -1 +1 @@
1
- {"version":3,"file":"just-bash.d.ts","sourceRoot":"","sources":["../src/just-bash.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,WAAW,CAAA;AAC5D,OAAO,IAAyB,MAAM,WAAW,CAAA;AAIjD,UAAU,kBAAkB;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,IAAI,CAAA;IACb,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;CACzF;AAED,KAAK,wBAAwB,GAAG,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,KAAK,GAAG,IAAI,GAAG,OAAO,GAAG,QAAQ,CAAC,CAAA;AAqQ/F,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1B,eAAe,CAsDjB;AAED,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAE,CAAA"}
1
+ {"version":3,"file":"just-bash.d.ts","sourceRoot":"","sources":["../src/just-bash.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,WAAW,CAAA;AAC5D,OAAO,IAAyB,MAAM,WAAW,CAAA;AAIjD,UAAU,kBAAkB;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,IAAI,CAAA;IACb,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;CACzF;AAED,KAAK,wBAAwB,GAAG,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,KAAK,GAAG,IAAI,GAAG,OAAO,GAAG,QAAQ,CAAC,CAAA;AAqQ/F,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1B,eAAe,CAqDjB;AAED,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAE,CAAA"}
package/dist/just-bash.js CHANGED
@@ -267,8 +267,7 @@ export function createJustBashCommand(cli, options) {
267
267
  });
268
268
  cloned.name = name;
269
269
  try {
270
- cloned.parse(argv, { run: false });
271
- await cloned.runMatchedCommand();
270
+ await cloned.parse(argv);
272
271
  const result = output.getResult();
273
272
  return {
274
273
  stdout: result.stdout,
@@ -30,6 +30,6 @@ declare const process: {
30
30
  exit(code: number): never;
31
31
  };
32
32
  declare const fs: GokeFs;
33
- declare function openInBrowser(_url: string): void;
33
+ declare function openInBrowser(_url: string): Promise<void>;
34
34
  export { EventEmitter, fs, openInBrowser, process };
35
35
  //# sourceMappingURL=runtime-browser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-browser.d.ts","sourceRoot":"","sources":["../src/runtime-browser.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C,KAAK,QAAQ,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;AAExC,cAAM,YAAY;;IAGhB,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,QAAQ;IAOjD,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;IAS/C,UAAU;IAIV,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;CAGrC;AAQD,QAAA,MAAM,OAAO;UACC,MAAM,EAAE;;;SAKQ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;;;;;;qBATjD,MAAM;;;;;qBAAN,MAAM;;eAcR,MAAM,GAAG,KAAK;CAK1B,CAAA;AAcD,QAAA,MAAM,EAAE,EAAE,MAcT,CAAA;AAED,iBAAS,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEzC;AAED,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"runtime-browser.d.ts","sourceRoot":"","sources":["../src/runtime-browser.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C,KAAK,QAAQ,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;AAExC,cAAM,YAAY;;IAGhB,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,QAAQ;IAOjD,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;IAS/C,UAAU;IAIV,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;CAGrC;AAQD,QAAA,MAAM,OAAO;UACC,MAAM,EAAE;;;SAKQ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;;;;;;qBATjD,MAAM;;;;;qBAAN,MAAM;;eAcR,MAAM,GAAG,KAAK;CAK1B,CAAA;AAcD,QAAA,MAAM,EAAE,EAAE,MAcT,CAAA;AAED,iBAAe,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAExD;AAED,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,CAAA"}
@@ -68,7 +68,7 @@ const fs = {
68
68
  utimes: createUnsupportedFsMethod('utimes'),
69
69
  writeFile: createUnsupportedFsMethod('writeFile'),
70
70
  };
71
- function openInBrowser(_url) {
71
+ async function openInBrowser(_url) {
72
72
  // Browser builds should decide how to surface URLs themselves.
73
73
  }
74
74
  export { EventEmitter, fs, openInBrowser, process };
@@ -5,6 +5,6 @@ import { EventEmitter } from 'events';
5
5
  import type { GokeFs } from './goke-fs.js';
6
6
  declare const process: NodeJS.Process;
7
7
  declare const fs: GokeFs;
8
- declare function openInBrowser(url: string): void;
8
+ declare function openInBrowser(url: string): Promise<void>;
9
9
  export { EventEmitter, fs, openInBrowser, process };
10
10
  //# sourceMappingURL=runtime-node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-node.d.ts","sourceRoot":"","sources":["../src/runtime-node.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAErC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C,QAAA,MAAM,OAAO,gBAAqB,CAAA;AAClC,QAAA,MAAM,EAAE,EAAE,MAAe,CAAA;AAEzB,iBAAS,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAiBxC;AAED,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"runtime-node.d.ts","sourceRoot":"","sources":["../src/runtime-node.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAErC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C,QAAA,MAAM,OAAO,gBAAqB,CAAA;AAClC,QAAA,MAAM,EAAE,EAAE,MAAe,CAAA;AAEzB,iBAAe,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBvD;AAED,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,CAAA"}