@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
|
@@ -21,9 +21,9 @@ describe('assistant integration bootstrap wrapper', () => {
|
|
|
21
21
|
|
|
22
22
|
const fakeBin = path.join(tempRoot, 'bin')
|
|
23
23
|
await fs.mkdir(fakeBin, { recursive: true })
|
|
24
|
-
await fs.mkdir(path.join(tempRoot, '.github'), { recursive: true })
|
|
24
|
+
await fs.mkdir(path.join(tempRoot, '.github/skills/inspecto-onboarding'), { recursive: true })
|
|
25
25
|
await fs.writeFile(
|
|
26
|
-
path.join(tempRoot, '.github/
|
|
26
|
+
path.join(tempRoot, '.github/skills/inspecto-onboarding/SKILL.md'),
|
|
27
27
|
'old content\n',
|
|
28
28
|
'utf8',
|
|
29
29
|
)
|
|
@@ -54,10 +54,7 @@ describe('assistant integration bootstrap wrapper', () => {
|
|
|
54
54
|
)
|
|
55
55
|
await fs.chmod(path.join(fakeBin, 'curl'), 0o755)
|
|
56
56
|
|
|
57
|
-
const scriptPath = path.resolve(
|
|
58
|
-
__dirname,
|
|
59
|
-
'../../../assistant-integrations/scripts/install.sh',
|
|
60
|
-
)
|
|
57
|
+
const scriptPath = path.resolve(__dirname, '../../../scripts/install.sh')
|
|
61
58
|
|
|
62
59
|
await execFileAsync('bash', [scriptPath, 'copilot', '--force'], {
|
|
63
60
|
cwd: tempRoot,
|
|
@@ -68,9 +65,50 @@ describe('assistant integration bootstrap wrapper', () => {
|
|
|
68
65
|
})
|
|
69
66
|
|
|
70
67
|
const installed = await fs.readFile(
|
|
71
|
-
path.join(tempRoot, '.github/
|
|
68
|
+
path.join(tempRoot, '.github/skills/inspecto-onboarding/SKILL.md'),
|
|
72
69
|
'utf8',
|
|
73
70
|
)
|
|
74
71
|
expect(installed).toBe('downloaded from wrapper\n')
|
|
75
72
|
})
|
|
73
|
+
|
|
74
|
+
it('passes --host-ide through to the CLI install command', async () => {
|
|
75
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'inspecto-wrapper-'))
|
|
76
|
+
tempDirs.push(tempRoot)
|
|
77
|
+
|
|
78
|
+
const fakeBin = path.join(tempRoot, 'bin')
|
|
79
|
+
await fs.mkdir(fakeBin, { recursive: true })
|
|
80
|
+
|
|
81
|
+
const argsFile = path.join(tempRoot, 'npx-args.txt')
|
|
82
|
+
await fs.writeFile(
|
|
83
|
+
path.join(fakeBin, 'npx'),
|
|
84
|
+
['#!/usr/bin/env bash', 'set -euo pipefail', 'printf "%s\n" "$@" > "$NPX_ARGS_FILE"'].join(
|
|
85
|
+
'\n',
|
|
86
|
+
),
|
|
87
|
+
'utf8',
|
|
88
|
+
)
|
|
89
|
+
await fs.chmod(path.join(fakeBin, 'npx'), 0o755)
|
|
90
|
+
|
|
91
|
+
const scriptPath = path.resolve(__dirname, '../../../scripts/install.sh')
|
|
92
|
+
|
|
93
|
+
await execFileAsync('bash', [scriptPath, 'codex', 'project', '--host-ide', 'cursor'], {
|
|
94
|
+
cwd: tempRoot,
|
|
95
|
+
env: {
|
|
96
|
+
...process.env,
|
|
97
|
+
PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
|
|
98
|
+
NPX_ARGS_FILE: argsFile,
|
|
99
|
+
},
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const forwardedArgs = await fs.readFile(argsFile, 'utf8')
|
|
103
|
+
expect(forwardedArgs.trim().split('\n')).toEqual([
|
|
104
|
+
'@inspecto-dev/cli',
|
|
105
|
+
'integrations',
|
|
106
|
+
'install',
|
|
107
|
+
'codex',
|
|
108
|
+
'--host-ide',
|
|
109
|
+
'cursor',
|
|
110
|
+
'--scope',
|
|
111
|
+
'project',
|
|
112
|
+
])
|
|
113
|
+
})
|
|
76
114
|
})
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
const installExtensionMock = vi.fn()
|
|
4
|
+
const openIdeWorkspaceMock = vi.fn()
|
|
5
|
+
const openUriMock = vi.fn()
|
|
6
|
+
const resolveHostIdeBinaryMock = vi.fn()
|
|
7
|
+
const resolveHostIdeMock = vi.fn()
|
|
8
|
+
const setTimeoutMock = vi.fn<(callback: () => void, delay: number) => number>()
|
|
9
|
+
const resolveDispatchModeMock = vi.fn()
|
|
10
|
+
const existsMock = vi.fn()
|
|
11
|
+
const logMock = {
|
|
12
|
+
header: vi.fn(),
|
|
13
|
+
info: vi.fn(),
|
|
14
|
+
success: vi.fn(),
|
|
15
|
+
warn: vi.fn(),
|
|
16
|
+
hint: vi.fn(),
|
|
17
|
+
ready: vi.fn(),
|
|
18
|
+
error: vi.fn(),
|
|
19
|
+
blank: vi.fn(),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
vi.mock('../src/inject/extension.js', () => ({
|
|
23
|
+
installExtension: installExtensionMock,
|
|
24
|
+
openIdeWorkspace: openIdeWorkspaceMock,
|
|
25
|
+
openUri: openUriMock,
|
|
26
|
+
resolveHostIdeBinary: resolveHostIdeBinaryMock,
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
vi.mock('../src/commands/integration-host-ide.js', () => ({
|
|
30
|
+
resolveIntegrationHostIde: resolveHostIdeMock,
|
|
31
|
+
}))
|
|
32
|
+
|
|
33
|
+
vi.mock('../src/commands/integration-dispatch-mode.js', () => ({
|
|
34
|
+
resolveIntegrationDispatchMode: resolveDispatchModeMock,
|
|
35
|
+
}))
|
|
36
|
+
|
|
37
|
+
vi.mock('../src/utils/logger.js', () => ({
|
|
38
|
+
log: logMock,
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
vi.mock('../src/utils/fs.js', () => ({
|
|
42
|
+
exists: existsMock,
|
|
43
|
+
}))
|
|
44
|
+
|
|
45
|
+
describe('runIntegrationAutomation', () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
vi.resetAllMocks()
|
|
48
|
+
installExtensionMock.mockResolvedValue(null)
|
|
49
|
+
openIdeWorkspaceMock.mockResolvedValue(true)
|
|
50
|
+
openUriMock.mockResolvedValue(true)
|
|
51
|
+
resolveHostIdeBinaryMock.mockResolvedValue('cursor')
|
|
52
|
+
existsMock.mockResolvedValue(true)
|
|
53
|
+
resolveDispatchModeMock.mockResolvedValue({
|
|
54
|
+
mode: 'extension',
|
|
55
|
+
ready: true,
|
|
56
|
+
reason: 'default',
|
|
57
|
+
})
|
|
58
|
+
setTimeoutMock.mockImplementation((callback, _delay) => {
|
|
59
|
+
callback()
|
|
60
|
+
return 0
|
|
61
|
+
})
|
|
62
|
+
vi.stubGlobal('setTimeout', setTimeoutMock as typeof setTimeout)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('installs the resolved IDE extension and launches onboarding when confidence is high', async () => {
|
|
66
|
+
resolveHostIdeMock.mockResolvedValue({
|
|
67
|
+
ide: 'cursor',
|
|
68
|
+
confidence: 'high',
|
|
69
|
+
source: 'explicit',
|
|
70
|
+
candidates: ['cursor'],
|
|
71
|
+
})
|
|
72
|
+
installExtensionMock.mockResolvedValue({
|
|
73
|
+
type: 'extension_installed',
|
|
74
|
+
id: 'inspecto.inspecto',
|
|
75
|
+
description: 'installed_via_cli',
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const { runIntegrationAutomation } = await import('../src/commands/integration-automation.js')
|
|
79
|
+
|
|
80
|
+
await runIntegrationAutomation('codex', { ide: 'cursor' }, '/repo')
|
|
81
|
+
|
|
82
|
+
expect(resolveHostIdeMock).toHaveBeenCalledWith({
|
|
83
|
+
explicitIde: 'cursor',
|
|
84
|
+
cwd: '/repo',
|
|
85
|
+
})
|
|
86
|
+
expect(installExtensionMock).toHaveBeenCalledWith(false, 'cursor', true, undefined)
|
|
87
|
+
expect(openIdeWorkspaceMock).toHaveBeenCalledWith('cursor', '/repo')
|
|
88
|
+
expect(logMock.success).toHaveBeenCalledWith('Step 2/6: Resolved host IDE')
|
|
89
|
+
expect(logMock.success).toHaveBeenCalledWith(
|
|
90
|
+
'Step 3/6: Installed the Inspecto extension in Cursor',
|
|
91
|
+
)
|
|
92
|
+
expect(logMock.success).toHaveBeenCalledWith('Step 4/6: Resolved Codex runtime')
|
|
93
|
+
expect(logMock.success).toHaveBeenCalledWith('Step 5/6: Opened workspace in Cursor')
|
|
94
|
+
expect(logMock.success).toHaveBeenCalledWith('Step 6/6: Launched onboarding in Cursor')
|
|
95
|
+
expect(openUriMock).toHaveBeenCalledWith(
|
|
96
|
+
'cursor://inspecto.inspecto/send?target=codex&prompt=Set+up+Inspecto+in+this+project&autoSend=true&workspace=%2Frepo&overrides=%7B%22type%22%3A%22extension%22%7D',
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('passes a local Inspecto VSIX path through to extension installation when provided', async () => {
|
|
101
|
+
resolveHostIdeMock.mockResolvedValue({
|
|
102
|
+
ide: 'trae-cn',
|
|
103
|
+
confidence: 'high',
|
|
104
|
+
source: 'explicit',
|
|
105
|
+
candidates: ['trae-cn'],
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const { runIntegrationAutomation } = await import('../src/commands/integration-automation.js')
|
|
109
|
+
|
|
110
|
+
await runIntegrationAutomation(
|
|
111
|
+
'gemini',
|
|
112
|
+
{ ide: 'trae-cn', inspectoVsix: '/tmp/inspecto.vsix' },
|
|
113
|
+
'/repo',
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
expect(installExtensionMock).toHaveBeenCalledWith(false, 'trae-cn', true, '/tmp/inspecto.vsix')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('waits briefly after a fresh extension install before launching the onboarding URI', async () => {
|
|
120
|
+
resolveHostIdeMock.mockResolvedValue({
|
|
121
|
+
ide: 'cursor',
|
|
122
|
+
confidence: 'high',
|
|
123
|
+
source: 'explicit',
|
|
124
|
+
candidates: ['cursor'],
|
|
125
|
+
})
|
|
126
|
+
installExtensionMock.mockResolvedValue({
|
|
127
|
+
type: 'extension_installed',
|
|
128
|
+
id: 'inspecto.inspecto',
|
|
129
|
+
description: 'installed_via_cli',
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
const { runIntegrationAutomation } = await import('../src/commands/integration-automation.js')
|
|
133
|
+
|
|
134
|
+
await runIntegrationAutomation('codex', { ide: 'cursor' }, '/repo')
|
|
135
|
+
|
|
136
|
+
expect(setTimeoutMock).toHaveBeenCalledWith(expect.any(Function), 1500)
|
|
137
|
+
expect(setTimeoutMock).toHaveBeenCalledWith(expect.any(Function), 1000)
|
|
138
|
+
expect(logMock.hint).toHaveBeenCalledWith(
|
|
139
|
+
'Waiting briefly for the IDE extension to finish activating...',
|
|
140
|
+
)
|
|
141
|
+
expect(openUriMock).toHaveBeenCalledTimes(1)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('uses codex CLI mode in Cursor when the Codex extension is unavailable but the CLI exists', async () => {
|
|
145
|
+
resolveHostIdeMock.mockResolvedValue({
|
|
146
|
+
ide: 'cursor',
|
|
147
|
+
confidence: 'high',
|
|
148
|
+
source: 'explicit',
|
|
149
|
+
candidates: ['cursor'],
|
|
150
|
+
})
|
|
151
|
+
resolveDispatchModeMock.mockResolvedValue({
|
|
152
|
+
mode: 'cli',
|
|
153
|
+
ready: true,
|
|
154
|
+
reason: 'codex_cli',
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const { runIntegrationAutomation } = await import('../src/commands/integration-automation.js')
|
|
158
|
+
|
|
159
|
+
await runIntegrationAutomation('codex', { ide: 'cursor' }, '/repo')
|
|
160
|
+
|
|
161
|
+
expect(openUriMock).toHaveBeenCalledWith(
|
|
162
|
+
'cursor://inspecto.inspecto/send?target=codex&prompt=Set+up+Inspecto+in+this+project&autoSend=true&workspace=%2Frepo&overrides=%7B%22type%22%3A%22cli%22%7D',
|
|
163
|
+
)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('stops and explains what to install when codex cannot run in Cursor', async () => {
|
|
167
|
+
resolveHostIdeMock.mockResolvedValue({
|
|
168
|
+
ide: 'cursor',
|
|
169
|
+
confidence: 'high',
|
|
170
|
+
source: 'explicit',
|
|
171
|
+
candidates: ['cursor'],
|
|
172
|
+
})
|
|
173
|
+
resolveDispatchModeMock.mockResolvedValue({
|
|
174
|
+
mode: null,
|
|
175
|
+
ready: false,
|
|
176
|
+
reason: 'missing_codex_runtime',
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
const { runIntegrationAutomation } = await import('../src/commands/integration-automation.js')
|
|
180
|
+
|
|
181
|
+
await runIntegrationAutomation('codex', { ide: 'cursor' }, '/repo')
|
|
182
|
+
|
|
183
|
+
expect(openUriMock).not.toHaveBeenCalled()
|
|
184
|
+
expect(logMock.warn).toHaveBeenCalledWith(
|
|
185
|
+
'Step 4/6: Could not resolve a runnable Codex runtime',
|
|
186
|
+
)
|
|
187
|
+
expect(logMock.hint).toHaveBeenCalledWith(
|
|
188
|
+
'Install the Codex plugin in Cursor or install the `codex` CLI, then rerun the command.',
|
|
189
|
+
)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('uses Claude CLI mode in Cursor when the Claude extension is unavailable but the CLI exists', async () => {
|
|
193
|
+
resolveHostIdeMock.mockResolvedValue({
|
|
194
|
+
ide: 'cursor',
|
|
195
|
+
confidence: 'high',
|
|
196
|
+
source: 'explicit',
|
|
197
|
+
candidates: ['cursor'],
|
|
198
|
+
})
|
|
199
|
+
resolveDispatchModeMock.mockResolvedValue({
|
|
200
|
+
mode: 'cli',
|
|
201
|
+
ready: true,
|
|
202
|
+
reason: 'claude-code_cli',
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const { runIntegrationAutomation } = await import('../src/commands/integration-automation.js')
|
|
206
|
+
|
|
207
|
+
await runIntegrationAutomation('claude-code', { ide: 'cursor' }, '/repo')
|
|
208
|
+
|
|
209
|
+
expect(openUriMock).toHaveBeenCalledWith(
|
|
210
|
+
'cursor://inspecto.inspecto/send?target=claude-code&prompt=Set+up+Inspecto+in+this+project&autoSend=false&workspace=%2Frepo&overrides=%7B%22type%22%3A%22cli%22%7D',
|
|
211
|
+
)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('stops and explains what to install when gemini cannot run in VS Code', async () => {
|
|
215
|
+
resolveHostIdeMock.mockResolvedValue({
|
|
216
|
+
ide: 'vscode',
|
|
217
|
+
confidence: 'high',
|
|
218
|
+
source: 'explicit',
|
|
219
|
+
candidates: ['vscode'],
|
|
220
|
+
})
|
|
221
|
+
resolveDispatchModeMock.mockResolvedValue({
|
|
222
|
+
mode: null,
|
|
223
|
+
ready: false,
|
|
224
|
+
reason: 'missing_gemini_runtime',
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
const { runIntegrationAutomation } = await import('../src/commands/integration-automation.js')
|
|
228
|
+
|
|
229
|
+
await runIntegrationAutomation('gemini', { ide: 'vscode' }, '/repo')
|
|
230
|
+
|
|
231
|
+
expect(openUriMock).not.toHaveBeenCalled()
|
|
232
|
+
expect(logMock.warn).toHaveBeenCalledWith(
|
|
233
|
+
'Step 4/6: Could not resolve a runnable Gemini runtime',
|
|
234
|
+
)
|
|
235
|
+
expect(logMock.hint).toHaveBeenCalledWith(
|
|
236
|
+
'Install the Gemini plugin in VS Code or install the `gemini` CLI, then rerun the command.',
|
|
237
|
+
)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('uses Gemini CLI mode in Trae CN when the Gemini extension is unavailable but the CLI exists', async () => {
|
|
241
|
+
resolveHostIdeMock.mockResolvedValue({
|
|
242
|
+
ide: 'trae-cn',
|
|
243
|
+
confidence: 'high',
|
|
244
|
+
source: 'explicit',
|
|
245
|
+
candidates: ['trae-cn'],
|
|
246
|
+
})
|
|
247
|
+
resolveDispatchModeMock.mockResolvedValue({
|
|
248
|
+
mode: 'cli',
|
|
249
|
+
ready: true,
|
|
250
|
+
reason: 'gemini_cli',
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const { runIntegrationAutomation } = await import('../src/commands/integration-automation.js')
|
|
254
|
+
|
|
255
|
+
await runIntegrationAutomation('gemini', { ide: 'trae-cn' }, '/repo')
|
|
256
|
+
|
|
257
|
+
expect(openIdeWorkspaceMock).toHaveBeenCalledWith('trae-cn', '/repo')
|
|
258
|
+
expect(logMock.success).toHaveBeenCalledWith('Step 5/6: Opened workspace in Trae CN')
|
|
259
|
+
expect(openUriMock).toHaveBeenCalledWith(
|
|
260
|
+
'trae-cn://inspecto.inspecto/send?target=gemini&prompt=Set+up+Inspecto+in+this+project&autoSend=false&workspace=%2Frepo&overrides=%7B%22type%22%3A%22cli%22%7D',
|
|
261
|
+
)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('skips extension install and URI launch when host ide confidence is low', async () => {
|
|
265
|
+
resolveHostIdeMock.mockResolvedValue({
|
|
266
|
+
ide: null,
|
|
267
|
+
confidence: 'low',
|
|
268
|
+
source: 'ambiguous',
|
|
269
|
+
candidates: ['cursor', 'vscode'],
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
const { runIntegrationAutomation } = await import('../src/commands/integration-automation.js')
|
|
273
|
+
|
|
274
|
+
await runIntegrationAutomation('codex', {}, '/repo')
|
|
275
|
+
|
|
276
|
+
expect(installExtensionMock).not.toHaveBeenCalled()
|
|
277
|
+
expect(openUriMock).not.toHaveBeenCalled()
|
|
278
|
+
expect(logMock.warn).toHaveBeenCalledWith(
|
|
279
|
+
'Step 2/6: Could not confidently resolve the host IDE',
|
|
280
|
+
)
|
|
281
|
+
expect(logMock.hint).toHaveBeenCalledWith(
|
|
282
|
+
'Re-run with --host-ide <vscode|cursor|trae|trae-cn> or run the command from the target IDE terminal to continue automatic setup.',
|
|
283
|
+
)
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('prints a dry preview without installing extensions or opening the IDE when preview mode is enabled', async () => {
|
|
287
|
+
resolveHostIdeMock.mockResolvedValue({
|
|
288
|
+
ide: 'cursor',
|
|
289
|
+
confidence: 'high',
|
|
290
|
+
source: 'explicit',
|
|
291
|
+
candidates: ['cursor'],
|
|
292
|
+
})
|
|
293
|
+
resolveDispatchModeMock.mockResolvedValue({
|
|
294
|
+
mode: 'cli',
|
|
295
|
+
ready: true,
|
|
296
|
+
reason: 'codex_cli',
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
const { runIntegrationAutomation } = await import('../src/commands/integration-automation.js')
|
|
300
|
+
|
|
301
|
+
await expect(
|
|
302
|
+
runIntegrationAutomation('codex', { ide: 'cursor', preview: true }, '/repo'),
|
|
303
|
+
).resolves.toMatchObject({
|
|
304
|
+
status: 'preview',
|
|
305
|
+
message:
|
|
306
|
+
'Preview complete. Inspecto did not write files or open IDE windows. Review the resolved setup below, then rerun without --preview to apply it.',
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
expect(installExtensionMock).not.toHaveBeenCalled()
|
|
310
|
+
expect(openIdeWorkspaceMock).not.toHaveBeenCalled()
|
|
311
|
+
expect(openUriMock).not.toHaveBeenCalled()
|
|
312
|
+
expect(logMock.info).toHaveBeenCalledWith('Step 2/6: Previewed host IDE resolution')
|
|
313
|
+
expect(logMock.info).toHaveBeenCalledWith('Step 3/6: Previewed Inspecto extension installation')
|
|
314
|
+
expect(logMock.info).toHaveBeenCalledWith('Step 4/6: Previewed Codex runtime')
|
|
315
|
+
expect(logMock.info).toHaveBeenCalledWith('Step 5/6: Previewed workspace routing in Cursor')
|
|
316
|
+
expect(logMock.info).toHaveBeenCalledWith('Step 6/6: Previewed onboarding launch')
|
|
317
|
+
expect(logMock.hint).toHaveBeenCalledWith(
|
|
318
|
+
'cursor://inspecto.inspecto/send?target=codex&prompt=Set+up+Inspecto+in+this+project&autoSend=true&workspace=%2Frepo&overrides=%7B%22type%22%3A%22cli%22%7D',
|
|
319
|
+
)
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
it('reports a blocked preflight when the host IDE binary is unavailable in preview mode', async () => {
|
|
323
|
+
resolveHostIdeMock.mockResolvedValue({
|
|
324
|
+
ide: 'trae-cn',
|
|
325
|
+
confidence: 'high',
|
|
326
|
+
source: 'explicit',
|
|
327
|
+
candidates: ['trae-cn'],
|
|
328
|
+
})
|
|
329
|
+
resolveDispatchModeMock.mockResolvedValue({
|
|
330
|
+
mode: 'cli',
|
|
331
|
+
ready: true,
|
|
332
|
+
reason: 'gemini_cli',
|
|
333
|
+
})
|
|
334
|
+
resolveHostIdeBinaryMock.mockResolvedValue(null)
|
|
335
|
+
|
|
336
|
+
const { runIntegrationAutomation } = await import('../src/commands/integration-automation.js')
|
|
337
|
+
|
|
338
|
+
await expect(
|
|
339
|
+
runIntegrationAutomation('gemini', { ide: 'trae-cn', preview: true }, '/repo'),
|
|
340
|
+
).resolves.toMatchObject({
|
|
341
|
+
status: 'preview_blocked',
|
|
342
|
+
message:
|
|
343
|
+
'Preview blocked. Inspecto did not write files or open IDE windows because setup cannot continue until the blocking issue below is resolved.',
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
expect(logMock.warn).toHaveBeenCalledWith(
|
|
347
|
+
'Step 3/6: Could not verify Inspecto extension installation in Trae CN',
|
|
348
|
+
)
|
|
349
|
+
expect(logMock.hint).toHaveBeenCalledWith(
|
|
350
|
+
'No Trae CN CLI binary was found. Automatic extension install and workspace opening may not work.',
|
|
351
|
+
)
|
|
352
|
+
expect(openUriMock).not.toHaveBeenCalled()
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
it('suppresses step logs and returns structured details in silent preview mode', async () => {
|
|
356
|
+
resolveHostIdeMock.mockResolvedValue({
|
|
357
|
+
ide: 'cursor',
|
|
358
|
+
confidence: 'high',
|
|
359
|
+
source: 'explicit',
|
|
360
|
+
candidates: ['cursor'],
|
|
361
|
+
})
|
|
362
|
+
resolveDispatchModeMock.mockResolvedValue({
|
|
363
|
+
mode: 'cli',
|
|
364
|
+
ready: true,
|
|
365
|
+
reason: 'codex_cli',
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
const { runIntegrationAutomation } = await import('../src/commands/integration-automation.js')
|
|
369
|
+
|
|
370
|
+
await expect(
|
|
371
|
+
runIntegrationAutomation('codex', { ide: 'cursor', preview: true, silent: true }, '/repo'),
|
|
372
|
+
).resolves.toMatchObject({
|
|
373
|
+
status: 'preview',
|
|
374
|
+
details: {
|
|
375
|
+
hostIde: {
|
|
376
|
+
id: 'cursor',
|
|
377
|
+
label: 'Cursor',
|
|
378
|
+
source: 'from --host-ide',
|
|
379
|
+
confidence: 'high',
|
|
380
|
+
candidates: ['cursor'],
|
|
381
|
+
},
|
|
382
|
+
inspectoExtension: {
|
|
383
|
+
source: 'marketplace',
|
|
384
|
+
reference: 'inspecto.inspecto',
|
|
385
|
+
binaryAvailable: true,
|
|
386
|
+
status: 'preview_ready',
|
|
387
|
+
},
|
|
388
|
+
runtime: {
|
|
389
|
+
assistant: 'Codex',
|
|
390
|
+
ready: true,
|
|
391
|
+
mode: 'cli',
|
|
392
|
+
},
|
|
393
|
+
workspace: {
|
|
394
|
+
path: '/repo',
|
|
395
|
+
attempted: true,
|
|
396
|
+
},
|
|
397
|
+
onboarding: {
|
|
398
|
+
uri: 'cursor://inspecto.inspecto/send?target=codex&prompt=Set+up+Inspecto+in+this+project&autoSend=true&workspace=%2Frepo&overrides=%7B%22type%22%3A%22cli%22%7D',
|
|
399
|
+
autoSend: true,
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
expect(logMock.info).not.toHaveBeenCalled()
|
|
405
|
+
expect(logMock.warn).not.toHaveBeenCalled()
|
|
406
|
+
expect(logMock.hint).not.toHaveBeenCalled()
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it('returns a workspace-specific message when onboarding opens but the target workspace could not be opened first', async () => {
|
|
410
|
+
resolveHostIdeMock.mockResolvedValue({
|
|
411
|
+
ide: 'cursor',
|
|
412
|
+
confidence: 'high',
|
|
413
|
+
source: 'explicit',
|
|
414
|
+
candidates: ['cursor'],
|
|
415
|
+
})
|
|
416
|
+
installExtensionMock.mockResolvedValue({
|
|
417
|
+
type: 'extension_installed',
|
|
418
|
+
id: 'inspecto.inspecto',
|
|
419
|
+
description: 'installed_via_cli',
|
|
420
|
+
})
|
|
421
|
+
openIdeWorkspaceMock.mockResolvedValue(false)
|
|
422
|
+
|
|
423
|
+
const { runIntegrationAutomation } = await import('../src/commands/integration-automation.js')
|
|
424
|
+
|
|
425
|
+
await expect(
|
|
426
|
+
runIntegrationAutomation('codex', { ide: 'cursor' }, '/repo'),
|
|
427
|
+
).resolves.toMatchObject({
|
|
428
|
+
status: 'partial',
|
|
429
|
+
message:
|
|
430
|
+
'Onboarding opened in Cursor for Codex, but Inspecto could not open the target workspace first.',
|
|
431
|
+
nextStep:
|
|
432
|
+
'If the wrong IDE window received onboarding, open /repo in Cursor and rerun the command from that project.',
|
|
433
|
+
})
|
|
434
|
+
})
|
|
435
|
+
})
|