goke 6.9.0 → 6.10.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.
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) {
@@ -937,6 +970,294 @@ class Goke extends EventEmitter {
937
970
  this.console.error(`\nRun "${cmdName}" for usage information.`);
938
971
  }
939
972
  }
973
+ /**
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
+ * 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
+ * ```
992
+ */
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
+ }
940
1261
  /**
941
1262
  * Parse argv
942
1263
  */
@@ -947,6 +1268,19 @@ class Goke extends EventEmitter {
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) => {
@@ -1294,5 +1628,149 @@ class Goke extends EventEmitter {
1294
1628
  }
1295
1629
  }
1296
1630
  }
1297
- export { createConsole, Command, GokeProcessExit, openInBrowser };
1631
+ /**
1632
+ * Generate markdown documentation pages for every command in a CLI.
1633
+ *
1634
+ * Returns one `DocPage` per non-hidden command, plus a root index page
1635
+ * that lists all available commands. Each page includes an arguments table,
1636
+ * options table, global options, and examples when available.
1637
+ *
1638
+ * @example
1639
+ * ```ts
1640
+ * import { goke, generateDocs } from 'goke'
1641
+ * import fs from 'node:fs'
1642
+ *
1643
+ * const cli = goke('mycli')
1644
+ * .command('deploy <env>', 'Deploy to an environment')
1645
+ * .option('--force', 'Skip confirmation')
1646
+ *
1647
+ * const pages = generateDocs({ cli })
1648
+ * for (const page of pages) {
1649
+ * fs.writeFileSync(`docs/${page.slug}.md`, page.content)
1650
+ * }
1651
+ * ```
1652
+ */
1653
+ function generateDocs({ cli }) {
1654
+ const pages = [];
1655
+ // Collect global options (from globalCommand), excluding deprecated
1656
+ const globalOptions = cli.globalCommand.options.filter((o) => !o.deprecated);
1657
+ // Root index page listing all commands
1658
+ const visibleCommands = cli.commands.filter((cmd) => !cmd._hidden);
1659
+ if (visibleCommands.length > 0) {
1660
+ const lines = [];
1661
+ lines.push(`# ${cli.name}`);
1662
+ lines.push('');
1663
+ const { versionNumber } = cli.globalCommand;
1664
+ if (versionNumber) {
1665
+ lines.push(`Version: ${versionNumber}`);
1666
+ lines.push('');
1667
+ }
1668
+ lines.push('## Commands');
1669
+ lines.push('');
1670
+ lines.push('| Command | Description |');
1671
+ lines.push('|---------|-------------|');
1672
+ for (const cmd of visibleCommands) {
1673
+ if (cmd.isDefaultCommand)
1674
+ continue;
1675
+ const desc = cmd.description.split('\n')[0].trim();
1676
+ const slug = cmd.name.replace(/\s+/g, '-');
1677
+ lines.push(`| [\`${cmd.name}\`](./${slug}.md) | ${desc} |`);
1678
+ }
1679
+ lines.push('');
1680
+ if (globalOptions.length > 0) {
1681
+ lines.push('## Global Options');
1682
+ lines.push('');
1683
+ lines.push(formatOptionsTable(globalOptions));
1684
+ lines.push('');
1685
+ }
1686
+ pages.push({ command: '', slug: 'index', content: lines.join('\n') });
1687
+ }
1688
+ // One page per command
1689
+ for (const cmd of visibleCommands) {
1690
+ if (cmd.isDefaultCommand)
1691
+ continue;
1692
+ const lines = [];
1693
+ const title = cmd.name;
1694
+ lines.push(`# ${title}`);
1695
+ lines.push('');
1696
+ if (cmd.description) {
1697
+ lines.push(cmd.description);
1698
+ lines.push('');
1699
+ }
1700
+ // Usage line
1701
+ const usage = cmd.usageText || cmd.rawName;
1702
+ lines.push('## Usage');
1703
+ lines.push('');
1704
+ lines.push('```sh');
1705
+ lines.push(`${cli.name} ${usage}`);
1706
+ lines.push('```');
1707
+ lines.push('');
1708
+ // Arguments table
1709
+ if (cmd.args.length > 0) {
1710
+ lines.push('## Arguments');
1711
+ lines.push('');
1712
+ lines.push('| Argument | Required | Description |');
1713
+ lines.push('|----------|----------|-------------|');
1714
+ for (const arg of cmd.args) {
1715
+ const bracket = arg.required
1716
+ ? `<${arg.variadic ? '...' : ''}${arg.value}>`
1717
+ : `[${arg.variadic ? '...' : ''}${arg.value}]`;
1718
+ const required = arg.required ? 'Yes' : 'No';
1719
+ const desc = arg.variadic ? `${arg.value} (variadic)` : arg.value;
1720
+ lines.push(`| \`${bracket}\` | ${required} | ${desc} |`);
1721
+ }
1722
+ lines.push('');
1723
+ }
1724
+ // Command-specific options
1725
+ const cmdOptions = cmd.options.filter((o) => !o.deprecated);
1726
+ if (cmdOptions.length > 0) {
1727
+ lines.push('## Options');
1728
+ lines.push('');
1729
+ lines.push(formatOptionsTable(cmdOptions));
1730
+ lines.push('');
1731
+ }
1732
+ // Global options section
1733
+ if (globalOptions.length > 0) {
1734
+ lines.push('## Global Options');
1735
+ lines.push('');
1736
+ lines.push(formatOptionsTable(globalOptions));
1737
+ lines.push('');
1738
+ }
1739
+ // Examples
1740
+ if (cmd.examples.length > 0) {
1741
+ lines.push('## Examples');
1742
+ lines.push('');
1743
+ for (const example of cmd.examples) {
1744
+ const text = typeof example === 'function' ? example(cli.name) : example;
1745
+ // Auto-wrap in ```sh if not already fenced
1746
+ if (text.trimStart().startsWith('```')) {
1747
+ lines.push(text);
1748
+ }
1749
+ else {
1750
+ lines.push('```sh');
1751
+ lines.push(text);
1752
+ lines.push('```');
1753
+ }
1754
+ lines.push('');
1755
+ }
1756
+ }
1757
+ const slug = cmd.name.replace(/\s+/g, '-');
1758
+ pages.push({ command: cmd.name, slug, content: lines.join('\n') });
1759
+ }
1760
+ return pages;
1761
+ }
1762
+ function formatOptionsTable(options) {
1763
+ const lines = [];
1764
+ lines.push('| Option | Default | Description |');
1765
+ lines.push('|--------|---------|-------------|');
1766
+ for (const opt of options) {
1767
+ const defaultVal = opt.default !== undefined ? `\`${String(opt.default)}\`` : '-';
1768
+ // Escape pipe characters in description for markdown tables
1769
+ const desc = opt.description.replace(/\|/g, '\\|').replace(/\n/g, ' ');
1770
+ lines.push(`| \`${opt.rawName}\` | ${defaultVal} | ${desc} |`);
1771
+ }
1772
+ return lines.join('\n');
1773
+ }
1774
+ export { createConsole, Command, GokeProcessExit, openInBrowser, generateDocs };
1775
+ export { generateCompletionScript, installCompletions, uninstallCompletions, detectShell, detectCompletionShell, validateShell };
1298
1776
  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";
