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/__test__/completions.test.d.ts +9 -0
- package/dist/__test__/completions.test.d.ts.map +1 -0
- package/dist/__test__/completions.test.js +774 -0
- package/dist/__test__/index.test.js +64 -0
- package/dist/__test__/readme-examples.test.js +141 -5
- package/dist/__test__/types.test-d.js +27 -0
- package/dist/agents.d.ts +38 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +63 -0
- package/dist/completions.d.ts +88 -0
- package/dist/completions.d.ts.map +1 -0
- package/dist/completions.js +315 -0
- package/dist/goke.d.ts +92 -2
- package/dist/goke.d.ts.map +1 -1
- package/dist/goke.js +479 -1
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/runtime-browser.d.ts +1 -1
- package/dist/runtime-browser.d.ts.map +1 -1
- package/dist/runtime-browser.js +1 -1
- package/dist/runtime-node.d.ts +1 -1
- package/dist/runtime-node.d.ts.map +1 -1
- package/dist/runtime-node.js +22 -13
- package/package.json +1 -1
- package/src/__test__/completions.test.ts +902 -0
- package/src/__test__/index.test.ts +75 -0
- package/src/__test__/readme-examples.test.ts +153 -5
- package/src/__test__/types.test-d.ts +27 -0
- package/src/agents.ts +101 -0
- package/src/completions.ts +363 -0
- package/src/goke.ts +529 -2
- package/src/index.ts +11 -2
- package/src/runtime-browser.ts +1 -1
- 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) {
|
|
@@ -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
|
-
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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,
|
|
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"}
|
package/dist/runtime-browser.js
CHANGED
|
@@ -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 };
|
package/dist/runtime-node.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/runtime-node.js
CHANGED
|
@@ -1,29 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node.js runtime bindings for goke core.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
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.
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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.
|
|
35
|
+
process.stderr.write(url + '\n');
|
|
27
36
|
}
|
|
28
37
|
}
|
|
29
38
|
export { EventEmitter, fs, openInBrowser, process };
|