@inspecto-dev/cli 0.3.0 → 0.3.2
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/.turbo/turbo-build.log +20 -19
- package/.turbo/turbo-test.log +2653 -0
- package/CHANGELOG.md +20 -0
- package/README.md +126 -6
- package/dist/bin.js +59 -359
- package/dist/{chunk-IBYH7QZM.js → chunk-LJOKPCPD.js} +1397 -94
- package/dist/index.d.ts +74 -1
- package/dist/index.js +3 -1
- package/package.json +2 -2
- package/src/bin.ts +85 -6
- package/src/commands/init.ts +5 -1
- package/src/commands/integration-automation.ts +484 -0
- package/src/commands/integration-dispatch-mode.ts +92 -0
- package/src/commands/integration-doctor.ts +117 -0
- package/src/commands/integration-host-ide.ts +156 -0
- package/src/commands/integration-install.ts +262 -99
- package/src/commands/onboard.ts +24 -1
- package/src/index.ts +1 -0
- package/src/inject/extension.ts +144 -24
- package/src/integrations/capabilities.ts +131 -0
- package/src/onboarding/session.ts +13 -4
- package/src/utils/process.ts +3 -0
- package/tests/apply.test.ts +6 -1
- package/tests/extension-installer.test.ts +205 -0
- package/tests/install-wrapper.test.ts +45 -7
- package/tests/integration-automation.test.ts +435 -0
- package/tests/integration-dispatch-mode.test.ts +203 -0
- package/tests/integration-doctor.test.ts +165 -0
- package/tests/integration-host-ide.test.ts +172 -0
- package/tests/integration-install.test.ts +282 -20
- package/tests/onboard.test.ts +123 -1
- package/tests/runner-script.test.ts +72 -0
- package/tests/shared-capabilities.test.ts +45 -0
|
@@ -13,6 +13,7 @@ const logMock = {
|
|
|
13
13
|
error: vi.fn(),
|
|
14
14
|
blank: vi.fn(),
|
|
15
15
|
}
|
|
16
|
+
const runIntegrationAutomationMock = vi.fn()
|
|
16
17
|
|
|
17
18
|
vi.mock('../src/utils/fs.js', () => ({
|
|
18
19
|
writeFile: writeFileMock,
|
|
@@ -33,6 +34,10 @@ vi.mock('../src/utils/logger.js', () => ({
|
|
|
33
34
|
log: logMock,
|
|
34
35
|
}))
|
|
35
36
|
|
|
37
|
+
vi.mock('../src/commands/integration-automation.js', () => ({
|
|
38
|
+
runIntegrationAutomation: runIntegrationAutomationMock,
|
|
39
|
+
}))
|
|
40
|
+
|
|
36
41
|
describe('integration install', () => {
|
|
37
42
|
beforeEach(() => {
|
|
38
43
|
vi.resetModules()
|
|
@@ -47,6 +52,10 @@ describe('integration install', () => {
|
|
|
47
52
|
statusText: 'OK',
|
|
48
53
|
}),
|
|
49
54
|
)
|
|
55
|
+
runIntegrationAutomationMock.mockResolvedValue({
|
|
56
|
+
status: 'launched',
|
|
57
|
+
message: 'Onboarding launched in Cursor for Codex.',
|
|
58
|
+
})
|
|
50
59
|
})
|
|
51
60
|
|
|
52
61
|
it('installs the Claude Code skill into the user skill directory', async () => {
|
|
@@ -70,38 +79,209 @@ describe('integration install', () => {
|
|
|
70
79
|
'/Users/tester/.claude/skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh',
|
|
71
80
|
0o755,
|
|
72
81
|
)
|
|
82
|
+
expect(runIntegrationAutomationMock).not.toHaveBeenCalled()
|
|
73
83
|
})
|
|
74
84
|
|
|
75
|
-
it('installs the Codex skill into the
|
|
85
|
+
it('installs the Codex skill into the user skill directory when scoped to user', async () => {
|
|
76
86
|
const { installIntegration } = await import('../src/commands/integration-install.js')
|
|
77
87
|
|
|
78
|
-
await installIntegration('codex')
|
|
88
|
+
await installIntegration('codex', { scope: 'user' })
|
|
79
89
|
|
|
80
90
|
expect(writeFileMock).toHaveBeenCalledWith(
|
|
81
|
-
'/Users/tester/.
|
|
91
|
+
'/Users/tester/.agents/skills/inspecto-onboarding-codex/SKILL.md',
|
|
82
92
|
'# mock asset',
|
|
83
93
|
)
|
|
84
94
|
expect(writeFileMock).toHaveBeenCalledWith(
|
|
85
|
-
'/Users/tester/.
|
|
95
|
+
'/Users/tester/.agents/skills/inspecto-onboarding-codex/agents/openai.yaml',
|
|
86
96
|
'# mock asset',
|
|
87
97
|
)
|
|
88
98
|
expect(writeFileMock).toHaveBeenCalledWith(
|
|
89
|
-
'/Users/tester/.
|
|
99
|
+
'/Users/tester/.agents/skills/inspecto-onboarding-codex/scripts/run-inspecto.sh',
|
|
90
100
|
'# mock asset',
|
|
91
101
|
)
|
|
92
102
|
expect(chmodMock).toHaveBeenCalledWith(
|
|
93
|
-
'/Users/tester/.
|
|
103
|
+
'/Users/tester/.agents/skills/inspecto-onboarding-codex/scripts/run-inspecto.sh',
|
|
94
104
|
0o755,
|
|
95
105
|
)
|
|
106
|
+
expect(logMock.success).toHaveBeenCalledWith('Step 1/6: Installed Codex integration assets')
|
|
107
|
+
expect(logMock.hint).toHaveBeenCalledWith(
|
|
108
|
+
'/Users/tester/.agents/skills/inspecto-onboarding-codex/SKILL.md',
|
|
109
|
+
)
|
|
110
|
+
expect(runIntegrationAutomationMock).not.toHaveBeenCalled()
|
|
111
|
+
expect(logMock.ready).toHaveBeenCalledWith(
|
|
112
|
+
'Installed Codex integration assets. User-level installs only write integration assets and do not launch onboarding automatically.',
|
|
113
|
+
)
|
|
114
|
+
expect(logMock.hint).toHaveBeenCalledWith(
|
|
115
|
+
'Run the install command again from your target project root with --host-ide <vscode|cursor|trae|trae-cn> when you want to launch onboarding automatically.',
|
|
116
|
+
)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('clarifies that --host-ide does not launch onboarding for user-level installs', async () => {
|
|
120
|
+
const { installIntegration } = await import('../src/commands/integration-install.js')
|
|
121
|
+
|
|
122
|
+
await installIntegration('codex', { scope: 'user', ide: 'cursor' })
|
|
123
|
+
|
|
124
|
+
expect(runIntegrationAutomationMock).not.toHaveBeenCalled()
|
|
125
|
+
expect(logMock.ready).toHaveBeenCalledWith(
|
|
126
|
+
'Installed Codex integration assets. User-level installs only write integration assets and do not launch onboarding automatically.',
|
|
127
|
+
)
|
|
128
|
+
expect(logMock.hint).toHaveBeenCalledWith(
|
|
129
|
+
'The provided --host-ide value is saved only as a rerun hint for later; this command does not open Cursor for user-level installs.',
|
|
130
|
+
)
|
|
131
|
+
expect(logMock.hint).toHaveBeenCalledWith(
|
|
132
|
+
'Run the install command again from your target project root with --host-ide cursor when you want to launch onboarding automatically.',
|
|
133
|
+
)
|
|
96
134
|
})
|
|
97
135
|
|
|
98
|
-
it('installs the Cursor
|
|
136
|
+
it('installs the Cursor agents fallback gracefully acting as skills mode', async () => {
|
|
99
137
|
const { installIntegration } = await import('../src/commands/integration-install.js')
|
|
100
138
|
|
|
101
139
|
await installIntegration('cursor', { mode: 'agents' })
|
|
102
140
|
|
|
103
141
|
expect(writeFileMock).toHaveBeenCalledTimes(1)
|
|
104
|
-
expect(writeFileMock).toHaveBeenCalledWith(
|
|
142
|
+
expect(writeFileMock).toHaveBeenCalledWith(
|
|
143
|
+
'.cursor/skills/inspecto-onboarding/SKILL.md',
|
|
144
|
+
'# mock asset',
|
|
145
|
+
)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('surfaces a blocked automation outcome as a warning with next-step guidance', async () => {
|
|
149
|
+
runIntegrationAutomationMock.mockResolvedValue({
|
|
150
|
+
status: 'blocked',
|
|
151
|
+
message:
|
|
152
|
+
'Automatic setup stopped: Inspecto could not find a runnable Gemini target in Trae CN.',
|
|
153
|
+
nextStep:
|
|
154
|
+
'Install the Gemini plugin in Trae CN or install the `gemini` CLI, then rerun the command.',
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const { installIntegration } = await import('../src/commands/integration-install.js')
|
|
158
|
+
|
|
159
|
+
await installIntegration('gemini', { ide: 'trae-cn' })
|
|
160
|
+
|
|
161
|
+
expect(logMock.warn).toHaveBeenCalledWith(
|
|
162
|
+
'Automatic setup stopped: Inspecto could not find a runnable Gemini target in Trae CN.',
|
|
163
|
+
)
|
|
164
|
+
expect(logMock.hint).toHaveBeenCalledWith(
|
|
165
|
+
'Install the Gemini plugin in Trae CN or install the `gemini` CLI, then rerun the command.',
|
|
166
|
+
)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('surfaces a partial automation outcome as a warning with follow-up guidance', async () => {
|
|
170
|
+
runIntegrationAutomationMock.mockResolvedValue({
|
|
171
|
+
status: 'partial',
|
|
172
|
+
message:
|
|
173
|
+
'Onboarding opened in Trae CN for Gemini, but the Inspecto extension may still need manual setup.',
|
|
174
|
+
nextStep: 'Install the Inspecto extension in Trae CN if IDE-side features are missing.',
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const { installIntegration } = await import('../src/commands/integration-install.js')
|
|
178
|
+
|
|
179
|
+
await installIntegration('gemini', { ide: 'trae-cn' })
|
|
180
|
+
|
|
181
|
+
expect(logMock.warn).toHaveBeenCalledWith(
|
|
182
|
+
'Onboarding opened in Trae CN for Gemini, but the Inspecto extension may still need manual setup.',
|
|
183
|
+
)
|
|
184
|
+
expect(logMock.hint).toHaveBeenCalledWith(
|
|
185
|
+
'Install the Inspecto extension in Trae CN if IDE-side features are missing.',
|
|
186
|
+
)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('does not write files in preview mode and surfaces the preview summary', async () => {
|
|
190
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
191
|
+
ok: true,
|
|
192
|
+
text: async () => '# mock asset',
|
|
193
|
+
status: 200,
|
|
194
|
+
statusText: 'OK',
|
|
195
|
+
})
|
|
196
|
+
vi.stubGlobal('fetch', fetchMock)
|
|
197
|
+
runIntegrationAutomationMock.mockResolvedValue({
|
|
198
|
+
status: 'preview',
|
|
199
|
+
message:
|
|
200
|
+
'Preview complete. Inspecto did not write files or open IDE windows. Review the resolved setup below, then rerun without --preview to apply it.',
|
|
201
|
+
nextStep:
|
|
202
|
+
'Run the same command again without --preview to apply the integration and launch onboarding.',
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const { installIntegration } = await import('../src/commands/integration-install.js')
|
|
206
|
+
|
|
207
|
+
await installIntegration('codex', { preview: true, ide: 'cursor' })
|
|
208
|
+
|
|
209
|
+
expect(writeFileMock).not.toHaveBeenCalled()
|
|
210
|
+
expect(chmodMock).not.toHaveBeenCalled()
|
|
211
|
+
expect(fetchMock).not.toHaveBeenCalled()
|
|
212
|
+
expect(logMock.info).toHaveBeenCalledWith('Step 1/6: Previewing Codex integration assets')
|
|
213
|
+
expect(logMock.ready).toHaveBeenCalledWith(
|
|
214
|
+
'Preview complete. Inspecto did not write files or open IDE windows. Review the resolved setup below, then rerun without --preview to apply it.',
|
|
215
|
+
)
|
|
216
|
+
expect(logMock.hint).toHaveBeenCalledWith(
|
|
217
|
+
'Run the same command again without --preview to apply the integration and launch onboarding.',
|
|
218
|
+
)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('prints structured JSON output for preview mode when requested', async () => {
|
|
222
|
+
const fetchMock = vi.fn()
|
|
223
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
224
|
+
vi.stubGlobal('fetch', fetchMock)
|
|
225
|
+
runIntegrationAutomationMock.mockResolvedValue({
|
|
226
|
+
status: 'preview',
|
|
227
|
+
message:
|
|
228
|
+
'Preview complete. Inspecto did not write files or open IDE windows. Review the resolved setup below, then rerun without --preview to apply it.',
|
|
229
|
+
nextStep:
|
|
230
|
+
'Run the same command again without --preview to apply the integration and launch onboarding.',
|
|
231
|
+
details: {
|
|
232
|
+
hostIde: {
|
|
233
|
+
id: 'cursor',
|
|
234
|
+
label: 'Cursor',
|
|
235
|
+
source: 'explicit --host-ide',
|
|
236
|
+
confidence: 'high',
|
|
237
|
+
candidates: ['cursor'],
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
const { installIntegration } = await import('../src/commands/integration-install.js')
|
|
243
|
+
|
|
244
|
+
await installIntegration('codex', { preview: true, ide: 'cursor', json: true })
|
|
245
|
+
|
|
246
|
+
expect(writeFileMock).not.toHaveBeenCalled()
|
|
247
|
+
expect(fetchMock).not.toHaveBeenCalled()
|
|
248
|
+
expect(logMock.header).not.toHaveBeenCalled()
|
|
249
|
+
expect(logMock.info).not.toHaveBeenCalled()
|
|
250
|
+
expect(logMock.ready).not.toHaveBeenCalled()
|
|
251
|
+
const payload = JSON.parse(String(consoleLogSpy.mock.calls.at(-1)?.[0] ?? '{}'))
|
|
252
|
+
expect(payload).toMatchObject({
|
|
253
|
+
status: 'preview',
|
|
254
|
+
assistant: 'codex',
|
|
255
|
+
preview: true,
|
|
256
|
+
message:
|
|
257
|
+
'Preview complete. Inspecto did not write files or open IDE windows. Review the resolved setup below, then rerun without --preview to apply it.',
|
|
258
|
+
})
|
|
259
|
+
expect(payload.assets).toEqual(
|
|
260
|
+
expect.arrayContaining(['.agents/skills/inspecto-onboarding-codex/SKILL.md']),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
consoleLogSpy.mockRestore()
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('surfaces a blocked preview outcome as a warning with follow-up guidance', async () => {
|
|
267
|
+
runIntegrationAutomationMock.mockResolvedValue({
|
|
268
|
+
status: 'preview_blocked',
|
|
269
|
+
message:
|
|
270
|
+
'Preview blocked. Inspecto did not write files or open IDE windows because setup cannot continue until the blocking issue below is resolved.',
|
|
271
|
+
nextStep:
|
|
272
|
+
'Install the Trae CN CLI binary or rerun the command from a shell where it is available, then rerun the command.',
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
const { installIntegration } = await import('../src/commands/integration-install.js')
|
|
276
|
+
|
|
277
|
+
await installIntegration('gemini', { preview: true, ide: 'trae-cn' })
|
|
278
|
+
|
|
279
|
+
expect(logMock.warn).toHaveBeenCalledWith(
|
|
280
|
+
'Preview blocked. Inspecto did not write files or open IDE windows because setup cannot continue until the blocking issue below is resolved.',
|
|
281
|
+
)
|
|
282
|
+
expect(logMock.hint).toHaveBeenCalledWith(
|
|
283
|
+
'Install the Trae CN CLI binary or rerun the command from a shell where it is available, then rerun the command.',
|
|
284
|
+
)
|
|
105
285
|
})
|
|
106
286
|
|
|
107
287
|
it('refuses a multi-file Claude install before writing anything when one target already exists', async () => {
|
|
@@ -171,7 +351,7 @@ describe('integration install', () => {
|
|
|
171
351
|
expect.objectContaining({
|
|
172
352
|
assistant: 'codex',
|
|
173
353
|
type: 'native-skill',
|
|
174
|
-
installTarget: '
|
|
354
|
+
installTarget: '.agents/skills/',
|
|
175
355
|
cliSupported: true,
|
|
176
356
|
}),
|
|
177
357
|
expect.objectContaining({
|
|
@@ -181,8 +361,8 @@ describe('integration install', () => {
|
|
|
181
361
|
}),
|
|
182
362
|
expect.objectContaining({
|
|
183
363
|
assistant: 'copilot',
|
|
184
|
-
type: '
|
|
185
|
-
installTarget: '.github/
|
|
364
|
+
type: 'native-skill',
|
|
365
|
+
installTarget: '.github/skills/inspecto-onboarding/',
|
|
186
366
|
}),
|
|
187
367
|
]),
|
|
188
368
|
)
|
|
@@ -198,21 +378,23 @@ describe('integration install', () => {
|
|
|
198
378
|
'/Users/tester/.claude/skills/inspecto-onboarding-claude-code/agents/openai.yaml',
|
|
199
379
|
'/Users/tester/.claude/skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh',
|
|
200
380
|
],
|
|
201
|
-
preferredInstall:
|
|
381
|
+
preferredInstall:
|
|
382
|
+
'npx @inspecto-dev/cli integrations install claude-code --scope project --host-ide <vscode|cursor|trae|trae-cn>',
|
|
202
383
|
})
|
|
203
384
|
})
|
|
204
385
|
|
|
205
386
|
it('describes codex using the same CLI-managed install target model', async () => {
|
|
206
387
|
const { describeIntegration } = await import('../src/commands/integration-install.js')
|
|
207
388
|
|
|
208
|
-
expect(describeIntegration('codex')).toMatchObject({
|
|
389
|
+
expect(describeIntegration('codex', { scope: 'user' })).toMatchObject({
|
|
209
390
|
assistant: 'codex',
|
|
210
391
|
targets: [
|
|
211
|
-
'/Users/tester/.
|
|
212
|
-
'/Users/tester/.
|
|
213
|
-
'/Users/tester/.
|
|
392
|
+
'/Users/tester/.agents/skills/inspecto-onboarding-codex/SKILL.md',
|
|
393
|
+
'/Users/tester/.agents/skills/inspecto-onboarding-codex/agents/openai.yaml',
|
|
394
|
+
'/Users/tester/.agents/skills/inspecto-onboarding-codex/scripts/run-inspecto.sh',
|
|
214
395
|
],
|
|
215
|
-
preferredInstall:
|
|
396
|
+
preferredInstall:
|
|
397
|
+
'npx @inspecto-dev/cli integrations install codex --host-ide <vscode|cursor|trae|trae-cn>',
|
|
216
398
|
})
|
|
217
399
|
})
|
|
218
400
|
|
|
@@ -225,13 +407,14 @@ describe('integration install', () => {
|
|
|
225
407
|
'/Users/tester/.claude/skills/inspecto-onboarding-claude-code/SKILL.md',
|
|
226
408
|
)
|
|
227
409
|
expect(logMock.hint).toHaveBeenCalledWith(
|
|
228
|
-
'Preferred install: npx @inspecto-dev/cli integrations install claude-code --scope project',
|
|
410
|
+
'Preferred install: npx @inspecto-dev/cli integrations install claude-code --scope project --host-ide <vscode|cursor|trae|trae-cn>',
|
|
229
411
|
)
|
|
230
412
|
expect(logMock.hint).not.toHaveBeenCalledWith('Restart Claude Code to load the new skill.')
|
|
231
413
|
})
|
|
232
414
|
|
|
233
415
|
it('rejects unsupported extra args and options for list/path', async () => {
|
|
234
416
|
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as never)
|
|
417
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
235
418
|
|
|
236
419
|
vi.doMock('../src/commands/init.js', () => ({ init: vi.fn() }))
|
|
237
420
|
vi.doMock('../src/commands/doctor.js', () => ({ doctor: vi.fn() }))
|
|
@@ -244,18 +427,44 @@ describe('integration install', () => {
|
|
|
244
427
|
|
|
245
428
|
await runCli(['node', 'inspecto', 'integrations', 'list', 'extra'])
|
|
246
429
|
await runCli(['node', 'inspecto', 'integrations', 'path', 'claude-code', '--force'])
|
|
430
|
+
await runCli(['node', 'inspecto', 'integrations', 'list', '--host-ide', 'cursor'])
|
|
431
|
+
await runCli([
|
|
432
|
+
'node',
|
|
433
|
+
'inspecto',
|
|
434
|
+
'integrations',
|
|
435
|
+
'path',
|
|
436
|
+
'claude-code',
|
|
437
|
+
'--inspecto-vsix',
|
|
438
|
+
'/tmp/inspecto.vsix',
|
|
439
|
+
])
|
|
440
|
+
await runCli(['node', 'inspecto', 'integrations', 'path', 'claude-code', '--preview'])
|
|
441
|
+
await runCli(['node', 'inspecto', 'integrations', 'path', 'claude-code', '--json'])
|
|
247
442
|
|
|
248
443
|
expect(logMock.error).toHaveBeenCalledWith(
|
|
249
|
-
'The `list` subcommand does not accept assistant names, --scope, --mode, or --force.',
|
|
444
|
+
'The `list` subcommand does not accept assistant names, --scope, --mode, --host-ide, --inspecto-vsix, --compact, --json, --preview, or --force.',
|
|
250
445
|
)
|
|
251
446
|
expect(logMock.error).toHaveBeenCalledWith('The `path` subcommand does not support `--force`.')
|
|
447
|
+
expect(logMock.error).toHaveBeenCalledWith(
|
|
448
|
+
'The `path` subcommand does not support `--inspecto-vsix`.',
|
|
449
|
+
)
|
|
450
|
+
expect(logMock.error).toHaveBeenCalledWith(
|
|
451
|
+
'The `path` subcommand does not support `--preview`.',
|
|
452
|
+
)
|
|
453
|
+
expect(JSON.parse(String(consoleErrorSpy.mock.calls.at(-1)?.[0] ?? '{}'))).toMatchObject({
|
|
454
|
+
status: 'error',
|
|
455
|
+
error: {
|
|
456
|
+
message: 'The `path` subcommand does not support `--json`.',
|
|
457
|
+
},
|
|
458
|
+
})
|
|
252
459
|
expect(exitSpy).toHaveBeenCalledWith(1)
|
|
253
460
|
|
|
461
|
+
consoleErrorSpy.mockRestore()
|
|
254
462
|
exitSpy.mockRestore()
|
|
255
463
|
})
|
|
256
464
|
|
|
257
465
|
it('wires the nested CLI command to the integration installer', async () => {
|
|
258
466
|
const installCommand = vi.fn().mockResolvedValue(undefined)
|
|
467
|
+
const doctorCommand = vi.fn().mockResolvedValue({ status: 'ok' })
|
|
259
468
|
const listCommand = vi.fn().mockResolvedValue(undefined)
|
|
260
469
|
const pathCommand = vi.fn().mockResolvedValue(undefined)
|
|
261
470
|
|
|
@@ -270,6 +479,9 @@ describe('integration install', () => {
|
|
|
270
479
|
printIntegrationList: listCommand,
|
|
271
480
|
printIntegrationPath: pathCommand,
|
|
272
481
|
}))
|
|
482
|
+
vi.doMock('../src/commands/integration-doctor.js', () => ({
|
|
483
|
+
integrationDoctor: doctorCommand,
|
|
484
|
+
}))
|
|
273
485
|
|
|
274
486
|
const { runCli } = await import('../src/bin.js')
|
|
275
487
|
|
|
@@ -279,6 +491,12 @@ describe('integration install', () => {
|
|
|
279
491
|
'integrations',
|
|
280
492
|
'install',
|
|
281
493
|
'copilot',
|
|
494
|
+
'--host-ide',
|
|
495
|
+
'cursor',
|
|
496
|
+
'--inspecto-vsix',
|
|
497
|
+
'/tmp/inspecto.vsix',
|
|
498
|
+
'--preview',
|
|
499
|
+
'--json',
|
|
282
500
|
'--mode',
|
|
283
501
|
'agents',
|
|
284
502
|
'--force',
|
|
@@ -286,9 +504,53 @@ describe('integration install', () => {
|
|
|
286
504
|
|
|
287
505
|
await runCli(['node', 'inspecto', 'integrations', 'list'])
|
|
288
506
|
await runCli(['node', 'inspecto', 'integrations', 'path', 'claude-code', '--scope', 'user'])
|
|
507
|
+
await runCli([
|
|
508
|
+
'node',
|
|
509
|
+
'inspecto',
|
|
510
|
+
'integrations',
|
|
511
|
+
'doctor',
|
|
512
|
+
'codex',
|
|
513
|
+
'--host-ide',
|
|
514
|
+
'cursor',
|
|
515
|
+
'--compact',
|
|
516
|
+
'--json',
|
|
517
|
+
])
|
|
289
518
|
|
|
290
|
-
expect(installCommand).toHaveBeenCalledWith('copilot', {
|
|
519
|
+
expect(installCommand).toHaveBeenCalledWith('copilot', {
|
|
520
|
+
ide: 'cursor',
|
|
521
|
+
inspectoVsix: '/tmp/inspecto.vsix',
|
|
522
|
+
preview: true,
|
|
523
|
+
json: true,
|
|
524
|
+
mode: 'agents',
|
|
525
|
+
force: true,
|
|
526
|
+
})
|
|
291
527
|
expect(listCommand).toHaveBeenCalledWith()
|
|
292
528
|
expect(pathCommand).toHaveBeenCalledWith('claude-code', { scope: 'user' })
|
|
529
|
+
expect(doctorCommand).toHaveBeenCalledWith('codex', {
|
|
530
|
+
ide: 'cursor',
|
|
531
|
+
compact: true,
|
|
532
|
+
failOnBlocked: true,
|
|
533
|
+
json: true,
|
|
534
|
+
})
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
it('sets a non-zero exit code when integrations doctor reports a blocked result', async () => {
|
|
538
|
+
const doctorCommand = vi.fn().mockResolvedValue({ status: 'blocked' })
|
|
539
|
+
|
|
540
|
+
vi.doMock('../src/commands/init.js', () => ({ init: vi.fn() }))
|
|
541
|
+
vi.doMock('../src/commands/doctor.js', () => ({ doctor: vi.fn() }))
|
|
542
|
+
vi.doMock('../src/commands/teardown.js', () => ({ teardown: vi.fn() }))
|
|
543
|
+
vi.doMock('../src/commands/detect.js', () => ({ detect: vi.fn() }))
|
|
544
|
+
vi.doMock('../src/commands/plan.js', () => ({ plan: vi.fn() }))
|
|
545
|
+
vi.doMock('../src/commands/apply.js', () => ({ apply: vi.fn() }))
|
|
546
|
+
vi.doMock('../src/commands/integration-doctor.js', () => ({
|
|
547
|
+
integrationDoctor: doctorCommand,
|
|
548
|
+
}))
|
|
549
|
+
|
|
550
|
+
const { runCli } = await import('../src/bin.js')
|
|
551
|
+
|
|
552
|
+
await runCli(['node', 'inspecto', 'integrations', 'doctor', 'codex', '--json'])
|
|
553
|
+
|
|
554
|
+
expect(doctorCommand).toHaveBeenCalledWith('codex', { json: true, failOnBlocked: true })
|
|
293
555
|
})
|
|
294
556
|
})
|
package/tests/onboard.test.ts
CHANGED
|
@@ -347,6 +347,128 @@ describe('onboard command', () => {
|
|
|
347
347
|
const result = await onboard({ json: false })
|
|
348
348
|
|
|
349
349
|
expect(result.status).toBe('success')
|
|
350
|
-
expect(
|
|
350
|
+
expect(
|
|
351
|
+
consoleSpy.mock.calls.some(call =>
|
|
352
|
+
String(call[0]).includes('Start the local dev server with `pnpm dev`'),
|
|
353
|
+
),
|
|
354
|
+
).toBe(true)
|
|
355
|
+
expect(
|
|
356
|
+
consoleSpy.mock.calls.some(call =>
|
|
357
|
+
String(call[0]).includes('code --install-extension inspecto.inspecto'),
|
|
358
|
+
),
|
|
359
|
+
).toBe(false)
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
it('prints manual extension guidance only when the IDE extension still needs manual completion', async () => {
|
|
363
|
+
const session: ResolvedOnboardingSession = {
|
|
364
|
+
status: 'success',
|
|
365
|
+
target: {
|
|
366
|
+
status: 'resolved',
|
|
367
|
+
selected: {
|
|
368
|
+
packagePath: '',
|
|
369
|
+
configPath: 'vite.config.ts',
|
|
370
|
+
buildTool: 'vite',
|
|
371
|
+
frameworks: ['react'],
|
|
372
|
+
automaticInjection: true,
|
|
373
|
+
},
|
|
374
|
+
candidates: [
|
|
375
|
+
{
|
|
376
|
+
packagePath: '',
|
|
377
|
+
configPath: 'vite.config.ts',
|
|
378
|
+
buildTool: 'vite',
|
|
379
|
+
frameworks: ['react'],
|
|
380
|
+
automaticInjection: true,
|
|
381
|
+
},
|
|
382
|
+
],
|
|
383
|
+
reason: 'Only one supported target was detected.',
|
|
384
|
+
},
|
|
385
|
+
summary: {
|
|
386
|
+
headline: 'Inspecto is ready to onboard /repo.',
|
|
387
|
+
changes: ['Install dependencies.'],
|
|
388
|
+
risks: [],
|
|
389
|
+
manualFollowUp: [],
|
|
390
|
+
},
|
|
391
|
+
confirmation: { required: false },
|
|
392
|
+
verification: {
|
|
393
|
+
available: true,
|
|
394
|
+
devCommand: 'pnpm dev',
|
|
395
|
+
message: 'Start the local dev server with `pnpm dev` to verify Inspecto in the browser.',
|
|
396
|
+
},
|
|
397
|
+
context: {
|
|
398
|
+
root: '/repo',
|
|
399
|
+
packageManager: 'pnpm',
|
|
400
|
+
buildTools: { supported: [], unsupported: [] },
|
|
401
|
+
frameworks: { supported: ['react'], unsupported: [] },
|
|
402
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
403
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
404
|
+
},
|
|
405
|
+
plan: {
|
|
406
|
+
status: 'ok',
|
|
407
|
+
warnings: [],
|
|
408
|
+
blockers: [],
|
|
409
|
+
strategy: 'supported',
|
|
410
|
+
actions: [],
|
|
411
|
+
defaults: {
|
|
412
|
+
provider: 'codex',
|
|
413
|
+
ide: 'vscode',
|
|
414
|
+
shared: false,
|
|
415
|
+
extension: true,
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
projectRoot: '/repo',
|
|
419
|
+
selectedIDE: { ide: 'vscode', supported: true },
|
|
420
|
+
providerDefault: 'codex.cli',
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const applied: OnboardCommandResult = {
|
|
424
|
+
status: 'partial_success',
|
|
425
|
+
target: session.target,
|
|
426
|
+
summary: session.summary,
|
|
427
|
+
confirmation: session.confirmation,
|
|
428
|
+
ideExtension: {
|
|
429
|
+
required: true,
|
|
430
|
+
installed: false,
|
|
431
|
+
manualRequired: true,
|
|
432
|
+
installCommand: 'code --install-extension inspecto.inspecto',
|
|
433
|
+
marketplaceUrl: 'https://marketplace.visualstudio.com/items?itemName=inspecto.inspecto',
|
|
434
|
+
openVsxUrl: 'https://open-vsx.org/extension/inspecto/inspecto',
|
|
435
|
+
},
|
|
436
|
+
verification: session.verification,
|
|
437
|
+
result: {
|
|
438
|
+
changedFiles: ['vite.config.ts'],
|
|
439
|
+
installedDependencies: ['@inspecto-dev/plugin', '@inspecto-dev/core'],
|
|
440
|
+
selectedProviderDefault: 'codex.cli',
|
|
441
|
+
selectedIDE: 'vscode',
|
|
442
|
+
mutations: [],
|
|
443
|
+
},
|
|
444
|
+
diagnostics: {
|
|
445
|
+
warnings: ['IDE extension installation still needs manual completion.'],
|
|
446
|
+
errors: [],
|
|
447
|
+
nextSteps: [],
|
|
448
|
+
},
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
vi.mocked(resolveOnboardingSession).mockResolvedValue(session)
|
|
452
|
+
vi.mocked(applyResolvedOnboardingSession).mockResolvedValue(applied)
|
|
453
|
+
|
|
454
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
455
|
+
const result = await onboard({ json: false })
|
|
456
|
+
|
|
457
|
+
expect(result.status).toBe('partial_success')
|
|
458
|
+
expect(
|
|
459
|
+
consoleSpy.mock.calls.some(call =>
|
|
460
|
+
String(call[0]).includes('Complete the IDE extension install before verification'),
|
|
461
|
+
),
|
|
462
|
+
).toBe(true)
|
|
463
|
+
expect(
|
|
464
|
+
consoleSpy.mock.calls.some(call =>
|
|
465
|
+
String(call[0]).includes('code --install-extension inspecto.inspecto'),
|
|
466
|
+
),
|
|
467
|
+
).toBe(true)
|
|
468
|
+
expect(
|
|
469
|
+
consoleSpy.mock.calls.some(call =>
|
|
470
|
+
String(call[0]).includes('Start the local dev server with `pnpm dev`'),
|
|
471
|
+
),
|
|
472
|
+
).toBe(false)
|
|
351
473
|
})
|
|
352
474
|
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { spawnSync } from 'node:child_process'
|
|
5
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
6
|
+
|
|
7
|
+
const TEMP_DIRS: string[] = []
|
|
8
|
+
|
|
9
|
+
async function makeTempDir(): Promise<string> {
|
|
10
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'inspecto-runner-test-'))
|
|
11
|
+
TEMP_DIRS.push(dir)
|
|
12
|
+
return dir
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function writeExecutable(filePath: string, content: string): Promise<void> {
|
|
16
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
|
17
|
+
await fs.writeFile(filePath, content, 'utf8')
|
|
18
|
+
await fs.chmod(filePath, 0o755)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function runRunner(scriptPath: string, cwd: string, pathEnv: string): string {
|
|
22
|
+
const result = spawnSync(scriptPath, ['onboard', '--json'], {
|
|
23
|
+
cwd,
|
|
24
|
+
env: {
|
|
25
|
+
...process.env,
|
|
26
|
+
PATH: pathEnv,
|
|
27
|
+
},
|
|
28
|
+
encoding: 'utf8',
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
expect(result.status).toBe(0)
|
|
32
|
+
return (result.stdout + result.stderr).trim()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('skill runner scripts', () => {
|
|
36
|
+
afterEach(async () => {
|
|
37
|
+
await Promise.all(TEMP_DIRS.splice(0).map(dir => fs.rm(dir, { recursive: true, force: true })))
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it.each([
|
|
41
|
+
'skills/inspecto-onboarding-codex/scripts/run-inspecto.sh',
|
|
42
|
+
'skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh',
|
|
43
|
+
'skills/inspecto-onboarding-core/scripts/run-inspecto.sh',
|
|
44
|
+
])(
|
|
45
|
+
'prefers an installed inspecto executable before package-manager download paths: %s',
|
|
46
|
+
async scriptRelativePath => {
|
|
47
|
+
const workspaceRoot = path.resolve(__dirname, '../../..')
|
|
48
|
+
const scriptPath = path.join(workspaceRoot, scriptRelativePath)
|
|
49
|
+
const tempDir = await makeTempDir()
|
|
50
|
+
const fakeBin = path.join(tempDir, 'bin')
|
|
51
|
+
|
|
52
|
+
await writeExecutable(
|
|
53
|
+
path.join(fakeBin, 'inspecto'),
|
|
54
|
+
`#!/usr/bin/env bash
|
|
55
|
+
echo "installed-inspecto:$*"
|
|
56
|
+
`,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
await writeExecutable(
|
|
60
|
+
path.join(fakeBin, 'pnpm'),
|
|
61
|
+
`#!/usr/bin/env bash
|
|
62
|
+
echo "pnpm-should-not-run:$*" >&2
|
|
63
|
+
exit 99
|
|
64
|
+
`,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
const output = runRunner(scriptPath, tempDir, `${fakeBin}:${process.env.PATH ?? ''}`)
|
|
68
|
+
expect(output).toContain('installed-inspecto:onboard --json')
|
|
69
|
+
expect(output).not.toContain('pnpm-should-not-run')
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
getDualModeProviderCapability,
|
|
4
|
+
getHostIdeLabel,
|
|
5
|
+
isSupportedHostIde,
|
|
6
|
+
} from '@inspecto-dev/types'
|
|
7
|
+
|
|
8
|
+
describe('shared capabilities', () => {
|
|
9
|
+
it('exposes shared host IDE labels', () => {
|
|
10
|
+
expect(getHostIdeLabel('vscode')).toBe('VS Code')
|
|
11
|
+
expect(getHostIdeLabel('cursor')).toBe('Cursor')
|
|
12
|
+
expect(getHostIdeLabel('trae')).toBe('Trae')
|
|
13
|
+
expect(getHostIdeLabel('trae-cn')).toBe('Trae CN')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('exposes supported host IDE guards', () => {
|
|
17
|
+
expect(isSupportedHostIde('vscode')).toBe(true)
|
|
18
|
+
expect(isSupportedHostIde('cursor')).toBe(true)
|
|
19
|
+
expect(isSupportedHostIde('trae')).toBe(true)
|
|
20
|
+
expect(isSupportedHostIde('trae-cn')).toBe(true)
|
|
21
|
+
expect(isSupportedHostIde('unknown')).toBe(false)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('exposes dual-mode provider metadata', () => {
|
|
25
|
+
expect(getDualModeProviderCapability('codex')).toEqual({
|
|
26
|
+
label: 'Codex',
|
|
27
|
+
extensionId: 'openai.chatgpt',
|
|
28
|
+
cliBin: 'codex',
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
expect(getDualModeProviderCapability('claude-code')).toEqual({
|
|
32
|
+
label: 'Claude Code',
|
|
33
|
+
extensionId: 'anthropic.claude-code',
|
|
34
|
+
cliBin: 'claude',
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
expect(getDualModeProviderCapability('gemini')).toEqual({
|
|
38
|
+
label: 'Gemini',
|
|
39
|
+
extensionId: 'google.geminicodeassist',
|
|
40
|
+
cliBin: 'gemini',
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
expect(getDualModeProviderCapability('copilot')).toBeUndefined()
|
|
44
|
+
})
|
|
45
|
+
})
|