goke 6.9.0 → 6.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +436 -308
- package/dist/__test__/just-bash.test.js +7 -7
- package/dist/__test__/readme-examples.test.js +149 -13
- 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 +95 -5
- package/dist/goke.d.ts.map +1 -1
- package/dist/goke.js +487 -4
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/just-bash.d.ts.map +1 -1
- package/dist/just-bash.js +1 -2
- 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 +471 -308
- package/src/__test__/just-bash.test.ts +7 -7
- package/src/__test__/readme-examples.test.ts +161 -13
- 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 +540 -8
- package/src/index.ts +11 -2
- package/src/just-bash.ts +1 -2
- package/src/runtime-browser.ts +1 -1
- package/src/runtime-node.ts +19 -11
|
@@ -54,7 +54,7 @@ function gokeTestable(name = '', options?: Partial<GokeOptions>) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
describe('injected execution context', () => {
|
|
57
|
-
test('command action receives injected console and process', () => {
|
|
57
|
+
test('command action receives injected console and process', async () => {
|
|
58
58
|
const stdout = createTestOutputStream()
|
|
59
59
|
const cli = gokeTestable('mycli', { stdout })
|
|
60
60
|
let seenArgv: string[] | undefined
|
|
@@ -66,13 +66,13 @@ describe('injected execution context', () => {
|
|
|
66
66
|
seenArgv = process.argv
|
|
67
67
|
})
|
|
68
68
|
|
|
69
|
-
cli.parse(['node', 'bin', 'status'], { run: true })
|
|
69
|
+
await cli.parse(['node', 'bin', 'status'], { run: true })
|
|
70
70
|
|
|
71
71
|
expect(stdout.text).toBe('ready\n')
|
|
72
72
|
expect(seenArgv).toEqual(['node', 'bin', 'status'])
|
|
73
73
|
})
|
|
74
74
|
|
|
75
|
-
test('middleware receives injected console and process', () => {
|
|
75
|
+
test('middleware receives injected console and process', async () => {
|
|
76
76
|
const stdout = createTestOutputStream()
|
|
77
77
|
const cli = gokeTestable('mycli', { stdout })
|
|
78
78
|
let seenArgv: string[] | undefined
|
|
@@ -85,7 +85,7 @@ describe('injected execution context', () => {
|
|
|
85
85
|
.command('build', 'Build')
|
|
86
86
|
.action(() => {})
|
|
87
87
|
|
|
88
|
-
cli.parse(['node', 'bin', 'build'], { run: true })
|
|
88
|
+
await cli.parse(['node', 'bin', 'build'], { run: true })
|
|
89
89
|
|
|
90
90
|
expect(stdout.text).toBe('middleware\n')
|
|
91
91
|
expect(seenArgv).toEqual(['node', 'bin', 'build'])
|
|
@@ -93,14 +93,14 @@ describe('injected execution context', () => {
|
|
|
93
93
|
})
|
|
94
94
|
|
|
95
95
|
describe('clone', () => {
|
|
96
|
-
test('clone creates isolated parse state', () => {
|
|
96
|
+
test('clone creates isolated parse state', async () => {
|
|
97
97
|
const cli = gokeTestable('mycli')
|
|
98
98
|
|
|
99
99
|
cli.command('build', 'Build').action(() => {})
|
|
100
100
|
|
|
101
101
|
const cloned = cli.clone({ exit: () => {} })
|
|
102
102
|
|
|
103
|
-
cloned.parse(['node', 'bin', 'build'], { run: false })
|
|
103
|
+
await cloned.parse(['node', 'bin', 'build'], { run: false })
|
|
104
104
|
|
|
105
105
|
expect(cloned).not.toBe(cli)
|
|
106
106
|
expect(cloned.matchedCommandName).toBe('build')
|
|
@@ -109,7 +109,7 @@ describe('clone', () => {
|
|
|
109
109
|
})
|
|
110
110
|
|
|
111
111
|
describe('createExecutionContext', () => {
|
|
112
|
-
test('returns a context that mirrors the cli defaults when called with no override', () => {
|
|
112
|
+
test('returns a context that mirrors the cli defaults when called with no override', async () => {
|
|
113
113
|
const stdout = createTestOutputStream()
|
|
114
114
|
const stderr = createTestOutputStream()
|
|
115
115
|
const cli = gokeTestable('mycli', {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { describe, expect, test } from 'vitest'
|
|
6
6
|
import { z } from 'zod'
|
|
7
|
-
import goke, { openInBrowser } from '../index.js'
|
|
7
|
+
import goke, { openInBrowser, generateDocs } from '../index.js'
|
|
8
8
|
import type { GokeOptions, GokeOutputStream } from '../index.js'
|
|
9
9
|
|
|
10
10
|
const ANSI_RE = /\x1B\[[0-9;]*m/g
|
|
@@ -52,7 +52,7 @@ describe('README smoke tests', () => {
|
|
|
52
52
|
console.log(`logs ${deploymentId} ${options.lines}`)
|
|
53
53
|
})
|
|
54
54
|
|
|
55
|
-
cli.parse(['node', 'bin', '--env', 'production', 'up', '--dry-run'], { run: false })
|
|
55
|
+
await cli.parse(['node', 'bin', '--env', 'production', 'up', '--dry-run'], { run: false })
|
|
56
56
|
await cli.runMatchedCommand()
|
|
57
57
|
|
|
58
58
|
expect(stdout.text).toBe(
|
|
@@ -61,7 +61,7 @@ describe('README smoke tests', () => {
|
|
|
61
61
|
|
|
62
62
|
stdout.lines.length = 0
|
|
63
63
|
|
|
64
|
-
cli.parse(['node', 'bin', 'logs', 'dep_123'], { run: false })
|
|
64
|
+
await cli.parse(['node', 'bin', 'logs', 'dep_123'], { run: false })
|
|
65
65
|
await cli.runMatchedCommand()
|
|
66
66
|
|
|
67
67
|
expect(stdout.text).toBe('Environment: staging\nlogs dep_123 100\n')
|
|
@@ -96,7 +96,7 @@ describe('README smoke tests', () => {
|
|
|
96
96
|
|
|
97
97
|
expect(stripAnsi(cli.helpText())).toContain('mycli lint src/**/*.ts')
|
|
98
98
|
|
|
99
|
-
cli.parse(['node', 'bin', '--type', 'bun', '--name', 'Tommy', 'build', 'src/index.ts', '--minify'], { run: false })
|
|
99
|
+
await cli.parse(['node', 'bin', '--type', 'bun', '--name', 'Tommy', 'build', 'src/index.ts', '--minify'], { run: false })
|
|
100
100
|
await cli.runMatchedCommand()
|
|
101
101
|
|
|
102
102
|
expect(stdout.text).toBe(
|
|
@@ -135,7 +135,7 @@ describe('README smoke tests', () => {
|
|
|
135
135
|
)
|
|
136
136
|
})
|
|
137
137
|
|
|
138
|
-
cli.parse(['node', 'bin', '--env', 'staging', '--dry-run'], { run: false })
|
|
138
|
+
await cli.parse(['node', 'bin', '--env', 'staging', '--dry-run'], { run: false })
|
|
139
139
|
await cli.runMatchedCommand()
|
|
140
140
|
|
|
141
141
|
expect(stdout.text).toBe(
|
|
@@ -144,7 +144,7 @@ describe('README smoke tests', () => {
|
|
|
144
144
|
|
|
145
145
|
stdout.lines.length = 0
|
|
146
146
|
|
|
147
|
-
cli.parse(['node', 'bin', 'logs', 'abc123', '--follow'], { run: false })
|
|
147
|
+
await cli.parse(['node', 'bin', 'logs', 'abc123', '--follow'], { run: false })
|
|
148
148
|
await cli.runMatchedCommand()
|
|
149
149
|
|
|
150
150
|
expect(stdout.text).toBe(
|
|
@@ -154,7 +154,7 @@ describe('README smoke tests', () => {
|
|
|
154
154
|
})
|
|
155
155
|
|
|
156
156
|
describe('documented command APIs', () => {
|
|
157
|
-
test('alias runs the same command through a short name', () => {
|
|
157
|
+
test('alias runs the same command through a short name', async () => {
|
|
158
158
|
const cli = gokeTestable('mycli')
|
|
159
159
|
let seen = ''
|
|
160
160
|
|
|
@@ -162,12 +162,12 @@ describe('documented command APIs', () => {
|
|
|
162
162
|
seen = 'install'
|
|
163
163
|
})
|
|
164
164
|
|
|
165
|
-
cli.parse(['node', 'bin', 'i'], { run: true })
|
|
165
|
+
await cli.parse(['node', 'bin', 'i'], { run: true })
|
|
166
166
|
|
|
167
167
|
expect(seen).toBe('install')
|
|
168
168
|
})
|
|
169
169
|
|
|
170
|
-
test('command helpText returns command-specific help without printing', () => {
|
|
170
|
+
test('command helpText returns command-specific help without printing', async () => {
|
|
171
171
|
const stdout = createTestOutputStream()
|
|
172
172
|
const cli = goke('mycli', { stdout })
|
|
173
173
|
|
|
@@ -187,7 +187,7 @@ describe('documented command APIs', () => {
|
|
|
187
187
|
expect(stdout.text).toBe('')
|
|
188
188
|
})
|
|
189
189
|
|
|
190
|
-
test('openInBrowser prints the URL to stdout in non-tty environments', () => {
|
|
190
|
+
test('openInBrowser prints the URL to stdout in non-tty environments', async () => {
|
|
191
191
|
const url = 'https://example.com/dashboard'
|
|
192
192
|
const originalStdoutWrite = process.stdout.write
|
|
193
193
|
const originalStderrWrite = process.stderr.write
|
|
@@ -209,7 +209,7 @@ describe('documented command APIs', () => {
|
|
|
209
209
|
}) as typeof process.stderr.write
|
|
210
210
|
|
|
211
211
|
try {
|
|
212
|
-
openInBrowser(url)
|
|
212
|
+
await openInBrowser(url)
|
|
213
213
|
} finally {
|
|
214
214
|
process.stdout.write = originalStdoutWrite
|
|
215
215
|
process.stderr.write = originalStderrWrite
|
|
@@ -219,7 +219,155 @@ describe('documented command APIs', () => {
|
|
|
219
219
|
})
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
expect(stdout).toBe(
|
|
223
|
-
expect(stderr).toBe(
|
|
222
|
+
expect(stdout).toBe('')
|
|
223
|
+
expect(stderr).toBe(`${url}\n`)
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
describe('generateDocs', () => {
|
|
228
|
+
test('generates pages for CLI with multiple commands', async () => {
|
|
229
|
+
const cli = gokeTestable('sentry')
|
|
230
|
+
.version('1.0.0')
|
|
231
|
+
.help()
|
|
232
|
+
|
|
233
|
+
cli
|
|
234
|
+
.command('event view <id>', 'View details of a specific event')
|
|
235
|
+
.option('-w, --web', 'Open in browser')
|
|
236
|
+
.option('--spans <spans>', z.string().default('3').describe('Span tree depth limit'))
|
|
237
|
+
.example('```\nsentry event view abc123\n```')
|
|
238
|
+
|
|
239
|
+
cli
|
|
240
|
+
.command('event list <issue>', 'List events for an issue')
|
|
241
|
+
.option('-n, --limit <limit>', z.number().default(25).describe('Number of events'))
|
|
242
|
+
.option('-q, --query <query>', 'Search query')
|
|
243
|
+
|
|
244
|
+
cli
|
|
245
|
+
.command('hidden-cmd', 'Should not appear')
|
|
246
|
+
.hidden()
|
|
247
|
+
|
|
248
|
+
const pages = generateDocs({ cli })
|
|
249
|
+
|
|
250
|
+
expect(pages.map((p) => p.slug)).toMatchInlineSnapshot(`
|
|
251
|
+
[
|
|
252
|
+
"index",
|
|
253
|
+
"event-view",
|
|
254
|
+
"event-list",
|
|
255
|
+
]
|
|
256
|
+
`)
|
|
257
|
+
|
|
258
|
+
// Index page
|
|
259
|
+
expect(pages[0].content).toMatchInlineSnapshot(`
|
|
260
|
+
"# sentry
|
|
261
|
+
|
|
262
|
+
Version: 1.0.0
|
|
263
|
+
|
|
264
|
+
## Commands
|
|
265
|
+
|
|
266
|
+
| Command | Description |
|
|
267
|
+
|---------|-------------|
|
|
268
|
+
| [\`event view\`](./event-view.md) | View details of a specific event |
|
|
269
|
+
| [\`event list\`](./event-list.md) | List events for an issue |
|
|
270
|
+
|
|
271
|
+
## Global Options
|
|
272
|
+
|
|
273
|
+
| Option | Default | Description |
|
|
274
|
+
|--------|---------|-------------|
|
|
275
|
+
| \`-v, --version\` | - | Display version number |
|
|
276
|
+
| \`-h, --help\` | - | Display this message |
|
|
277
|
+
"
|
|
278
|
+
`)
|
|
279
|
+
|
|
280
|
+
// Command page with examples
|
|
281
|
+
expect(pages[1].content).toMatchInlineSnapshot(`
|
|
282
|
+
"# event view
|
|
283
|
+
|
|
284
|
+
View details of a specific event
|
|
285
|
+
|
|
286
|
+
## Usage
|
|
287
|
+
|
|
288
|
+
\`\`\`sh
|
|
289
|
+
sentry event view <id>
|
|
290
|
+
\`\`\`
|
|
291
|
+
|
|
292
|
+
## Arguments
|
|
293
|
+
|
|
294
|
+
| Argument | Required | Description |
|
|
295
|
+
|----------|----------|-------------|
|
|
296
|
+
| \`<id>\` | Yes | id |
|
|
297
|
+
|
|
298
|
+
## Options
|
|
299
|
+
|
|
300
|
+
| Option | Default | Description |
|
|
301
|
+
|--------|---------|-------------|
|
|
302
|
+
| \`-w, --web\` | - | Open in browser |
|
|
303
|
+
| \`--spans <spans>\` | \`3\` | Span tree depth limit |
|
|
304
|
+
|
|
305
|
+
## Global Options
|
|
306
|
+
|
|
307
|
+
| Option | Default | Description |
|
|
308
|
+
|--------|---------|-------------|
|
|
309
|
+
| \`-v, --version\` | - | Display version number |
|
|
310
|
+
| \`-h, --help\` | - | Display this message |
|
|
311
|
+
|
|
312
|
+
## Examples
|
|
313
|
+
|
|
314
|
+
\`\`\`
|
|
315
|
+
sentry event view abc123
|
|
316
|
+
\`\`\`
|
|
317
|
+
"
|
|
318
|
+
`)
|
|
319
|
+
|
|
320
|
+
// Command page without examples
|
|
321
|
+
expect(pages[2].content).toMatchInlineSnapshot(`
|
|
322
|
+
"# event list
|
|
323
|
+
|
|
324
|
+
List events for an issue
|
|
325
|
+
|
|
326
|
+
## Usage
|
|
327
|
+
|
|
328
|
+
\`\`\`sh
|
|
329
|
+
sentry event list <issue>
|
|
330
|
+
\`\`\`
|
|
331
|
+
|
|
332
|
+
## Arguments
|
|
333
|
+
|
|
334
|
+
| Argument | Required | Description |
|
|
335
|
+
|----------|----------|-------------|
|
|
336
|
+
| \`<issue>\` | Yes | issue |
|
|
337
|
+
|
|
338
|
+
## Options
|
|
339
|
+
|
|
340
|
+
| Option | Default | Description |
|
|
341
|
+
|--------|---------|-------------|
|
|
342
|
+
| \`-n, --limit <limit>\` | \`25\` | Number of events |
|
|
343
|
+
| \`-q, --query <query>\` | - | Search query |
|
|
344
|
+
|
|
345
|
+
## Global Options
|
|
346
|
+
|
|
347
|
+
| Option | Default | Description |
|
|
348
|
+
|--------|---------|-------------|
|
|
349
|
+
| \`-v, --version\` | - | Display version number |
|
|
350
|
+
| \`-h, --help\` | - | Display this message |
|
|
351
|
+
"
|
|
352
|
+
`)
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
test('handles CLI with no commands', async () => {
|
|
356
|
+
const cli = gokeTestable('empty')
|
|
357
|
+
const pages = generateDocs({ cli })
|
|
358
|
+
expect(pages).toEqual([])
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
test('skips deprecated options', async () => {
|
|
362
|
+
const cli = gokeTestable('mycli')
|
|
363
|
+
cli
|
|
364
|
+
.command('deploy', 'Deploy the app')
|
|
365
|
+
.option('--force', 'Skip confirmation')
|
|
366
|
+
.option('--old-flag', z.boolean().meta({ deprecated: true }).describe('Use --force instead'))
|
|
367
|
+
|
|
368
|
+
const pages = generateDocs({ cli })
|
|
369
|
+
const deployPage = pages.find((p) => p.slug === 'deploy')!
|
|
370
|
+
// The deprecated option should not appear
|
|
371
|
+
expect(deployPage.content).not.toContain('old-flag')
|
|
224
372
|
})
|
|
225
373
|
})
|
|
@@ -522,6 +522,33 @@ describe('type-level: README TypeScript examples', () => {
|
|
|
522
522
|
})
|
|
523
523
|
})
|
|
524
524
|
|
|
525
|
+
test('getAction() returns correctly typed function', () => {
|
|
526
|
+
const cmd = goke('test')
|
|
527
|
+
.command('convert <input> <output>', 'Convert file format')
|
|
528
|
+
.option('--quality <quality>', z.number())
|
|
529
|
+
.option('--format <format>', z.enum(['png', 'jpg']))
|
|
530
|
+
.action((input, output, options, ctx) => {
|
|
531
|
+
void input; void output; void options; void ctx
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
const action = cmd.getAction()
|
|
535
|
+
expectTypeOf(action).parameter(0).toEqualTypeOf<string>() // input
|
|
536
|
+
expectTypeOf(action).parameter(1).toEqualTypeOf<string>() // output
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
test('getAction() with no positional args has (options, ctx) signature', () => {
|
|
540
|
+
const cmd = goke('test')
|
|
541
|
+
.command('deploy', 'Deploy')
|
|
542
|
+
.option('--env <env>', z.enum(['staging', 'production']))
|
|
543
|
+
.action((options, ctx) => {
|
|
544
|
+
void options; void ctx
|
|
545
|
+
})
|
|
546
|
+
|
|
547
|
+
const action = cmd.getAction()
|
|
548
|
+
// First param is options with env
|
|
549
|
+
expectTypeOf(action).parameter(0).toMatchTypeOf<{ env: 'staging' | 'production' }>()
|
|
550
|
+
})
|
|
551
|
+
|
|
525
552
|
test('README global options and middleware example stays typed end-to-end', () => {
|
|
526
553
|
// `z.boolean().default(false)` and `z.string().default(...)` are
|
|
527
554
|
// effectively required at runtime: the default applies when the flag is
|
package/src/agents.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI coding agent detection for goke CLIs.
|
|
3
|
+
*
|
|
4
|
+
* Detects whether the current process is running inside an AI coding agent
|
|
5
|
+
* (Claude, Cursor, Codex, Gemini, etc.) by checking environment variables.
|
|
6
|
+
* Ported from unjs/std-env with the same detection logic.
|
|
7
|
+
*
|
|
8
|
+
* CLI authors can use this to adjust behavior: skip interactive prompts,
|
|
9
|
+
* prefer structured output, avoid browser opens, etc.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const env: Record<string, string | undefined> =
|
|
13
|
+
globalThis.process?.env || Object.create(null)
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Known AI coding agent names.
|
|
17
|
+
*/
|
|
18
|
+
export type AgentName =
|
|
19
|
+
| (string & {})
|
|
20
|
+
| 'cursor'
|
|
21
|
+
| 'claude'
|
|
22
|
+
| 'devin'
|
|
23
|
+
| 'replit'
|
|
24
|
+
| 'gemini'
|
|
25
|
+
| 'codex'
|
|
26
|
+
| 'auggie'
|
|
27
|
+
| 'opencode'
|
|
28
|
+
| 'kiro'
|
|
29
|
+
| 'goose'
|
|
30
|
+
| 'pi'
|
|
31
|
+
|
|
32
|
+
type EnvCheck = string | (() => boolean)
|
|
33
|
+
|
|
34
|
+
type InternalAgent = [agentName: AgentName, envChecks: EnvCheck[]]
|
|
35
|
+
|
|
36
|
+
function envMatcher(envKey: string, regex: RegExp) {
|
|
37
|
+
return () => {
|
|
38
|
+
const value = env[envKey]
|
|
39
|
+
return value ? regex.test(value) : false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Detection order matters: specific agents first, IDE-based agents last
|
|
44
|
+
// so that agents running inside those IDEs are detected by their own env vars first.
|
|
45
|
+
const agents: InternalAgent[] = [
|
|
46
|
+
// CLI agents
|
|
47
|
+
['claude', ['CLAUDECODE', 'CLAUDE_CODE']],
|
|
48
|
+
['replit', ['REPL_ID']],
|
|
49
|
+
['gemini', ['GEMINI_CLI']],
|
|
50
|
+
['codex', ['CODEX_SANDBOX', 'CODEX_THREAD_ID']],
|
|
51
|
+
['opencode', ['OPENCODE']],
|
|
52
|
+
['pi', [envMatcher('PATH', /\.pi[\\/]agent/)]],
|
|
53
|
+
['auggie', ['AUGMENT_AGENT']],
|
|
54
|
+
['goose', ['GOOSE_PROVIDER']],
|
|
55
|
+
|
|
56
|
+
// IDE-based agents (checked last)
|
|
57
|
+
['devin', [envMatcher('EDITOR', /devin/)]],
|
|
58
|
+
['cursor', ['CURSOR_AGENT']],
|
|
59
|
+
['kiro', [envMatcher('TERM_PROGRAM', /kiro/)]],
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Information about the detected AI coding agent.
|
|
64
|
+
*/
|
|
65
|
+
export type AgentInfo = {
|
|
66
|
+
/** The name of the detected agent, or undefined if no agent was detected. */
|
|
67
|
+
name?: AgentName
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Detect the current AI coding agent from environment variables.
|
|
72
|
+
*
|
|
73
|
+
* Checks `AI_AGENT` env var first (explicit override), then scans for
|
|
74
|
+
* known agent-specific env vars in priority order.
|
|
75
|
+
*
|
|
76
|
+
* Supported agents: `cursor`, `claude`, `devin`, `replit`, `gemini`,
|
|
77
|
+
* `codex`, `auggie`, `opencode`, `kiro`, `goose`, `pi`
|
|
78
|
+
*/
|
|
79
|
+
export function detectAgent(): AgentInfo {
|
|
80
|
+
const aiAgent = env.AI_AGENT
|
|
81
|
+
if (aiAgent) {
|
|
82
|
+
return { name: aiAgent.toLowerCase() }
|
|
83
|
+
}
|
|
84
|
+
for (const [name, checks] of agents) {
|
|
85
|
+
for (const check of checks) {
|
|
86
|
+
if (typeof check === 'string' ? env[check] : check()) {
|
|
87
|
+
return { name }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return {}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Detected agent info, evaluated once at import time. */
|
|
95
|
+
export const agentInfo: AgentInfo = /* #__PURE__ */ detectAgent()
|
|
96
|
+
|
|
97
|
+
/** Name of the detected agent, or undefined if not running inside one. */
|
|
98
|
+
export const agent: AgentName | undefined = agentInfo.name
|
|
99
|
+
|
|
100
|
+
/** Whether the current process is running inside an AI coding agent. */
|
|
101
|
+
export const isAgent: boolean = !!agentInfo.name
|