@inspecto-dev/cli 0.3.1 → 0.3.3

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.
@@ -0,0 +1,165 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+
3
+ const describeIntegrationMock = vi.fn()
4
+ const runIntegrationAutomationMock = vi.fn()
5
+ const exitProcessMock = vi.fn()
6
+ const logMock = {
7
+ header: vi.fn(),
8
+ info: vi.fn(),
9
+ success: vi.fn(),
10
+ warn: vi.fn(),
11
+ hint: vi.fn(),
12
+ ready: vi.fn(),
13
+ error: vi.fn(),
14
+ blank: vi.fn(),
15
+ }
16
+
17
+ vi.mock('../src/commands/integration-install.js', () => ({
18
+ describeIntegration: describeIntegrationMock,
19
+ }))
20
+
21
+ vi.mock('../src/commands/integration-automation.js', () => ({
22
+ runIntegrationAutomation: runIntegrationAutomationMock,
23
+ }))
24
+
25
+ vi.mock('../src/utils/logger.js', () => ({
26
+ log: logMock,
27
+ }))
28
+
29
+ vi.mock('../src/utils/process.js', () => ({
30
+ exitProcess: exitProcessMock,
31
+ }))
32
+
33
+ describe('integration doctor', () => {
34
+ beforeEach(() => {
35
+ vi.resetAllMocks()
36
+ vi.spyOn(process, 'cwd').mockReturnValue('/repo')
37
+ describeIntegrationMock.mockReturnValue({
38
+ assistant: 'codex',
39
+ type: 'native-skill',
40
+ targets: ['.agents/skills/inspecto-onboarding-codex/SKILL.md'],
41
+ preferredInstall: 'npx @inspecto-dev/cli integrations install codex',
42
+ cliSupported: true,
43
+ })
44
+ runIntegrationAutomationMock.mockResolvedValue({
45
+ status: 'preview',
46
+ message:
47
+ 'Preview complete. Inspecto did not write files or open IDE windows. Review the resolved setup below, then rerun without --preview to apply it.',
48
+ nextStep:
49
+ 'Run the same command again without --preview to apply the integration and launch onboarding.',
50
+ details: {
51
+ hostIde: {
52
+ id: 'cursor',
53
+ label: 'Cursor',
54
+ source: 'from --host-ide',
55
+ confidence: 'high',
56
+ candidates: ['cursor'],
57
+ },
58
+ inspectoExtension: {
59
+ source: 'marketplace',
60
+ reference: 'inspecto.inspecto',
61
+ binaryAvailable: true,
62
+ status: 'preview_ready',
63
+ },
64
+ runtime: {
65
+ assistant: 'Codex',
66
+ ready: true,
67
+ mode: 'cli',
68
+ },
69
+ workspace: {
70
+ path: '/repo',
71
+ attempted: true,
72
+ },
73
+ onboarding: {
74
+ uri: 'cursor://inspecto.inspecto/send?...',
75
+ autoSend: true,
76
+ },
77
+ },
78
+ })
79
+ })
80
+
81
+ it('returns structured JSON diagnostics for integration preflight checks', async () => {
82
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
83
+ const { integrationDoctor } = await import('../src/commands/integration-doctor.js')
84
+
85
+ const result = await integrationDoctor('codex', { ide: 'cursor', json: true })
86
+
87
+ expect(runIntegrationAutomationMock).toHaveBeenCalledWith(
88
+ 'codex',
89
+ { ide: 'cursor', preview: true, silent: true },
90
+ '/repo',
91
+ )
92
+ expect(result).toMatchObject({
93
+ schemaVersion: '1',
94
+ status: 'ok',
95
+ assistant: 'codex',
96
+ assets: ['.agents/skills/inspecto-onboarding-codex/SKILL.md'],
97
+ automation: {
98
+ status: 'preview',
99
+ details: {
100
+ hostIde: {
101
+ id: 'cursor',
102
+ },
103
+ },
104
+ },
105
+ })
106
+ expect(logMock.header).not.toHaveBeenCalled()
107
+ expect(consoleSpy).toHaveBeenCalledWith(JSON.stringify(result, null, 2))
108
+ })
109
+
110
+ it('prints a human-readable preflight summary in text mode', async () => {
111
+ const { integrationDoctor } = await import('../src/commands/integration-doctor.js')
112
+
113
+ const result = await integrationDoctor('codex', { ide: 'cursor' })
114
+
115
+ expect(result.status).toBe('ok')
116
+ expect(logMock.header).toHaveBeenCalledWith('Inspecto Integration Doctor')
117
+ expect(logMock.info).toHaveBeenCalledWith('Assistant: codex')
118
+ expect(logMock.info).toHaveBeenCalledWith('Host IDE: Cursor (from --host-ide)')
119
+ expect(logMock.info).toHaveBeenCalledWith('Inspecto extension: marketplace (inspecto.inspecto)')
120
+ expect(logMock.info).toHaveBeenCalledWith('Runtime: Codex via cli')
121
+ expect(logMock.info).toHaveBeenCalledWith('Workspace: /repo')
122
+ expect(logMock.info).toHaveBeenCalledWith('Onboarding URI: cursor://inspecto.inspecto/send?...')
123
+ })
124
+
125
+ it('prints a compact summary when compact mode is enabled', async () => {
126
+ const { integrationDoctor } = await import('../src/commands/integration-doctor.js')
127
+
128
+ const result = await integrationDoctor('codex', { ide: 'cursor', compact: true })
129
+
130
+ expect(result.status).toBe('ok')
131
+ expect(logMock.header).toHaveBeenCalledWith('Inspecto Integration Doctor')
132
+ expect(logMock.info).toHaveBeenCalledWith('Assistant: codex')
133
+ expect(logMock.info).toHaveBeenCalledWith('Host IDE: Cursor (from --host-ide)')
134
+ expect(logMock.info).toHaveBeenCalledWith('Runtime: Codex via cli')
135
+ expect(logMock.info).not.toHaveBeenCalledWith('Asset targets:')
136
+ expect(logMock.info).not.toHaveBeenCalledWith(
137
+ 'Inspecto extension: marketplace (inspecto.inspecto)',
138
+ )
139
+ expect(logMock.info).not.toHaveBeenCalledWith(
140
+ 'Onboarding URI: cursor://inspecto.inspecto/send?...',
141
+ )
142
+ })
143
+
144
+ it('exits with code 1 when failOnBlocked is enabled and the preflight is blocked', async () => {
145
+ runIntegrationAutomationMock.mockResolvedValue({
146
+ status: 'preview_blocked',
147
+ message:
148
+ 'Preview blocked. Inspecto did not write files or open IDE windows because setup cannot continue until the blocking issue below is resolved.',
149
+ nextStep: 'Install Codex in Cursor and rerun the command.',
150
+ details: {
151
+ runtime: {
152
+ assistant: 'Codex',
153
+ ready: false,
154
+ mode: null,
155
+ },
156
+ },
157
+ })
158
+
159
+ const { integrationDoctor } = await import('../src/commands/integration-doctor.js')
160
+
161
+ await integrationDoctor('codex', { ide: 'cursor', failOnBlocked: true })
162
+
163
+ expect(exitProcessMock).toHaveBeenCalledWith(1)
164
+ })
165
+ })
@@ -0,0 +1,172 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import * as fsUtils from '../src/utils/fs.js'
3
+
4
+ vi.mock('../src/utils/fs.js', () => ({
5
+ exists: vi.fn(),
6
+ readJSON: vi.fn(),
7
+ }))
8
+
9
+ describe('resolveIntegrationHostIde', () => {
10
+ const originalEnv = process.env
11
+
12
+ beforeEach(() => {
13
+ vi.resetAllMocks()
14
+ process.env = { ...originalEnv }
15
+ delete process.env.TERM_PROGRAM
16
+ delete process.env.CURSOR_TRACE_DIR
17
+ delete process.env.CURSOR_CHANNEL
18
+ delete process.env.TRAE_APP_DIR
19
+ delete process.env.__CFBundleIdentifier
20
+ delete process.env.COCO_IDE_PLUGIN_TYPE
21
+ delete process.env.npm_config_user_agent
22
+ vi.mocked(fsUtils.exists).mockResolvedValue(false)
23
+ vi.mocked(fsUtils.readJSON).mockResolvedValue(null)
24
+ })
25
+
26
+ afterEach(() => {
27
+ process.env = originalEnv
28
+ })
29
+
30
+ it('prefers an explicit ide argument', async () => {
31
+ const { resolveIntegrationHostIde } = await import('../src/commands/integration-host-ide.js')
32
+
33
+ await expect(
34
+ resolveIntegrationHostIde({
35
+ explicitIde: 'cursor',
36
+ cwd: '/repo',
37
+ }),
38
+ ).resolves.toMatchObject({
39
+ ide: 'cursor',
40
+ confidence: 'high',
41
+ source: 'explicit',
42
+ })
43
+ })
44
+
45
+ it('accepts trae-cn as an explicit ide argument', async () => {
46
+ const { resolveIntegrationHostIde } = await import('../src/commands/integration-host-ide.js')
47
+
48
+ await expect(
49
+ resolveIntegrationHostIde({
50
+ explicitIde: 'trae-cn',
51
+ cwd: '/repo',
52
+ }),
53
+ ).resolves.toMatchObject({
54
+ ide: 'trae-cn',
55
+ confidence: 'high',
56
+ source: 'explicit',
57
+ })
58
+ })
59
+
60
+ it('uses .inspecto settings ide when present', async () => {
61
+ vi.mocked(fsUtils.readJSON).mockImplementation(async filePath => {
62
+ if (filePath === '/repo/.inspecto/settings.local.json') {
63
+ return { ide: 'vscode' }
64
+ }
65
+ return null
66
+ })
67
+
68
+ const { resolveIntegrationHostIde } = await import('../src/commands/integration-host-ide.js')
69
+
70
+ await expect(
71
+ resolveIntegrationHostIde({
72
+ cwd: '/repo',
73
+ }),
74
+ ).resolves.toMatchObject({
75
+ ide: 'vscode',
76
+ confidence: 'high',
77
+ source: 'config',
78
+ })
79
+ })
80
+
81
+ it('uses trae-cn from .inspecto settings when present', async () => {
82
+ vi.mocked(fsUtils.readJSON).mockImplementation(async filePath => {
83
+ if (filePath === '/repo/.inspecto/settings.local.json') {
84
+ return { ide: 'trae-cn' }
85
+ }
86
+ return null
87
+ })
88
+
89
+ const { resolveIntegrationHostIde } = await import('../src/commands/integration-host-ide.js')
90
+
91
+ await expect(
92
+ resolveIntegrationHostIde({
93
+ cwd: '/repo',
94
+ }),
95
+ ).resolves.toMatchObject({
96
+ ide: 'trae-cn',
97
+ confidence: 'high',
98
+ source: 'config',
99
+ })
100
+ })
101
+
102
+ it('treats a single env-detected ide as high confidence', async () => {
103
+ process.env.CURSOR_CHANNEL = 'stable'
104
+
105
+ const { resolveIntegrationHostIde } = await import('../src/commands/integration-host-ide.js')
106
+
107
+ await expect(
108
+ resolveIntegrationHostIde({
109
+ cwd: '/repo',
110
+ }),
111
+ ).resolves.toMatchObject({
112
+ ide: 'cursor',
113
+ confidence: 'high',
114
+ source: 'env',
115
+ })
116
+ })
117
+
118
+ it('treats a single project artifact as medium confidence', async () => {
119
+ vi.mocked(fsUtils.exists).mockImplementation(async filePath => {
120
+ return filePath === '/repo/.cursor'
121
+ })
122
+
123
+ const { resolveIntegrationHostIde } = await import('../src/commands/integration-host-ide.js')
124
+
125
+ await expect(
126
+ resolveIntegrationHostIde({
127
+ cwd: '/repo',
128
+ }),
129
+ ).resolves.toMatchObject({
130
+ ide: 'cursor',
131
+ confidence: 'medium',
132
+ source: 'artifact',
133
+ })
134
+ })
135
+
136
+ it('treats a .trae-cn project artifact as medium confidence', async () => {
137
+ vi.mocked(fsUtils.exists).mockImplementation(async filePath => {
138
+ return filePath === '/repo/.trae-cn'
139
+ })
140
+
141
+ const { resolveIntegrationHostIde } = await import('../src/commands/integration-host-ide.js')
142
+
143
+ await expect(
144
+ resolveIntegrationHostIde({
145
+ cwd: '/repo',
146
+ }),
147
+ ).resolves.toMatchObject({
148
+ ide: 'trae-cn',
149
+ confidence: 'medium',
150
+ source: 'artifact',
151
+ })
152
+ })
153
+
154
+ it('refuses to resolve an ide when project artifacts are ambiguous', async () => {
155
+ vi.mocked(fsUtils.exists).mockImplementation(async filePath => {
156
+ return filePath === '/repo/.cursor' || filePath === '/repo/.vscode'
157
+ })
158
+
159
+ const { resolveIntegrationHostIde } = await import('../src/commands/integration-host-ide.js')
160
+
161
+ await expect(
162
+ resolveIntegrationHostIde({
163
+ cwd: '/repo',
164
+ }),
165
+ ).resolves.toMatchObject({
166
+ ide: null,
167
+ confidence: 'low',
168
+ source: 'ambiguous',
169
+ candidates: ['cursor', 'vscode'],
170
+ })
171
+ })
172
+ })