@otto-assistant/bridge 0.4.102 → 0.4.103

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 (70) hide show
  1. package/dist/agent-model.e2e.test.js +1 -0
  2. package/dist/anthropic-auth-plugin.js +22 -1
  3. package/dist/anthropic-auth-state.js +31 -0
  4. package/dist/btw-prefix-detection.js +17 -0
  5. package/dist/btw-prefix-detection.test.js +63 -0
  6. package/dist/cli.js +101 -15
  7. package/dist/commands/agent.js +21 -2
  8. package/dist/commands/ask-question.js +50 -4
  9. package/dist/commands/ask-question.test.js +92 -0
  10. package/dist/commands/btw.js +71 -66
  11. package/dist/commands/new-worktree.js +92 -35
  12. package/dist/commands/queue.js +17 -0
  13. package/dist/commands/worktrees.js +196 -139
  14. package/dist/context-awareness-plugin.js +16 -8
  15. package/dist/context-awareness-plugin.test.js +4 -2
  16. package/dist/discord-bot.js +35 -2
  17. package/dist/discord-command-registration.js +9 -2
  18. package/dist/memory-overview-plugin.js +3 -1
  19. package/dist/opencode.js +9 -0
  20. package/dist/queue-question-select-drain.e2e.test.js +135 -10
  21. package/dist/session-handler/thread-runtime-state.js +27 -0
  22. package/dist/session-handler/thread-session-runtime.js +58 -28
  23. package/dist/session-title-rename.test.js +12 -0
  24. package/dist/skill-filter.js +31 -0
  25. package/dist/skill-filter.test.js +65 -0
  26. package/dist/store.js +2 -0
  27. package/dist/system-message.js +12 -3
  28. package/dist/system-message.test.js +10 -6
  29. package/dist/thread-message-queue.e2e.test.js +109 -0
  30. package/dist/worktree-lifecycle.e2e.test.js +4 -1
  31. package/dist/worktrees.js +106 -12
  32. package/dist/worktrees.test.js +232 -6
  33. package/package.json +2 -2
  34. package/skills/goke/SKILL.md +13 -619
  35. package/skills/new-skill/SKILL.md +34 -10
  36. package/skills/npm-package/SKILL.md +336 -2
  37. package/skills/profano/SKILL.md +24 -0
  38. package/skills/zele/SKILL.md +50 -21
  39. package/src/agent-model.e2e.test.ts +1 -0
  40. package/src/anthropic-auth-plugin.ts +24 -4
  41. package/src/anthropic-auth-state.ts +45 -0
  42. package/src/btw-prefix-detection.test.ts +73 -0
  43. package/src/btw-prefix-detection.ts +23 -0
  44. package/src/cli.ts +138 -46
  45. package/src/commands/agent.ts +24 -2
  46. package/src/commands/ask-question.test.ts +111 -0
  47. package/src/commands/ask-question.ts +69 -4
  48. package/src/commands/btw.ts +105 -85
  49. package/src/commands/new-worktree.ts +107 -40
  50. package/src/commands/queue.ts +22 -0
  51. package/src/commands/worktrees.ts +246 -154
  52. package/src/context-awareness-plugin.test.ts +4 -2
  53. package/src/context-awareness-plugin.ts +16 -8
  54. package/src/discord-bot.ts +40 -2
  55. package/src/discord-command-registration.ts +12 -2
  56. package/src/memory-overview-plugin.ts +3 -1
  57. package/src/opencode.ts +9 -0
  58. package/src/queue-question-select-drain.e2e.test.ts +174 -10
  59. package/src/session-handler/thread-runtime-state.ts +36 -1
  60. package/src/session-handler/thread-session-runtime.ts +72 -32
  61. package/src/session-title-rename.test.ts +18 -0
  62. package/src/skill-filter.test.ts +83 -0
  63. package/src/skill-filter.ts +42 -0
  64. package/src/store.ts +17 -0
  65. package/src/system-message.test.ts +10 -6
  66. package/src/system-message.ts +12 -3
  67. package/src/thread-message-queue.e2e.test.ts +126 -0
  68. package/src/worktree-lifecycle.e2e.test.ts +6 -1
  69. package/src/worktrees.test.ts +274 -9
  70. package/src/worktrees.ts +144 -23
@@ -12,633 +12,27 @@ version: 0.0.1
12
12
 
13
13
  # goke
14
14
 
