@inspecto-dev/cli 0.2.0-alpha.6 → 0.3.0-alpha.1
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 +19 -20
- package/CHANGELOG.md +16 -0
- package/README.md +44 -9
- package/dist/bin.d.ts +1 -1
- package/dist/bin.js +448 -6
- package/dist/{chunk-PDDFPQJS.js → chunk-FZS2TLXQ.js} +620 -85
- package/dist/index.d.ts +106 -1
- package/dist/index.js +3 -1
- package/package.json +2 -2
- package/src/bin.ts +148 -0
- package/src/commands/apply.ts +5 -1
- package/src/commands/init.ts +60 -23
- package/src/commands/integration-install.ts +452 -0
- package/src/commands/onboard.ts +50 -0
- package/src/index.ts +4 -0
- package/src/inject/ast-injector.ts +15 -4
- package/src/inject/extension.ts +40 -24
- package/src/inject/gitignore.ts +10 -3
- package/src/onboarding/apply.ts +48 -9
- package/src/onboarding/planner.ts +6 -0
- package/src/onboarding/session.ts +434 -0
- package/src/onboarding/target-resolution.ts +116 -0
- package/src/types.ts +89 -0
- package/tests/apply.test.ts +47 -1
- package/tests/init.test.ts +31 -0
- package/tests/install-wrapper.test.ts +76 -0
- package/tests/integration-install.test.ts +294 -0
- package/tests/onboard.test.ts +258 -0
- package/.turbo/turbo-test.log +0 -16
package/src/types.ts
CHANGED
|
@@ -11,6 +11,14 @@ export type BuildTool = 'vite' | 'webpack' | 'rspack' | 'rsbuild' | 'esbuild' |
|
|
|
11
11
|
/** Machine-readable status for onboarding commands */
|
|
12
12
|
export type CommandStatus = 'ok' | 'warning' | 'blocked' | 'error'
|
|
13
13
|
|
|
14
|
+
/** Assistant-facing status for single-entry onboarding */
|
|
15
|
+
export type OnboardStatus =
|
|
16
|
+
| 'success'
|
|
17
|
+
| 'partial_success'
|
|
18
|
+
| 'needs_target_selection'
|
|
19
|
+
| 'needs_confirmation'
|
|
20
|
+
| 'error'
|
|
21
|
+
|
|
14
22
|
/** Structured message emitted by onboarding commands */
|
|
15
23
|
export interface CommandMessage {
|
|
16
24
|
code: string
|
|
@@ -54,6 +62,87 @@ export interface OnboardingContext {
|
|
|
54
62
|
providers: OnboardingProvider[]
|
|
55
63
|
}
|
|
56
64
|
|
|
65
|
+
export interface OnboardingTargetCandidate {
|
|
66
|
+
packagePath: string
|
|
67
|
+
configPath: string
|
|
68
|
+
buildTool: BuildTool
|
|
69
|
+
frameworks: string[]
|
|
70
|
+
automaticInjection: boolean
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface OnboardingTargetResolution {
|
|
74
|
+
status: 'resolved' | 'needs_selection'
|
|
75
|
+
selected?: OnboardingTargetCandidate
|
|
76
|
+
candidates: OnboardingTargetCandidate[]
|
|
77
|
+
reason: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface OnboardingSummary {
|
|
81
|
+
headline: string
|
|
82
|
+
changes: string[]
|
|
83
|
+
risks: string[]
|
|
84
|
+
manualFollowUp: string[]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface OnboardingConfirmation {
|
|
88
|
+
required: boolean
|
|
89
|
+
reason?: string
|
|
90
|
+
question?: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface OnboardingExecutionResult {
|
|
94
|
+
changedFiles: string[]
|
|
95
|
+
installedDependencies: string[]
|
|
96
|
+
selectedProviderDefault?: string
|
|
97
|
+
selectedIDE?: string
|
|
98
|
+
mutations: Mutation[]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface OnboardingDiagnostics {
|
|
102
|
+
warnings: string[]
|
|
103
|
+
errors: string[]
|
|
104
|
+
nextSteps: string[]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface OnboardingIdeExtensionStatus {
|
|
108
|
+
required: boolean
|
|
109
|
+
installed: boolean
|
|
110
|
+
manualRequired: boolean
|
|
111
|
+
installCommand?: string
|
|
112
|
+
marketplaceUrl?: string
|
|
113
|
+
openVsxUrl?: string
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface OnboardingVerification {
|
|
117
|
+
available: boolean
|
|
118
|
+
devCommand?: string
|
|
119
|
+
message: string
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface ResolvedOnboardingSession {
|
|
123
|
+
status: OnboardStatus
|
|
124
|
+
target: OnboardingTargetResolution
|
|
125
|
+
summary: OnboardingSummary
|
|
126
|
+
confirmation: OnboardingConfirmation
|
|
127
|
+
verification: OnboardingVerification
|
|
128
|
+
context: OnboardingContext
|
|
129
|
+
plan: PlanResult
|
|
130
|
+
projectRoot: string
|
|
131
|
+
selectedIDE?: { ide: string; supported: boolean } | null
|
|
132
|
+
providerDefault?: string
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface OnboardCommandResult {
|
|
136
|
+
status: OnboardStatus
|
|
137
|
+
target: OnboardingTargetResolution
|
|
138
|
+
summary: OnboardingSummary
|
|
139
|
+
confirmation: OnboardingConfirmation
|
|
140
|
+
ideExtension?: OnboardingIdeExtensionStatus
|
|
141
|
+
verification?: OnboardingVerification
|
|
142
|
+
result?: OnboardingExecutionResult
|
|
143
|
+
diagnostics?: OnboardingDiagnostics
|
|
144
|
+
}
|
|
145
|
+
|
|
57
146
|
/** Machine-readable detection output for skill-first onboarding */
|
|
58
147
|
export interface DetectionResult {
|
|
59
148
|
status: CommandStatus
|
package/tests/apply.test.ts
CHANGED
|
@@ -121,7 +121,7 @@ describe('apply onboarding flow', () => {
|
|
|
121
121
|
'pnpm add -D @inspecto-dev/plugin @inspecto-dev/core',
|
|
122
122
|
'/repo',
|
|
123
123
|
)
|
|
124
|
-
expect(astInjectorUtils.injectPlugin).toHaveBeenCalledWith('/repo', supportedBuild, false)
|
|
124
|
+
expect(astInjectorUtils.injectPlugin).toHaveBeenCalledWith('/repo', supportedBuild, false, false)
|
|
125
125
|
expect(fsUtils.writeJSON).toHaveBeenCalledWith('/repo/.inspecto/settings.local.json', {
|
|
126
126
|
ide: 'vscode',
|
|
127
127
|
'provider.default': 'codex.extension',
|
|
@@ -534,4 +534,50 @@ describe('apply onboarding flow', () => {
|
|
|
534
534
|
)
|
|
535
535
|
expect(fsUtils.writeJSON).toHaveBeenCalledWith('/repo/.inspecto/prompts.local.json', [])
|
|
536
536
|
})
|
|
537
|
+
|
|
538
|
+
it('prints the same short 3-step success guide when apply finishes cleanly', async () => {
|
|
539
|
+
const context: OnboardingContext = {
|
|
540
|
+
root: '/repo',
|
|
541
|
+
packageManager: 'pnpm',
|
|
542
|
+
buildTools: {
|
|
543
|
+
supported: [supportedBuild],
|
|
544
|
+
unsupported: [],
|
|
545
|
+
},
|
|
546
|
+
frameworks: {
|
|
547
|
+
supported: ['react'],
|
|
548
|
+
unsupported: [],
|
|
549
|
+
},
|
|
550
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
551
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const planResult: PlanResult = {
|
|
555
|
+
status: 'ok',
|
|
556
|
+
warnings: [],
|
|
557
|
+
blockers: [],
|
|
558
|
+
strategy: 'supported',
|
|
559
|
+
actions: [],
|
|
560
|
+
defaults: {
|
|
561
|
+
provider: 'codex',
|
|
562
|
+
ide: 'vscode',
|
|
563
|
+
shared: false,
|
|
564
|
+
extension: true,
|
|
565
|
+
},
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
vi.mocked(onboardingContext.buildOnboardingContext).mockResolvedValue(context)
|
|
569
|
+
vi.mocked(planner.createPlanResult).mockReturnValue(planResult)
|
|
570
|
+
|
|
571
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
572
|
+
|
|
573
|
+
await apply()
|
|
574
|
+
|
|
575
|
+
const output = consoleSpy.mock.calls.flatMap(call => call.map(value => String(value)))
|
|
576
|
+
expect(output.some(line => line.includes('Ready! Inspecto is set up.'))).toBe(true)
|
|
577
|
+
expect(output.some(line => line.includes('1. Start or restart your dev server.'))).toBe(true)
|
|
578
|
+
expect(output.some(line => line.includes('2. Open your app in the browser.'))).toBe(true)
|
|
579
|
+
expect(output.some(line => line.includes('3. Hold Alt + Click any element to inspect.'))).toBe(
|
|
580
|
+
true,
|
|
581
|
+
)
|
|
582
|
+
})
|
|
537
583
|
})
|
package/tests/init.test.ts
CHANGED
|
@@ -330,4 +330,35 @@ describe('init in monorepo roots', () => {
|
|
|
330
330
|
output.some(line => line.includes('Ready! Hold Alt + Click any element to inspect.')),
|
|
331
331
|
).toBe(false)
|
|
332
332
|
})
|
|
333
|
+
|
|
334
|
+
it('prints a short 3-step success guide after automatic setup succeeds', async () => {
|
|
335
|
+
vi.mocked(buildToolUtils.detectBuildTools).mockResolvedValue({
|
|
336
|
+
supported: [
|
|
337
|
+
{
|
|
338
|
+
tool: 'vite',
|
|
339
|
+
configPath: 'vite.config.ts',
|
|
340
|
+
label: 'Vite (vite.config.ts)',
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
unsupported: [],
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
347
|
+
|
|
348
|
+
await init({
|
|
349
|
+
shared: false,
|
|
350
|
+
skipInstall: false,
|
|
351
|
+
dryRun: false,
|
|
352
|
+
noExtension: false,
|
|
353
|
+
force: false,
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
const output = consoleSpy.mock.calls.flatMap(call => call.map(value => String(value)))
|
|
357
|
+
expect(output.some(line => line.includes('Ready! Inspecto is set up.'))).toBe(true)
|
|
358
|
+
expect(output.some(line => line.includes('1. Start or restart your dev server.'))).toBe(true)
|
|
359
|
+
expect(output.some(line => line.includes('2. Open your app in the browser.'))).toBe(true)
|
|
360
|
+
expect(output.some(line => line.includes('3. Hold Alt + Click any element to inspect.'))).toBe(
|
|
361
|
+
true,
|
|
362
|
+
)
|
|
363
|
+
})
|
|
333
364
|
})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process'
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { promisify } from 'node:util'
|
|
6
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
7
|
+
|
|
8
|
+
const execFileAsync = promisify(execFile)
|
|
9
|
+
|
|
10
|
+
describe('assistant integration bootstrap wrapper', () => {
|
|
11
|
+
const tempDirs: string[] = []
|
|
12
|
+
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
await Promise.all(tempDirs.map(dir => fs.rm(dir, { recursive: true, force: true })))
|
|
15
|
+
tempDirs.length = 0
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('falls back to raw asset download and still honors --force', async () => {
|
|
19
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'inspecto-wrapper-'))
|
|
20
|
+
tempDirs.push(tempRoot)
|
|
21
|
+
|
|
22
|
+
const fakeBin = path.join(tempRoot, 'bin')
|
|
23
|
+
await fs.mkdir(fakeBin, { recursive: true })
|
|
24
|
+
await fs.mkdir(path.join(tempRoot, '.github'), { recursive: true })
|
|
25
|
+
await fs.writeFile(
|
|
26
|
+
path.join(tempRoot, '.github/copilot-instructions.md'),
|
|
27
|
+
'old content\n',
|
|
28
|
+
'utf8',
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
await fs.writeFile(path.join(fakeBin, 'npx'), '#!/usr/bin/env bash\nexit 127\n', 'utf8')
|
|
32
|
+
await fs.chmod(path.join(fakeBin, 'npx'), 0o755)
|
|
33
|
+
|
|
34
|
+
await fs.writeFile(
|
|
35
|
+
path.join(fakeBin, 'curl'),
|
|
36
|
+
[
|
|
37
|
+
'#!/usr/bin/env bash',
|
|
38
|
+
'set -euo pipefail',
|
|
39
|
+
'out=""',
|
|
40
|
+
'while [[ $# -gt 0 ]]; do',
|
|
41
|
+
' case "$1" in',
|
|
42
|
+
' -o)',
|
|
43
|
+
' out="$2"',
|
|
44
|
+
' shift 2',
|
|
45
|
+
' ;;',
|
|
46
|
+
' *)',
|
|
47
|
+
' shift',
|
|
48
|
+
' ;;',
|
|
49
|
+
' esac',
|
|
50
|
+
'done',
|
|
51
|
+
'printf "downloaded from wrapper\n" > "$out"',
|
|
52
|
+
].join('\n'),
|
|
53
|
+
'utf8',
|
|
54
|
+
)
|
|
55
|
+
await fs.chmod(path.join(fakeBin, 'curl'), 0o755)
|
|
56
|
+
|
|
57
|
+
const scriptPath = path.resolve(
|
|
58
|
+
__dirname,
|
|
59
|
+
'../../../assistant-integrations/scripts/install.sh',
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
await execFileAsync('bash', [scriptPath, 'copilot', '--force'], {
|
|
63
|
+
cwd: tempRoot,
|
|
64
|
+
env: {
|
|
65
|
+
...process.env,
|
|
66
|
+
PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const installed = await fs.readFile(
|
|
71
|
+
path.join(tempRoot, '.github/copilot-instructions.md'),
|
|
72
|
+
'utf8',
|
|
73
|
+
)
|
|
74
|
+
expect(installed).toBe('downloaded from wrapper\n')
|
|
75
|
+
})
|
|
76
|
+
})
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
const writeFileMock = vi.fn()
|
|
4
|
+
const existsMock = vi.fn()
|
|
5
|
+
const chmodMock = 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/utils/fs.js', () => ({
|
|
18
|
+
writeFile: writeFileMock,
|
|
19
|
+
exists: existsMock,
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
vi.mock('node:fs/promises', () => ({
|
|
23
|
+
default: {
|
|
24
|
+
chmod: chmodMock,
|
|
25
|
+
},
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
vi.mock('node:os', () => ({
|
|
29
|
+
homedir: () => '/Users/tester',
|
|
30
|
+
}))
|
|
31
|
+
|
|
32
|
+
vi.mock('../src/utils/logger.js', () => ({
|
|
33
|
+
log: logMock,
|
|
34
|
+
}))
|
|
35
|
+
|
|
36
|
+
describe('integration install', () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
vi.resetModules()
|
|
39
|
+
vi.clearAllMocks()
|
|
40
|
+
existsMock.mockResolvedValue(false)
|
|
41
|
+
vi.stubGlobal(
|
|
42
|
+
'fetch',
|
|
43
|
+
vi.fn().mockResolvedValue({
|
|
44
|
+
ok: true,
|
|
45
|
+
text: async () => '# mock asset',
|
|
46
|
+
status: 200,
|
|
47
|
+
statusText: 'OK',
|
|
48
|
+
}),
|
|
49
|
+
)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('installs the Claude Code skill into the user skill directory', async () => {
|
|
53
|
+
const { installIntegration } = await import('../src/commands/integration-install.js')
|
|
54
|
+
|
|
55
|
+
await installIntegration('claude-code', { scope: 'user' })
|
|
56
|
+
|
|
57
|
+
expect(writeFileMock).toHaveBeenCalledWith(
|
|
58
|
+
'/Users/tester/.claude/skills/inspecto-onboarding-claude-code/SKILL.md',
|
|
59
|
+
'# mock asset',
|
|
60
|
+
)
|
|
61
|
+
expect(writeFileMock).toHaveBeenCalledWith(
|
|
62
|
+
'/Users/tester/.claude/skills/inspecto-onboarding-claude-code/agents/openai.yaml',
|
|
63
|
+
'# mock asset',
|
|
64
|
+
)
|
|
65
|
+
expect(writeFileMock).toHaveBeenCalledWith(
|
|
66
|
+
'/Users/tester/.claude/skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh',
|
|
67
|
+
'# mock asset',
|
|
68
|
+
)
|
|
69
|
+
expect(chmodMock).toHaveBeenCalledWith(
|
|
70
|
+
'/Users/tester/.claude/skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh',
|
|
71
|
+
0o755,
|
|
72
|
+
)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('installs the Codex skill into the Codex skill directory', async () => {
|
|
76
|
+
const { installIntegration } = await import('../src/commands/integration-install.js')
|
|
77
|
+
|
|
78
|
+
await installIntegration('codex')
|
|
79
|
+
|
|
80
|
+
expect(writeFileMock).toHaveBeenCalledWith(
|
|
81
|
+
'/Users/tester/.codex/skills/inspecto-onboarding-codex/SKILL.md',
|
|
82
|
+
'# mock asset',
|
|
83
|
+
)
|
|
84
|
+
expect(writeFileMock).toHaveBeenCalledWith(
|
|
85
|
+
'/Users/tester/.codex/skills/inspecto-onboarding-codex/agents/openai.yaml',
|
|
86
|
+
'# mock asset',
|
|
87
|
+
)
|
|
88
|
+
expect(writeFileMock).toHaveBeenCalledWith(
|
|
89
|
+
'/Users/tester/.codex/skills/inspecto-onboarding-codex/scripts/run-inspecto.sh',
|
|
90
|
+
'# mock asset',
|
|
91
|
+
)
|
|
92
|
+
expect(chmodMock).toHaveBeenCalledWith(
|
|
93
|
+
'/Users/tester/.codex/skills/inspecto-onboarding-codex/scripts/run-inspecto.sh',
|
|
94
|
+
0o755,
|
|
95
|
+
)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('installs the Cursor AGENTS fallback when agents mode is selected', async () => {
|
|
99
|
+
const { installIntegration } = await import('../src/commands/integration-install.js')
|
|
100
|
+
|
|
101
|
+
await installIntegration('cursor', { mode: 'agents' })
|
|
102
|
+
|
|
103
|
+
expect(writeFileMock).toHaveBeenCalledTimes(1)
|
|
104
|
+
expect(writeFileMock).toHaveBeenCalledWith('AGENTS.md', '# mock asset')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('refuses a multi-file Claude install before writing anything when one target already exists', async () => {
|
|
108
|
+
const { installIntegration } = await import('../src/commands/integration-install.js')
|
|
109
|
+
|
|
110
|
+
existsMock.mockImplementation(async filePath => {
|
|
111
|
+
return (
|
|
112
|
+
filePath ===
|
|
113
|
+
'/Users/tester/.claude/skills/inspecto-onboarding-claude-code/agents/openai.yaml'
|
|
114
|
+
)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
await expect(installIntegration('claude-code', { scope: 'user' })).rejects.toThrow(
|
|
118
|
+
'Refusing to overwrite existing file',
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
expect(writeFileMock).not.toHaveBeenCalled()
|
|
122
|
+
expect(chmodMock).not.toHaveBeenCalled()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('downloads all Claude assets before writing any file', async () => {
|
|
126
|
+
const fetchMock = vi
|
|
127
|
+
.fn()
|
|
128
|
+
.mockResolvedValueOnce({
|
|
129
|
+
ok: true,
|
|
130
|
+
text: async () => '# skill',
|
|
131
|
+
status: 200,
|
|
132
|
+
statusText: 'OK',
|
|
133
|
+
})
|
|
134
|
+
.mockResolvedValueOnce({
|
|
135
|
+
ok: false,
|
|
136
|
+
text: async () => '',
|
|
137
|
+
status: 503,
|
|
138
|
+
statusText: 'Unavailable',
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
vi.stubGlobal('fetch', fetchMock)
|
|
142
|
+
|
|
143
|
+
const { installIntegration } = await import('../src/commands/integration-install.js')
|
|
144
|
+
|
|
145
|
+
await expect(installIntegration('claude-code', { scope: 'user' })).rejects.toThrow(
|
|
146
|
+
'Failed to download',
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
expect(writeFileMock).not.toHaveBeenCalled()
|
|
150
|
+
expect(chmodMock).not.toHaveBeenCalled()
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('surfaces the asset URL when fetch itself fails', async () => {
|
|
154
|
+
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('fetch failed')))
|
|
155
|
+
|
|
156
|
+
const { installIntegration } = await import('../src/commands/integration-install.js')
|
|
157
|
+
|
|
158
|
+
await expect(installIntegration('codex')).rejects.toThrow(
|
|
159
|
+
'Failed to download https://raw.githubusercontent.com/inspecto-dev/inspecto/main/skills/inspecto-onboarding-codex/SKILL.md: fetch failed',
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
expect(writeFileMock).not.toHaveBeenCalled()
|
|
163
|
+
expect(chmodMock).not.toHaveBeenCalled()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('lists supported integrations with their preferred install targets', async () => {
|
|
167
|
+
const { listIntegrationManifests } = await import('../src/commands/integration-install.js')
|
|
168
|
+
|
|
169
|
+
expect(listIntegrationManifests()).toEqual(
|
|
170
|
+
expect.arrayContaining([
|
|
171
|
+
expect.objectContaining({
|
|
172
|
+
assistant: 'codex',
|
|
173
|
+
type: 'native-skill',
|
|
174
|
+
installTarget: '~/.codex/skills/',
|
|
175
|
+
cliSupported: true,
|
|
176
|
+
}),
|
|
177
|
+
expect.objectContaining({
|
|
178
|
+
assistant: 'claude-code',
|
|
179
|
+
type: 'native-skill',
|
|
180
|
+
installTarget: '.claude/skills/ or ~/.claude/skills/',
|
|
181
|
+
}),
|
|
182
|
+
expect.objectContaining({
|
|
183
|
+
assistant: 'copilot',
|
|
184
|
+
type: 'instruction-template',
|
|
185
|
+
installTarget: '.github/copilot-instructions.md or AGENTS.md',
|
|
186
|
+
}),
|
|
187
|
+
]),
|
|
188
|
+
)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('describes the resolved install targets for a selected assistant variant', async () => {
|
|
192
|
+
const { describeIntegration } = await import('../src/commands/integration-install.js')
|
|
193
|
+
|
|
194
|
+
expect(describeIntegration('claude-code', { scope: 'user' })).toMatchObject({
|
|
195
|
+
assistant: 'claude-code',
|
|
196
|
+
targets: [
|
|
197
|
+
'/Users/tester/.claude/skills/inspecto-onboarding-claude-code/SKILL.md',
|
|
198
|
+
'/Users/tester/.claude/skills/inspecto-onboarding-claude-code/agents/openai.yaml',
|
|
199
|
+
'/Users/tester/.claude/skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh',
|
|
200
|
+
],
|
|
201
|
+
preferredInstall: 'npx @inspecto-dev/cli integrations install claude-code --scope project',
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('describes codex using the same CLI-managed install target model', async () => {
|
|
206
|
+
const { describeIntegration } = await import('../src/commands/integration-install.js')
|
|
207
|
+
|
|
208
|
+
expect(describeIntegration('codex')).toMatchObject({
|
|
209
|
+
assistant: 'codex',
|
|
210
|
+
targets: [
|
|
211
|
+
'/Users/tester/.codex/skills/inspecto-onboarding-codex/SKILL.md',
|
|
212
|
+
'/Users/tester/.codex/skills/inspecto-onboarding-codex/agents/openai.yaml',
|
|
213
|
+
'/Users/tester/.codex/skills/inspecto-onboarding-codex/scripts/run-inspecto.sh',
|
|
214
|
+
],
|
|
215
|
+
preferredInstall: 'npx @inspecto-dev/cli integrations install codex',
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it('prints integration paths without using post-install hints', async () => {
|
|
220
|
+
const { printIntegrationPath } = await import('../src/commands/integration-install.js')
|
|
221
|
+
|
|
222
|
+
printIntegrationPath('claude-code', { scope: 'user' })
|
|
223
|
+
|
|
224
|
+
expect(logMock.info).toHaveBeenCalledWith(
|
|
225
|
+
'/Users/tester/.claude/skills/inspecto-onboarding-claude-code/SKILL.md',
|
|
226
|
+
)
|
|
227
|
+
expect(logMock.hint).toHaveBeenCalledWith(
|
|
228
|
+
'Preferred install: npx @inspecto-dev/cli integrations install claude-code --scope project',
|
|
229
|
+
)
|
|
230
|
+
expect(logMock.hint).not.toHaveBeenCalledWith('Restart Claude Code to load the new skill.')
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it('rejects unsupported extra args and options for list/path', async () => {
|
|
234
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => undefined) as never)
|
|
235
|
+
|
|
236
|
+
vi.doMock('../src/commands/init.js', () => ({ init: vi.fn() }))
|
|
237
|
+
vi.doMock('../src/commands/doctor.js', () => ({ doctor: vi.fn() }))
|
|
238
|
+
vi.doMock('../src/commands/teardown.js', () => ({ teardown: vi.fn() }))
|
|
239
|
+
vi.doMock('../src/commands/detect.js', () => ({ detect: vi.fn() }))
|
|
240
|
+
vi.doMock('../src/commands/plan.js', () => ({ plan: vi.fn() }))
|
|
241
|
+
vi.doMock('../src/commands/apply.js', () => ({ apply: vi.fn() }))
|
|
242
|
+
|
|
243
|
+
const { runCli } = await import('../src/bin.js')
|
|
244
|
+
|
|
245
|
+
await runCli(['node', 'inspecto', 'integrations', 'list', 'extra'])
|
|
246
|
+
await runCli(['node', 'inspecto', 'integrations', 'path', 'claude-code', '--force'])
|
|
247
|
+
|
|
248
|
+
expect(logMock.error).toHaveBeenCalledWith(
|
|
249
|
+
'The `list` subcommand does not accept assistant names, --scope, --mode, or --force.',
|
|
250
|
+
)
|
|
251
|
+
expect(logMock.error).toHaveBeenCalledWith('The `path` subcommand does not support `--force`.')
|
|
252
|
+
expect(exitSpy).toHaveBeenCalledWith(1)
|
|
253
|
+
|
|
254
|
+
exitSpy.mockRestore()
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('wires the nested CLI command to the integration installer', async () => {
|
|
258
|
+
const installCommand = vi.fn().mockResolvedValue(undefined)
|
|
259
|
+
const listCommand = vi.fn().mockResolvedValue(undefined)
|
|
260
|
+
const pathCommand = vi.fn().mockResolvedValue(undefined)
|
|
261
|
+
|
|
262
|
+
vi.doMock('../src/commands/init.js', () => ({ init: vi.fn() }))
|
|
263
|
+
vi.doMock('../src/commands/doctor.js', () => ({ doctor: vi.fn() }))
|
|
264
|
+
vi.doMock('../src/commands/teardown.js', () => ({ teardown: vi.fn() }))
|
|
265
|
+
vi.doMock('../src/commands/detect.js', () => ({ detect: vi.fn() }))
|
|
266
|
+
vi.doMock('../src/commands/plan.js', () => ({ plan: vi.fn() }))
|
|
267
|
+
vi.doMock('../src/commands/apply.js', () => ({ apply: vi.fn() }))
|
|
268
|
+
vi.doMock('../src/commands/integration-install.js', () => ({
|
|
269
|
+
installIntegration: installCommand,
|
|
270
|
+
printIntegrationList: listCommand,
|
|
271
|
+
printIntegrationPath: pathCommand,
|
|
272
|
+
}))
|
|
273
|
+
|
|
274
|
+
const { runCli } = await import('../src/bin.js')
|
|
275
|
+
|
|
276
|
+
await runCli([
|
|
277
|
+
'node',
|
|
278
|
+
'inspecto',
|
|
279
|
+
'integrations',
|
|
280
|
+
'install',
|
|
281
|
+
'copilot',
|
|
282
|
+
'--mode',
|
|
283
|
+
'agents',
|
|
284
|
+
'--force',
|
|
285
|
+
])
|
|
286
|
+
|
|
287
|
+
await runCli(['node', 'inspecto', 'integrations', 'list'])
|
|
288
|
+
await runCli(['node', 'inspecto', 'integrations', 'path', 'claude-code', '--scope', 'user'])
|
|
289
|
+
|
|
290
|
+
expect(installCommand).toHaveBeenCalledWith('copilot', { mode: 'agents', force: true })
|
|
291
|
+
expect(listCommand).toHaveBeenCalledWith()
|
|
292
|
+
expect(pathCommand).toHaveBeenCalledWith('claude-code', { scope: 'user' })
|
|
293
|
+
})
|
|
294
|
+
})
|