@@ -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"}
@@ -1,29 +1,38 @@
1
1
  /**
2
2
  * Node.js runtime bindings for goke core.
3
3
  */
4
- import { execSync } from 'child_process';
4
+ import { exec } from 'child_process';
5
5
  import { EventEmitter } from 'events';
6
6
  import * as nodeFs from 'node:fs/promises';
7
7
  const process = globalThis.process;
8
8
  const fs = nodeFs;
9
- function openInBrowser(url) {
9
+ async function openInBrowser(url) {
10
10
  if (!process.stdout.isTTY) {
11
- process.stdout.write(url + '\n');
11
+ process.stderr.write(url + '\n');
12
12
  return;
13
13
  }
14
+ let cmd;
15
+ if (process.platform === 'darwin') {
16
+ cmd = `open ${JSON.stringify(url)}`;
17
+ }
18
+ else if (process.platform === 'win32') {
19
+ cmd = `start "" ${JSON.stringify(url)}`;
20
+ }
21
+ else {
22
+ cmd = `xdg-open ${JSON.stringify(url)}`;
23
+ }
14
24
  try {
15
- if (process.platform === 'darwin') {
16
- execSync(`open ${JSON.stringify(url)}`, { stdio: 'ignore' });
17
- }
18
- else if (process.platform === 'win32') {
19
- execSync(`start "" ${JSON.stringify(url)}`, { stdio: 'ignore' });
20
- }
21
- else {
22
- execSync(`xdg-open ${JSON.stringify(url)}`, { stdio: 'ignore' });
23
- }
25
+ await new Promise((resolve, reject) => {
26
+ exec(cmd, { stdio: 'ignore' }, (err) => {
27
+ if (err)
28
+ reject(err);
29
+ else
30
+ resolve();
31
+ });
32
+ });
24
33
  }
25
34
  catch {
26
- process.stdout.write(url + '\n');
35
+ process.stderr.write(url + '\n');
27
36
  }
28
37
  }
29
38
  export { EventEmitter, fs, openInBrowser, process };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goke",
3
- "version": "6.9.0",
3
+ "version": "6.10.0",
4
4
  "type": "module",
5
5
  "description": "Simple yet powerful framework for building command-line apps. Inspired by cac.",
6
6
  "repository": {