15
- Zero-dependency, type-safe CLI framework for TypeScript. A CAC replacement with Standard Schema support.
16
-
17
- 5 core APIs: `cli.option`, `cli.use`, `cli.version`, `cli.help`, `cli.parse`.
18
-
19
- ```ts
20
- import { goke } from 'goke'
21
- import { z } from 'zod'
22
-
23
- const cli = goke('mycli')
24
-
25
- cli
26
- .command('serve', 'Start the dev server')
27
- .option('--port <port>', z.number().default(3000).describe('Port to listen on'))
28
- .option('--host [host]', z.string().default('localhost').describe('Hostname to bind'))
29
- .option('--open', 'Open browser on start')
30
- .action((options) => {
31
- // options.port: number, options.host: string, options.open: boolean
32
- console.log(options)
33
- })
34
-
35
- cli.help()
36
- cli.version('1.0.0')
37
- cli.parse()
38
- ```
39
-
40
- ## Version
41
-
42
- Import `package.json` with `type: 'json'` and use the `version` field:
43
-
44
- ```ts
45
- import pkg from './package.json' with { type: 'json' }
46
-
47
- cli.version(pkg.version)
48
- ```
49
-
50
- This works in Node.js and keeps the version in sync with `package.json` automatically.
51
-
52
- ## Rules
53
-
54
- 1. Always use schema-based options (Zod, Valibot, etc.) for typed values — without a schema, all values are raw strings
55
- 2. **Never add `(default: X)` in the description string** when using `.default()` — goke extracts the default from the schema and appends it to help output automatically. Adding it in the description shows the default twice
56
- 3. Don't manually type `action` callback arguments — goke infers argument and option types automatically from the command signature and option schemas
57
- 4. Use `<angle brackets>` for required values, `[square brackets]` for optional values — this applies to both command arguments and option values
58
- 5. Use `z.array()` for options that can be passed multiple times (repeatable flags)
59
- 6. Use `z.enum()` for options constrained to a fixed set of values
60
- 7. Write very detailed descriptions for commands and options — agents and users rely on `--help` output as documentation. Include what the option does, when to use it, and examples if relevant
61
- 8. Add `.example()` to commands to show usage patterns in help output — use a `#` comment as the first line to explain the scenario
62
- 9. Options without brackets are boolean flags — `undefined` when not passed, `true` when passed (`--verbose`), `false` when negated (`--no-verbose`). This three-state behavior lets you distinguish "user explicitly set" from "not provided"
63
- 10. Kebab-case options are auto-camelCased in the parsed result (`--max-retries` → `options.maxRetries`)
64
- 11. Use `.use()` for middleware that reacts to global options (logging setup, auth, state init) — it runs before any command action
65
- 12. Place `.use()` after the `.option()` calls it depends on — type safety is positional in the chain
66
-
67
- ## Schema-based options
68
-
69
- Pass a Standard Schema (Zod, Valibot, ArkType) as the second argument to `.option()` for automatic type coercion. Description, default, and deprecated flag are extracted from the schema.
70
-
71
- ### Typed values
72
-
73
- ```ts
74
- // number — string "3000" coerced to number 3000
75
- .option('--port <port>', z.number().describe('Port number'))
76
-
77
- // integer — rejects decimals like "3.14"
78
- .option('--workers <n>', z.int().describe('Number of worker threads'))
79
-
80
- // string — preserves value as-is (no auto-conversion)
81
- .option('--name <name>', z.string().describe('Project name'))
82
-
83
- // boolean value option — accepts "true" or "false" strings
84
- .option('--flag <flag>', z.boolean().describe('Enable feature'))
85
- ```
86
-
87
- ### Default values
88
-
89
- Use `.default()` on the schema. The default is shown in help output automatically.
90
-
91
- ```ts
92
- // Port defaults to 3000 if not passed
93
- .option('--port [port]', z.number().default(3000).describe('Port to listen on'))
94
-
95
- // Host defaults to "localhost"
96
- .option('--host [host]', z.string().default('localhost').describe('Hostname to bind'))
97
- ```
98
-
99
- **Important:** use `[optional]` brackets when the option has a default — `<required>` brackets throw an error when the value is missing, even if a default exists.
100
-
101
- Help output for the above:
102
-
103
- ```
104
- --port [port] Port to listen on (default: 3000)
105
- --host [host] Hostname to bind (default: localhost)
106
- ```
107
-
108
- The `(default: 3000)` is appended automatically. Never write `.default(3000).describe('Port to listen on (default: 3000)')` — this would display the default twice.
109
-
110
- ### Enum options (constrained values)
111
-
112
- Use `z.enum()` for options that only accept specific values:
113
-
114
- ```ts
115
- .option('--format <format>', z.enum(['json', 'yaml', 'csv']).describe('Output format'))
116
- .option('--env <env>', z.enum(['dev', 'staging', 'production']).describe('Target environment'))
117
- ```
118
-
119
- Invalid values throw a clear error: `expected one of "json", "yaml", "csv", got "xml"`.
120
-
121
- ### Repeatable options (arrays)
122
-
123
- Use `z.array()` to allow passing the same flag multiple times:
124
-
125
- ```ts
126
- // Pass --tag multiple times: --tag foo --tag bar → ["foo", "bar"]
127
- .option('--tag <tag>', z.array(z.string()).describe('Tags (repeatable)'))
128
-
129
- // Typed array items: --id 1 --id 2 → [1, 2] (numbers, not strings)
130
- .option('--id <id>', z.array(z.number()).describe('IDs (repeatable)'))
131
- ```
132
-
133
- The optimal way for users to pass array values is repeating the flag:
15
+ Fetch the full README from GitHub and read it before using goke:
134
16
 
135
17
  ```bash
136
- mycli deploy --tag v2.1.0 --tag latest --tag rollback
137
- # → tags: ["v2.1.0", "latest", "rollback"]
138
- ```
139
-
140
- A single value is automatically wrapped: `--tag foo` → `["foo"]`.
141
-
142
- JSON array strings also work but are less ergonomic: `--ids '[1,2,3]'` → `[1, 2, 3]`.
143
-
144
- **Non-array schemas reject repeated flags.** If a user passes `--port 3000 --port 4000` with a `z.number()` schema, goke throws `does not accept multiple values`.
145
-
146
- ### Nullable options
147
-
148
- ```ts
149
- // Pass empty string "" to get null, or a number
150
- .option('--timeout <timeout>', z.nullable(z.number()).describe('Timeout in ms, empty for none'))
151
- ```
152
-
153
- ### Union types
154
-
155
- ```ts
156
- // Tries number first, falls back to string
157
- .option('--val <val>', z.union([z.number(), z.string()]).describe('A number or string value'))
158
- ```
159
-
160
- ### Deprecated options (hidden from help)
161
-
162
- Use `.meta({ deprecated: true })` to hide options from `--help` while still parsing them:
163
-
164
- ```ts
165
- .option('--old-port <port>', z.number().meta({ deprecated: true, description: 'Use --port instead' }))
166
- .option('--port <port>', z.number().describe('Port number'))
167
- ```
168
-
169
- ### No schema = raw strings
170
-
171
- Without a schema, all values stay as strings. `--port 3000` → `"3000"` (string, not number). Use schemas for type safety.
172
-
173
- ## Brackets
174
-
175
- | Syntax | Meaning |
176
- |--------|---------|
177
- | `<name>` in command | Required argument |
178
- | `[name]` in command | Optional argument |
179
- | `[...files]` in command | Variadic (collects remaining args into array) |
180
- | `<value>` in option | Required value (error if missing) |
181
- | `[value]` in option | Optional value (`undefined` if flag present without value) |
182
- | no brackets in option | Boolean flag (`undefined` if not passed, `true` if passed) |
183
-
184
- **Optionality is determined solely by bracket syntax, not by the schema.** `[square brackets]` makes an option optional regardless of whether the schema is `z.string()` or `z.string().optional()`. The schema's `.optional()` is never consulted for this — it only affects type coercion. So `z.string()` with `[--name]` is treated as optional: if the flag is omitted, `options.name` is `undefined` even though the schema has no `.optional()`.
185
-
186
- ## Global Options and Middleware
187
-
188
- Global options apply to all commands. Use `.use()` to register middleware that runs before any command action — for reacting to global options (logging, state init, auth).
189
-
190
- ```ts
191
- const cli = goke('mycli')
192
-
193
- cli
194
- .option('--verbose', z.boolean().default(false).describe('Enable verbose logging'))
195
- .option('--api-url [url]', z.string().default('https://api.example.com').describe('API base URL'))
196
- .use((options) => {
197
- // options.verbose: boolean, options.apiUrl: string — fully typed
198
- if (options.verbose) {
199
- process.env.LOG_LEVEL = 'debug'
200
- }
201
- })
202
-
203
- cli
204
- .command('deploy <env>', 'Deploy to environment')
205
- .action((env, options) => {
206
- // options includes global options (verbose, apiUrl) + command options
207
- console.log(`Deploying to ${env} via ${options.apiUrl}`)
208
- })
209
- ```
210
-
211
- Middleware runs in registration order, after parsing/validation, before the command action. Type safety is positional — each `.use()` only sees options declared before it in the chain:
212
-
213
- ```ts
214
- cli
215
- .option('--verbose', z.boolean().default(false).describe('Verbose'))
216
- .use((options) => {
217
- options.verbose // boolean — typed
218
- options.port // TypeScript error — not declared yet
219
- })
220
- .option('--port <port>', z.number().describe('Port'))
221
- .use((options) => {
222
- options.verbose // boolean — still visible
223
- options.port // number — now visible
224
- })
225
- ```
226
-
227
- Async middleware is supported — the chain awaits each middleware before proceeding:
228
-
229
- ```ts
230
- cli
231
- .option('--token <token>', z.string().describe('API token'))
232
- .use(async (options) => {
233
- globalState.client = await connectToApi(options.token)
234
- })
235
- ```
236
-
237
- ## Commands
238
-
239
- ### Basic commands with arguments
240
-
241
- ```ts
242
- cli
243
- .command('deploy <env>', 'Deploy to an environment')
244
- .option('--dry-run', 'Preview without deploying')
245
- .action((env, options) => {
246
- // env: string, options.dryRun: boolean
247
- })
248
- ```
249
-
250
- ### Root command (runs when no subcommand given)
251
-
252
- Use empty string `''` as the command name:
253
-
254
- ```ts
255
- // `mycli` runs the root command, `mycli status` runs the subcommand
256
- cli
257
- .command('', 'Deploy the current project')
258
- .option('--env <env>', z.string().default('production').describe('Target environment'))
259
- .action((options) => {})
260
-
261
- cli.command('status', 'Show deployment status').action(() => {})
262
- ```
263
-
264
- ### Space-separated subcommands
265
-
266
- For git-like nested commands:
267
-
268
- ```ts
269
- cli.command('mcp login <url>', 'Login to MCP server').action((url) => {})
270
- cli.command('mcp logout', 'Logout from MCP server').action(() => {})
271
- cli.command('git remote add <name> <url>', 'Add a git remote').action((name, url) => {})
272
- ```
273
-
274
- Greedy matching: `mcp login` matches before `mcp` when both exist.
275
-
276
- ### Variadic arguments
277
-
278
- The last argument can be variadic with `...` prefix:
279
-
280
- ```ts
281
- cli
282
- .command('build <entry> [...otherFiles]', 'Build your app')
283
- .action((entry, otherFiles, options) => {
284
- // entry: string, otherFiles: string[]
285
- })
286
- ```
287
-
288
- ### Command aliases
289
-
290
- ```ts
291
- cli.command('install', 'Install packages').alias('i').action(() => {})
292
- // Now both `mycli install` and `mycli i` work
293
- ```
294
-
295
- ## Double-dash `--` (end of options)
296
-
297
- `--` signals end of options. Everything after it goes into `options['--']` as a separate array, not mixed into positional args. This lets you distinguish command args from passthrough args.
298
-
299
- ```ts
300
- cli
301
- .command('run <script>', 'Run a script with injected env vars')
302
- .option('--env <env>', z.enum(['dev', 'staging', 'production']).describe('Target environment'))
303
- .action((script, options) => {
304
- // runner run --env staging server.js -- --port 3000
305
- // script = 'server.js'
306
- // options['--'] = ['--port', '3000']
307
- const extra = (options['--'] || []).join(' ')
308
- execSync(`node ${script} ${extra}`, { env: { ...process.env, ...loadSecrets(options.env) } })
309
- })
310
- ```
311
-
312
- ## Writing detailed help text
313
-
314
- Agents and users rely on `--help` as the primary documentation for a CLI. Write descriptions that are thorough and actionable.
315
-
316
- ### Detailed command descriptions
317
-
318
- Use `string-dedent` for multi-line descriptions:
319
-
320
- ```ts
321
- import dedent from 'string-dedent'
322
-
323
- cli
324
- .command(
325
- 'release <version>',
326
- dedent`
327
- Publish a versioned release to distribution channels.
328
-
329
- - Validates release metadata and changelog before publishing.
330
- - Builds production artifacts with reproducible settings.
331
- - Tags git history using semantic version format.
332
- - Publishes to npm and creates release notes.
333
-
334
- > Recommended: run with --dry-run first in CI to verify output.
335
- `,
336
- )
337
- .option('--channel <name>', z.enum(['stable', 'beta', 'alpha']).describe('Target release channel'))
338
- .option('--dry-run', 'Preview every step without publishing')
339
- .action((version, options) => {})
340
- ```
341
-
342
- ### Detailed option descriptions
343
-
344
- Write descriptions that tell the user exactly what the option does:
345
-
346
- ```ts
347
- // Bad — too terse
348
- .option('--limit [limit]', z.number().default(10).describe('Limit'))
349
-
350
- // Good — tells what it does, what values are valid
351
- .option('--limit [limit]', z.number().default(10).describe('Maximum number of results to return'))
352
- ```
353
-
354
- ### Examples in help output
355
-
356
- Add `.example()` to commands. Use `#` comments to explain the scenario:
357
-
358
- ```ts
359
- cli
360
- .command('deploy', 'Deploy current app')
361
- .option('--env <env>', z.enum(['staging', 'production']).describe('Target environment'))
362
- .example('# Deploy to staging first')
363
- .example('mycli deploy --env staging')
364
- .example('# Then deploy to production')
365
- .example('mycli deploy --env production')
366
- .action(() => {})
367
- ```
368
-
369
- Root-level examples:
370
-
371
- ```ts
372
- cli.example((bin) => `${bin} deploy --env production`)
373
- ```
374
-
375
- ## Boolean flags
376
-
377
- Options without brackets are boolean flags. They default to `undefined` (not `false`), so you can distinguish between "not passed" and "explicitly set":
378
-
379
- ```ts
380
- .option('--verbose', 'Enable verbose output')
381
- .option('--no-cache', 'Disable caching')
382
- ```
383
-
384
- | Input | `options.verbose` | `options.cache` |
385
- |-------|-------------------|-----------------|
386
- | *(not passed)* | `undefined` | `undefined` |
387
- | `--verbose` | `true` | — |
388
- | `--no-verbose` | `false` | — |
389
- | `--no-cache` | — | `false` |
390
-
391
- This lets you apply defaults or merge configs only when the user didn't explicitly set a flag:
392
-
393
- ```ts
394
- .action((options) => {
395
- // undefined means "user didn't say" — apply your own default
396
- const verbose = options.verbose ?? config.verbose ?? false
397
- })
398
- ```
399
-
400
- ## Dot-nested options
401
-
402
- ```ts
403
- cli.option('--env <env>', 'Set envs')
404
- // --env.API_SECRET xxx → options.env = { API_SECRET: 'xxx' }
405
- ```
406
-
407
- ## Short aliases
408
-
409
- ```ts
410
- .option('-p, --port <port>', z.number().describe('Port number'))
411
- // -p 3000 and --port 3000 both work
412
- // options.port and options.p both equal 3000
413
- ```
414
-
415
- ## Interactive prompts with @clack/prompts
416
-
417
- For commands that need user input (login, setup, init), use `@clack/prompts` for select menus, password inputs, and text prompts. **Every interactive input must also be available as a CLI option** so agents and CI can use the command non-interactively.
418
-
419
- The pattern: check if flags are present — if yes, use them directly. If no flags, fall back to interactive prompts.
420
-
421
- ```ts
422
- import { select, password, isCancel, cancel } from '@clack/prompts'
423
-
424
- cli
425
- .command('login', 'Configure API keys interactively or via flags')
426
- .option('-p, --provider [name]', z.string().describe('Provider for non-interactive login (google, openai)'))
427
- .option('-k, --key [key]', z.string().describe('API key for non-interactive login'))
428
- .action(async (options) => {
429
- // Non-interactive path (agents, CI)
430
- if (options.provider) {
431
- saveKey(options.provider, options.key || await readKeyFromStdin())
432
- return
433
- }
434
- // Interactive path (humans)
435
- // NEVER use hint in clack select options. looks ugly
436
- const provider = await select({ message: 'Select provider', options: [...] })
437
- if (isCancel(provider)) { cancel(); process.exit(0) }
438
- const key = await password({ message: 'Paste API key' })
439
- if (isCancel(key)) { cancel(); process.exit(0) }
440
- saveKey(provider, key.trim())
441
- })
442
- ```
443
-
444
- ## Programmatic help text
445
-
446
- `cli.helpText()` returns the formatted help string without printing it. Useful for embedding help text in docs, READMEs, or other programmatic uses:
447
-
448
- ```ts
449
- const cli = goke('mycli')
450
- cli.command('build', 'Build project')
451
- cli.option('--watch', 'Watch mode')
452
- cli.help()
453
-
454
- const help = cli.helpText()
455
- // => "mycli\n\nUsage:\n $ mycli ..."
456
- ```
457
-
458
- The string includes ANSI color codes (same as `--help` output). Strip them if you need plain text.
459
-
460
- `cli.outputHelp()` still exists and prints to stdout — it calls `helpText()` internally.
461
-
462
- ## Injectable I/O (testing)
463
-
464
- Override stdout, stderr, argv, and exit for testing:
465
-
466
- ```ts
467
- import goke, { createConsole } from 'goke'
468
-
469
- const stdout = { lines: [], write(data) { this.lines.push(data) } }
470
- const cli = goke('mycli', {
471
- stdout,
472
- stderr: process.stderr,
473
- exit: () => {}, // prevent process.exit in tests
474
- })
475
- ```
476
-
477
- ## @goke/mcp — MCP ↔ CLI bridge
478
-
479
- ### MCP server → CLI
480
-
481
- `addMcpCommands` auto-discovers tools from an MCP server and registers them as CLI commands with typed options from JSON Schema:
482
-
483
- ```ts
484
- import { goke } from 'goke'
485
- import { addMcpCommands } from '@goke/mcp'
486
-
487
- const cli = goke('mycli')
488
-
489
- await addMcpCommands({
490
- cli,
491
- getMcpUrl: () => 'https://your-mcp-server.com/mcp',
492
- oauth: {
493
- clientName: 'My CLI',
494
- load: () => loadConfig().oauthState,
495
- save: (state) => saveConfig({ oauthState: state }),
496
- },
497
- loadCache: () => loadConfig().cache,
498
- saveCache: (cache) => saveConfig({ cache }),
499
- })
500
-
501
- cli.help()
502
- cli.parse()
18
+ curl -L https://raw.githubusercontent.com/remorses/goke/main/README.md
503
19
  ```
504
20
 
505
- Tools are cached for 1 hour. OAuth is lazy triggered only on 401 errors.
21
+ > Read the README in full every time you use goke.
22
+ >
23
+ > Important: never use `head` or `tail` to truncate it. Read the full README instead.
506
24
 
507
- ### CLI → MCP server
508
-
509
- `createMcpAction` turns a CLI into a stdio MCP server. Every command becomes an MCP tool. The MCP command itself is auto-excluded from the tool list.
510
-
511
- ```ts
512
- import { goke } from 'goke'
513
- import { z } from 'zod'
514
- import { createMcpAction } from '@goke/mcp'
515
-
516
- const cli = goke('mycli')
517
-
518
- cli
519
- .command('search', 'Search pages')
520
- .option('--query <query>', z.string().describe('Search query'))
521
- .action((options) => findPages(options.query))
522
-
523
- cli.command('mcp', 'Start MCP server over stdio')
524
- .action(createMcpAction({ cli }))
525
-
526
- cli.help()
527
- cli.parse()
528
- ```
529
-
530
- Options: `commandFilter`, `sanitizeToolName`, `serverName`, `serverVersion`, `createTransport`.
531
-
532
- ### Installing an MCP server in clients
533
-
534
- Users can install any CLI that exposes an `mcp` command using [@playwriter/install-mcp](https://github.com/nicepkg/install-mcp) — a cross-platform tool that handles config file locations for every major MCP client:
25
+ ## Install
535
26
 
536
27
  ```bash
537
- npx @playwriter/install-mcp mycli --client claude-desktop
538
- npx @playwriter/install-mcp mycli --client cursor
539
- npx @playwriter/install-mcp mycli --client vscode
28
+ npm install goke
540
29
  ```
541
30
 
542
- Supports `claude-desktop`, `cursor`, `vscode`, `windsurf`, `claude-code`, `opencode`, `zed`, `goose`, `cline`, `codex`, `gemini-cli`, and more. For custom arguments: `npx @playwriter/install-mcp 'npx mycli mcp' --client cursor`.
543
-
544
- ## Complete example
545
-
546
- ```ts
547
- import { goke } from 'goke'
548
- import { z } from 'zod'
549
- import dedent from 'string-dedent'
550
-
551
- const cli = goke('acme')
552
-
553
- cli
554
- .command('', 'Run the default workflow')
555
- .option('--env [env]', z.string().default('development').describe('Target environment'))
556
- .action((options) => {
557
- console.log(`Running in ${options.env}`)
558
- })
559
-
560
- cli
561
- .command(
562
- 'deploy <target>',
563
- dedent`
564
- Deploy the application to a target environment.
565
-
566
- - Builds optimized production bundle.
567
- - Uploads artifacts to the target.
568
- - Runs post-deploy health checks.
569
-
570
- > Always deploy to staging first before production.
571
- `,
572
- )
573
- .option('--env <env>', z.enum(['staging', 'production']).describe('Deployment environment'))
574
- .option('--tag <tag>', z.array(z.string()).describe('Docker image tags to deploy (repeatable)'))
575
- .option('--workers <n>', z.int().default(4).describe('Number of parallel upload workers'))
576
- .option('--timeout [ms]', z.number().default(30000).describe('Deployment timeout in milliseconds'))
577
- .option('--dry-run', 'Preview the deployment plan without executing')
578
- .option('--verbose', 'Enable detailed deployment logging')
579
- .example('# Deploy to staging with custom tags')
580
- .example('acme deploy web --env staging --tag v2.1.0 --tag latest')
581
- .example('# Dry-run production deploy')
582
- .example('acme deploy api --env production --dry-run --verbose')
583
- .action((target, options) => {
584
- console.log('deploying', target, options)
585
- })
586
-
587
- cli
588
- .command('db migrate', 'Apply pending database migrations in sequence')
589
- .option('--target <migration>', z.string().describe('Apply up to a specific migration ID'))
590
- .option('--dry-run', 'Print SQL plan without executing')
591
- .option('--verbose', 'Show each executed SQL statement')
592
- .action((options) => {
593
- console.log('migrating', options)
594
- })
595
-
596
- cli
597
- .command('config set <key> <value>', 'Set a configuration value')
598
- .action((key, value) => {
599
- console.log('setting', key, value)
600
- })
601
-
602
- cli.help()
603
- cli.version('1.0.0')
604
- cli.parse()
605
- ```
606
-
607
- ## `openInBrowser(url)`
608
-
609
- Opens a URL in the default browser. In non-TTY environments (CI, piped output, agents), prints the URL to stdout instead of opening a browser.
610
-
611
- ```ts
612
- import { openInBrowser } from 'goke'
613
-
614
- openInBrowser('https://example.com/dashboard')
615
- ```
616
-
617
- Use this after generating URLs (OAuth callbacks, dashboards, docs links) so interactive users get a browser tab and non-interactive environments get a printable URL.
618
-
619
- ## Exposing your CLI as a skill
620
-
621
- When you build a CLI with goke, the optimal way to create a skill for it is a minimal SKILL.md that tells agents to run `--help` before using the CLI. This way descriptions, examples, and usage patterns live in the CLI code (collocated with the implementation) instead of a separate markdown file that can go stale.
622
-
623
- Example SKILL.md for a CLI built with goke:
624
-
625
- ````markdown
626
- ---
627
- name: acme
628
- description: >
629
- acme is a deployment CLI. Always run `acme --help` before using it
630
- to discover available commands, options, and usage examples.
631
- ---
632
-
633
- # acme
634
-
635
- Always run `acme --help` before using this CLI. The help output contains
636
- all commands, options, defaults, and usage examples.
31
+ ## Quick Notes
637
32
 
638
- For subcommand details: `acme <command> --help`
639
- ````
33
+ - Core APIs: `cli.option`, `cli.use`, `cli.version`, `cli.help`, `cli.parse`
34
+ - Prefer injected `{ fs, console, process }` over globals
35
+ - Use relative paths with injected `fs`; if a helper needs current-cwd semantics, pass injected `process.cwd` into that helper
36
+ - For JustBash compatibility tests, import the existing CLI from app code instead of defining a new CLI inside the test
640
37
 
641
- This is the recommended pattern because:
642
- - Descriptions and examples are defined once in `.option()` and `.example()` calls
643
- - Help output always matches the actual CLI behavior
644
- - No separate documentation to maintain or keep in sync
38
+ The README is the source of truth for rules, examples, testing patterns, JustBash integration, and API details.