@inspecto-dev/cli 0.3.1 → 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.
@@ -0,0 +1,156 @@
1
+ import path from 'node:path'
2
+ import { exists, readJSON } from '../utils/fs.js'
3
+ import {
4
+ HOST_IDE_IDS,
5
+ getHostIdeArtifactPath,
6
+ isSupportedHostIde,
7
+ type SupportedHostIde,
8
+ } from '../integrations/capabilities.js'
9
+ export type HostIdeConfidence = 'high' | 'medium' | 'low'
10
+ export type HostIdeSource = 'explicit' | 'config' | 'env' | 'artifact' | 'ambiguous' | 'none'
11
+
12
+ export interface ResolveIntegrationHostIdeOptions {
13
+ explicitIde?: string
14
+ cwd?: string
15
+ }
16
+
17
+ export interface ResolvedIntegrationHostIde {
18
+ ide: SupportedHostIde | null
19
+ confidence: HostIdeConfidence
20
+ source: HostIdeSource
21
+ candidates: SupportedHostIde[]
22
+ }
23
+
24
+ interface InspectoSettingsShape {
25
+ ide?: string
26
+ }
27
+
28
+ export async function resolveIntegrationHostIde(
29
+ options: ResolveIntegrationHostIdeOptions = {},
30
+ ): Promise<ResolvedIntegrationHostIde> {
31
+ if (isSupportedHostIde(options.explicitIde)) {
32
+ return {
33
+ ide: options.explicitIde,
34
+ confidence: 'high',
35
+ source: 'explicit',
36
+ candidates: [options.explicitIde],
37
+ }
38
+ }
39
+
40
+ const cwd = options.cwd ?? process.cwd()
41
+ const configuredIde = await resolveConfiguredIde(cwd)
42
+ if (configuredIde) {
43
+ return {
44
+ ide: configuredIde,
45
+ confidence: 'high',
46
+ source: 'config',
47
+ candidates: [configuredIde],
48
+ }
49
+ }
50
+
51
+ const envCandidates = detectEnvHostIdes()
52
+ if (envCandidates.length === 1) {
53
+ return {
54
+ ide: envCandidates[0],
55
+ confidence: 'high',
56
+ source: 'env',
57
+ candidates: envCandidates,
58
+ }
59
+ }
60
+
61
+ if (envCandidates.length > 1) {
62
+ return {
63
+ ide: null,
64
+ confidence: 'low',
65
+ source: 'ambiguous',
66
+ candidates: envCandidates,
67
+ }
68
+ }
69
+
70
+ const artifactCandidates = await detectArtifactHostIdes(cwd)
71
+ if (artifactCandidates.length === 1) {
72
+ return {
73
+ ide: artifactCandidates[0],
74
+ confidence: 'medium',
75
+ source: 'artifact',
76
+ candidates: artifactCandidates,
77
+ }
78
+ }
79
+
80
+ if (artifactCandidates.length > 1) {
81
+ return {
82
+ ide: null,
83
+ confidence: 'low',
84
+ source: 'ambiguous',
85
+ candidates: artifactCandidates,
86
+ }
87
+ }
88
+
89
+ return {
90
+ ide: null,
91
+ confidence: 'low',
92
+ source: 'none',
93
+ candidates: [],
94
+ }
95
+ }
96
+
97
+ async function resolveConfiguredIde(cwd: string): Promise<SupportedHostIde | null> {
98
+ const settingsPaths = [
99
+ path.join(cwd, '.inspecto', 'settings.local.json'),
100
+ path.join(cwd, '.inspecto', 'settings.json'),
101
+ ]
102
+
103
+ for (const settingsPath of settingsPaths) {
104
+ const settings = await readJSON<InspectoSettingsShape>(settingsPath)
105
+ if (settings && isSupportedHostIde(settings.ide)) {
106
+ return settings.ide
107
+ }
108
+ }
109
+
110
+ return null
111
+ }
112
+
113
+ function detectEnvHostIdes(): SupportedHostIde[] {
114
+ const detected = new Set<SupportedHostIde>()
115
+
116
+ if (process.env.CURSOR_TRACE_DIR || process.env.CURSOR_CHANNEL) {
117
+ detected.add('cursor')
118
+ }
119
+
120
+ if (
121
+ process.env.TRAE_APP_DIR ||
122
+ process.env.__CFBundleIdentifier === 'com.byteocean.trae' ||
123
+ process.env.COCO_IDE_PLUGIN_TYPE === 'Trae' ||
124
+ (process.env.npm_config_user_agent && process.env.npm_config_user_agent.includes('trae'))
125
+ ) {
126
+ detected.add('trae')
127
+ }
128
+
129
+ if (
130
+ process.env.__CFBundleIdentifier === 'com.byteocean.trae.cn' ||
131
+ process.env.COCO_IDE_PLUGIN_TYPE === 'TraeCN' ||
132
+ (process.env.npm_config_user_agent && process.env.npm_config_user_agent.includes('trae-cn'))
133
+ ) {
134
+ detected.add('trae-cn')
135
+ }
136
+
137
+ if (detected.size === 0 && process.env.TERM_PROGRAM === 'vscode') {
138
+ detected.add('vscode')
139
+ }
140
+
141
+ return Array.from(detected)
142
+ }
143
+
144
+ async function detectArtifactHostIdes(cwd: string): Promise<SupportedHostIde[]> {
145
+ const artifactOrder: SupportedHostIde[] = ['cursor', 'trae', 'trae-cn', 'vscode']
146
+ const candidates = artifactOrder.map(ide => ({
147
+ ide,
148
+ target: getHostIdeArtifactPath(ide, cwd),
149
+ }))
150
+
151
+ const resolved = await Promise.all(
152
+ candidates.map(async candidate => ((await exists(candidate.target)) ? candidate.ide : null)),
153
+ )
154
+
155
+ return resolved.filter((value): value is SupportedHostIde => value !== null)
156
+ }
@@ -4,13 +4,16 @@ import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
5
  import { exists, writeFile } from '../utils/fs.js'
6
6
  import { log } from '../utils/logger.js'
7
+ import { writeCommandOutput } from '../utils/output.js'
8
+ import { runIntegrationAutomation } from './integration-automation.js'
7
9
 
8
10
  const REPO_RAW_BASE = 'https://raw.githubusercontent.com/inspecto-dev/inspecto/main'
11
+ const TOTAL_STEPS = 6
9
12
 
10
13
  type AssistantId = 'codex' | 'claude-code' | 'copilot' | 'cursor' | 'gemini' | 'trae' | 'coco'
11
14
  type ClaudeScope = 'project' | 'user'
12
- type CopilotMode = 'instructions' | 'agents'
13
- type CursorMode = 'rules' | 'agents'
15
+ type CopilotMode = 'skills' | 'instructions' | 'agents'
16
+ type CursorMode = 'skills' | 'rules' | 'agents'
14
17
 
15
18
  interface DownloadAsset {
16
19
  source: string
@@ -21,12 +24,7 @@ interface DownloadAsset {
21
24
 
22
25
  export interface IntegrationManifest {
23
26
  assistant: string
24
- type:
25
- | 'native-skill'
26
- | 'instruction-template'
27
- | 'rule-template'
28
- | 'context-template'
29
- | 'compatibility-template'
27
+ type: 'native-skill' | 'context-template'
30
28
  installTarget: string
31
29
  preferredInstall: string
32
30
  cliSupported: boolean
@@ -44,6 +42,11 @@ export interface InstallIntegrationOptions {
44
42
  scope?: ClaudeScope
45
43
  mode?: CopilotMode | CursorMode
46
44
  force?: boolean
45
+ ide?: string
46
+ inspectoVsix?: string
47
+ compact?: boolean
48
+ preview?: boolean
49
+ json?: boolean
47
50
  }
48
51
 
49
52
  interface InstallPlan {
@@ -52,54 +55,71 @@ interface InstallPlan {
52
55
  nextStep: string
53
56
  }
54
57
 
58
+ export interface IntegrationInstallResult {
59
+ status: 'launched' | 'partial' | 'blocked' | 'preview' | 'preview_blocked'
60
+ assistant: string
61
+ preview: boolean
62
+ assets: string[]
63
+ message: string
64
+ nextStep?: string
65
+ automation: Awaited<ReturnType<typeof runIntegrationAutomation>>
66
+ }
67
+
55
68
  const INTEGRATION_MANIFESTS: IntegrationManifest[] = [
56
69
  {
57
70
  assistant: 'codex',
58
71
  type: 'native-skill',
59
- installTarget: '~/.codex/skills/',
60
- preferredInstall: 'npx @inspecto-dev/cli integrations install codex',
72
+ installTarget: '.agents/skills/',
73
+ preferredInstall:
74
+ 'npx @inspecto-dev/cli integrations install codex --host-ide <vscode|cursor|trae|trae-cn>',
61
75
  cliSupported: true,
62
76
  },
63
77
  {
64
78
  assistant: 'claude-code',
65
79
  type: 'native-skill',
66
80
  installTarget: '.claude/skills/ or ~/.claude/skills/',
67
- preferredInstall: 'npx @inspecto-dev/cli integrations install claude-code --scope project',
81
+ preferredInstall:
82
+ 'npx @inspecto-dev/cli integrations install claude-code --scope project --host-ide <vscode|cursor|trae|trae-cn>',
68
83
  cliSupported: true,
69
84
  },
70
85
  {
71
86
  assistant: 'copilot',
72
- type: 'instruction-template',
73
- installTarget: '.github/copilot-instructions.md or AGENTS.md',
74
- preferredInstall: 'npx @inspecto-dev/cli integrations install copilot',
87
+ type: 'native-skill',
88
+ installTarget: '.github/skills/inspecto-onboarding/',
89
+ preferredInstall:
90
+ 'npx @inspecto-dev/cli integrations install copilot --host-ide <vscode|cursor|trae|trae-cn>',
75
91
  cliSupported: true,
76
92
  },
77
93
  {
78
94
  assistant: 'cursor',
79
- type: 'rule-template',
80
- installTarget: '.cursor/rules/inspecto-onboarding.mdc or AGENTS.md',
81
- preferredInstall: 'npx @inspecto-dev/cli integrations install cursor --mode rules',
95
+ type: 'native-skill',
96
+ installTarget: '.cursor/skills/inspecto-onboarding/',
97
+ preferredInstall:
98
+ 'npx @inspecto-dev/cli integrations install cursor --host-ide <vscode|cursor|trae|trae-cn>',
82
99
  cliSupported: true,
83
100
  },
84
101
  {
85
102
  assistant: 'gemini',
86
- type: 'context-template',
87
- installTarget: 'GEMINI.md',
88
- preferredInstall: 'npx @inspecto-dev/cli integrations install gemini',
103
+ type: 'native-skill',
104
+ installTarget: '.gemini/skills/inspecto-onboarding/',
105
+ preferredInstall:
106
+ 'npx @inspecto-dev/cli integrations install gemini --host-ide <vscode|cursor|trae|trae-cn>',
89
107
  cliSupported: true,
90
108
  },
91
109
  {
92
110
  assistant: 'trae',
93
- type: 'compatibility-template',
94
- installTarget: 'AGENTS.md',
95
- preferredInstall: 'npx @inspecto-dev/cli integrations install trae',
111
+ type: 'native-skill',
112
+ installTarget: '.trae/skills/inspecto-onboarding/',
113
+ preferredInstall:
114
+ 'npx @inspecto-dev/cli integrations install trae --host-ide <vscode|cursor|trae|trae-cn>',
96
115
  cliSupported: true,
97
116
  },
98
117
  {
99
118
  assistant: 'coco',
100
- type: 'compatibility-template',
101
- installTarget: 'AGENTS.md',
102
- preferredInstall: 'npx @inspecto-dev/cli integrations install coco',
119
+ type: 'native-skill',
120
+ installTarget: '.traecli/skills/inspecto-onboarding/',
121
+ preferredInstall:
122
+ 'npx @inspecto-dev/cli integrations install coco --host-ide <vscode|cursor|trae|trae-cn>',
103
123
  cliSupported: true,
104
124
  },
105
125
  ]
@@ -107,36 +127,173 @@ const INTEGRATION_MANIFESTS: IntegrationManifest[] = [
107
127
  export async function installIntegration(
108
128
  assistant: string,
109
129
  options: InstallIntegrationOptions = {},
110
- ): Promise<void> {
130
+ ): Promise<IntegrationInstallResult> {
111
131
  const plan = resolveInstallPlan(assistant, options)
132
+ const manifest = getIntegrationManifest(assistant)
133
+ const silent = options.json ?? false
112
134
 
113
- log.header('Inspecto Integration Install')
135
+ if (!silent) {
136
+ log.header('Inspecto Integration Install')
137
+ }
138
+
139
+ if (!options.preview) {
140
+ // Check for existing files
141
+ const existingFiles = new Map<string, string>()
142
+ for (const asset of plan.assets) {
143
+ if (await exists(asset.target)) {
144
+ if (options.force) {
145
+ // Will overwrite later
146
+ } else if (manifest.type === 'context-template') {
147
+ // Safe to append for templates and markdown files
148
+ const originalContent = await fs.readFile(asset.target, 'utf-8')
149
+ existingFiles.set(asset.target, originalContent)
150
+ if (!silent) {
151
+ log.info(`File ${asset.target} already exists. Content will be appended safely.`)
152
+ }
153
+ } else {
154
+ // Native skills (like codex, claude-code) shouldn't be blindly appended to
155
+ throw new Error(
156
+ `Refusing to overwrite existing file: ${asset.target}. Re-run with --force if you want to replace it.`,
157
+ )
158
+ }
159
+ }
160
+ }
161
+
162
+ const downloadedAssets = [] as Array<{ asset: DownloadAsset; content: string }>
163
+
164
+ for (const asset of plan.assets) {
165
+ let content = await loadAsset(asset)
166
+
167
+ // Handle appending if needed
168
+ if (existingFiles.has(asset.target)) {
169
+ const existingContent = existingFiles.get(asset.target)!
170
+ if (
171
+ !existingContent.includes('Inspecto Onboarding') &&
172
+ !existingContent.includes('inspecto-onboarding')
173
+ ) {
174
+ content = `${existingContent}\n\n---\n\n${content}`
175
+ } else {
176
+ if (!silent) {
177
+ log.info(
178
+ `Skipping ${asset.target} as it seems to already contain Inspecto rules. Use --force to overwrite.`,
179
+ )
180
+ }
181
+ continue // Skip this asset
182
+ }
183
+ }
184
+
185
+ downloadedAssets.push({ asset, content })
186
+ }
187
+
188
+ for (const { asset, content } of downloadedAssets) {
189
+ await writeFile(asset.target, content)
190
+
191
+ if (asset.executable) {
192
+ await fs.chmod(asset.target, 0o755)
193
+ }
194
+ }
195
+ }
196
+
197
+ const stepOneMessage = options.preview
198
+ ? formatIntegrationStep(1, `Previewing ${getAssistantLabel(assistant)} integration assets`)
199
+ : formatIntegrationStep(1, `Installed ${getAssistantLabel(assistant)} integration assets`)
200
+
201
+ if (!silent) {
202
+ if (options.preview) {
203
+ log.info(stepOneMessage)
204
+ } else {
205
+ log.success(stepOneMessage)
206
+ }
207
+ for (const asset of plan.assets) {
208
+ log.hint(asset.target)
209
+ }
210
+ if (!options.preview) {
211
+ log.hint(plan.nextStep)
212
+ }
213
+ }
214
+
215
+ if (shouldSkipAutomationForInstall(options)) {
216
+ const message = `Installed ${getAssistantLabel(assistant)} integration assets. User-level installs only write integration assets and do not launch onboarding automatically.`
217
+ const nextStep = options.ide
218
+ ? `Run the install command again from your target project root with --host-ide ${options.ide} when you want to launch onboarding automatically.`
219
+ : '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.'
220
+ const result: IntegrationInstallResult = {
221
+ status: 'partial',
222
+ assistant,
223
+ preview: options.preview ?? false,
224
+ assets: plan.assets.map(asset => asset.target),
225
+ message,
226
+ nextStep,
227
+ automation: {
228
+ status: 'blocked',
229
+ message,
230
+ nextStep,
231
+ },
232
+ }
233
+
234
+ if (options.json) {
235
+ return writeCommandOutput(result, true, () => {})
236
+ }
114
237
 
115
- for (const asset of plan.assets) {
116
- if ((await exists(asset.target)) && !options.force) {
117
- throw new Error(
118
- `Refusing to overwrite existing file: ${asset.target}. Re-run with --force if you want to replace it.`,
238
+ log.ready(message)
239
+ if (options.ide) {
240
+ log.hint(
241
+ `The provided --host-ide value is saved only as a rerun hint for later; this command does not open ${formatHostIdeLabel(options.ide)} for user-level installs.`,
119
242
  )
120
243
  }
244
+ log.hint(nextStep)
245
+ return result
121
246
  }
122
247
 
123
- const downloadedAssets = [] as Array<{ asset: DownloadAsset; content: string }>
248
+ const automationResult = await runIntegrationAutomation(
249
+ assistant,
250
+ { ...options, silent },
251
+ process.cwd(),
252
+ )
253
+ const result: IntegrationInstallResult = {
254
+ status: automationResult.status,
255
+ assistant,
256
+ preview: options.preview ?? false,
257
+ assets: plan.assets.map(asset => asset.target),
258
+ message: automationResult.message,
259
+ ...(automationResult.nextStep ? { nextStep: automationResult.nextStep } : {}),
260
+ automation: automationResult,
261
+ }
262
+
263
+ if (options.json) {
264
+ return writeCommandOutput(result, true, () => {})
265
+ }
124
266
 
125
- for (const asset of plan.assets) {
126
- const content = await loadAsset(asset)
127
- downloadedAssets.push({ asset, content })
267
+ if (automationResult.status === 'launched') {
268
+ log.ready(automationResult.message)
269
+ return result
128
270
  }
129
271
 
130
- for (const { asset, content } of downloadedAssets) {
131
- await writeFile(asset.target, content)
272
+ if (automationResult.status === 'preview') {
273
+ log.ready(automationResult.message)
274
+ if (automationResult.nextStep) {
275
+ log.hint(automationResult.nextStep)
276
+ }
277
+ return result
278
+ }
132
279
 
133
- if (asset.executable) {
134
- await fs.chmod(asset.target, 0o755)
280
+ if (automationResult.status === 'partial' || automationResult.status === 'preview_blocked') {
281
+ log.warn(automationResult.message)
282
+ if (automationResult.nextStep) {
283
+ log.hint(automationResult.nextStep)
135
284
  }
285
+ return result
136
286
  }
137
287
 
138
- log.success(plan.successMessage)
139
- log.hint(plan.nextStep)
288
+ log.warn(automationResult.message)
289
+ if (automationResult.nextStep) {
290
+ log.hint(automationResult.nextStep)
291
+ }
292
+ return result
293
+ }
294
+
295
+ function shouldSkipAutomationForInstall(options: InstallIntegrationOptions): boolean {
296
+ return options.scope === 'user' && !options.preview
140
297
  }
141
298
 
142
299
  export function listIntegrationManifests(): IntegrationManifest[] {
@@ -202,36 +359,36 @@ function resolveInstallPlan(assistant: string, options: InstallIntegrationOption
202
359
  return {
203
360
  assets: [
204
361
  {
205
- source: `${REPO_RAW_BASE}/assistant-integrations/gemini/GEMINI.md`,
206
- target: 'GEMINI.md',
207
- localSource: 'assistant-integrations/gemini/GEMINI.md',
362
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-gemini/SKILL.md`,
363
+ target: '.gemini/skills/inspecto-onboarding/SKILL.md',
364
+ localSource: 'skills/inspecto-onboarding-gemini/SKILL.md',
208
365
  },
209
366
  ],
210
- successMessage: 'Installed Gemini context to GEMINI.md',
211
- nextStep: 'Start a new Gemini CLI session.',
367
+ successMessage: 'Installed Gemini skill to .gemini/skills/inspecto-onboarding/SKILL.md',
368
+ nextStep: 'Start a new Gemini CLI session and use /skills list to verify.',
212
369
  }
213
370
  case 'trae':
214
371
  return {
215
372
  assets: [
216
373
  {
217
- source: `${REPO_RAW_BASE}/assistant-integrations/trae/AGENTS.md`,
218
- target: 'AGENTS.md',
219
- localSource: 'assistant-integrations/trae/AGENTS.md',
374
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-trae/SKILL.md`,
375
+ target: '.trae/skills/inspecto-onboarding/SKILL.md',
376
+ localSource: 'skills/inspecto-onboarding-trae/SKILL.md',
220
377
  },
221
378
  ],
222
- successMessage: 'Installed Trae compatibility instructions to AGENTS.md',
223
- nextStep: 'Open a new Trae chat.',
379
+ successMessage: 'Installed Trae skill to .trae/skills/inspecto-onboarding/SKILL.md',
380
+ nextStep: 'Open a new Trae chat and verify the inspecto-onboarding skill is available.',
224
381
  }
225
382
  case 'coco':
226
383
  return {
227
384
  assets: [
228
385
  {
229
- source: `${REPO_RAW_BASE}/assistant-integrations/coco/AGENTS.md`,
230
- target: 'AGENTS.md',
231
- localSource: 'assistant-integrations/coco/AGENTS.md',
386
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-trae/SKILL.md`,
387
+ target: '.traecli/skills/inspecto-onboarding/SKILL.md',
388
+ localSource: 'skills/inspecto-onboarding-trae/SKILL.md',
232
389
  },
233
390
  ],
234
- successMessage: 'Installed Coco compatibility instructions to AGENTS.md',
391
+ successMessage: 'Installed Coco skill to .traecli/skills/inspecto-onboarding/SKILL.md',
235
392
  nextStep: 'Start a new Coco session.',
236
393
  }
237
394
  default:
@@ -251,16 +408,40 @@ function getIntegrationManifest(assistant: string): IntegrationManifest {
251
408
  return manifest
252
409
  }
253
410
 
411
+ function formatIntegrationStep(step: number, text: string): string {
412
+ return `Step ${step}/${TOTAL_STEPS}: ${text}`
413
+ }
414
+
415
+ function getAssistantLabel(assistant: string): string {
416
+ if (assistant === 'claude-code') return 'Claude Code'
417
+ if (assistant === 'codex') return 'Codex'
418
+ if (assistant === 'copilot') return 'GitHub Copilot'
419
+ if (assistant === 'cursor') return 'Cursor'
420
+ if (assistant === 'gemini') return 'Gemini'
421
+ if (assistant === 'trae') return 'Trae'
422
+ if (assistant === 'coco') return 'Coco'
423
+ return assistant
424
+ }
425
+
426
+ function formatHostIdeLabel(ide: string): string {
427
+ if (ide === 'vscode') return 'VS Code'
428
+ if (ide === 'cursor') return 'Cursor'
429
+ if (ide === 'trae') return 'Trae'
430
+ if (ide === 'trae-cn') return 'Trae CN'
431
+ return ide
432
+ }
433
+
254
434
  function resolveCodexPlan(options: InstallIntegrationOptions): InstallPlan {
255
- if (options.scope !== undefined) {
256
- throw new Error('`--scope` is not supported for codex.')
257
- }
435
+ const scope = options.scope ?? 'project'
258
436
 
259
437
  if (options.mode !== undefined) {
260
438
  throw new Error('`--mode` is not supported for codex.')
261
439
  }
262
440
 
263
- const baseDir = path.join(homedir(), '.codex/skills/inspecto-onboarding-codex')
441
+ const baseDir =
442
+ scope === 'user'
443
+ ? path.join(homedir(), '.agents/skills/inspecto-onboarding-codex')
444
+ : '.agents/skills/inspecto-onboarding-codex'
264
445
 
265
446
  return {
266
447
  assets: [
@@ -330,37 +511,27 @@ function resolveClaudeCodePlan(options: InstallIntegrationOptions): InstallPlan
330
511
  }
331
512
 
332
513
  function resolveCopilotPlan(options: InstallIntegrationOptions): InstallPlan {
333
- const mode = options.mode ?? 'instructions'
514
+ const mode = options.mode ?? 'skills'
334
515
 
335
516
  if (options.scope !== undefined) {
336
517
  throw new Error(
337
- '`--scope` is not supported for copilot. Use `--mode instructions|agents` instead.',
518
+ '`--scope` is not supported for copilot. Use `--mode skills|instructions|agents` instead.',
338
519
  )
339
520
  }
340
521
 
341
522
  switch (mode) {
342
- case 'instructions':
343
- return {
344
- assets: [
345
- {
346
- source: `${REPO_RAW_BASE}/assistant-integrations/copilot/.github/copilot-instructions.md`,
347
- target: '.github/copilot-instructions.md',
348
- localSource: 'assistant-integrations/copilot/.github/copilot-instructions.md',
349
- },
350
- ],
351
- successMessage: 'Installed Copilot instructions to .github/copilot-instructions.md',
352
- nextStep: 'Open a new Copilot chat or agent session.',
353
- }
354
- case 'agents':
523
+ case 'skills':
524
+ case 'instructions': // Legacy fallback gracefully acts as skills mode now
525
+ case 'agents': // Legacy fallback gracefully acts as skills mode now
355
526
  return {
356
527
  assets: [
357
528
  {
358
- source: `${REPO_RAW_BASE}/assistant-integrations/copilot/AGENTS.md`,
359
- target: 'AGENTS.md',
360
- localSource: 'assistant-integrations/copilot/AGENTS.md',
529
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-copilot/SKILL.md`,
530
+ target: '.github/skills/inspecto-onboarding/SKILL.md',
531
+ localSource: 'skills/inspecto-onboarding-copilot/SKILL.md',
361
532
  },
362
533
  ],
363
- successMessage: 'Installed Copilot compatibility instructions to AGENTS.md',
534
+ successMessage: 'Installed Copilot skill to .github/skills/inspecto-onboarding/SKILL.md',
364
535
  nextStep: 'Open a new Copilot chat or agent session.',
365
536
  }
366
537
  default:
@@ -369,35 +540,25 @@ function resolveCopilotPlan(options: InstallIntegrationOptions): InstallPlan {
369
540
  }
370
541
 
371
542
  function resolveCursorPlan(options: InstallIntegrationOptions): InstallPlan {
372
- const mode = options.mode ?? 'rules'
543
+ const mode = options.mode ?? 'skills'
373
544
 
374
545
  if (options.scope !== undefined) {
375
- throw new Error('`--scope` is not supported for cursor. Use `--mode rules|agents` instead.')
546
+ throw new Error('`--scope` is not supported for cursor. Use `--mode skills|agents` instead.')
376
547
  }
377
548
 
378
549
  switch (mode) {
379
- case 'rules':
380
- return {
381
- assets: [
382
- {
383
- source: `${REPO_RAW_BASE}/assistant-integrations/cursor/.cursor/rules/inspecto-onboarding.mdc`,
384
- target: '.cursor/rules/inspecto-onboarding.mdc',
385
- localSource: 'assistant-integrations/cursor/.cursor/rules/inspecto-onboarding.mdc',
386
- },
387
- ],
388
- successMessage: 'Installed Cursor rule to .cursor/rules/inspecto-onboarding.mdc',
389
- nextStep: 'Open a new Cursor chat.',
390
- }
391
- case 'agents':
550
+ case 'skills':
551
+ case 'rules': // Legacy fallback gracefully acts as skills mode now
552
+ case 'agents': // Legacy fallback gracefully acts as skills mode now
392
553
  return {
393
554
  assets: [
394
555
  {
395
- source: `${REPO_RAW_BASE}/assistant-integrations/cursor/AGENTS.md`,
396
- target: 'AGENTS.md',
397
- localSource: 'assistant-integrations/cursor/AGENTS.md',
556
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-cursor/SKILL.md`,
557
+ target: '.cursor/skills/inspecto-onboarding/SKILL.md',
558
+ localSource: 'skills/inspecto-onboarding-cursor/SKILL.md',
398
559
  },
399
560
  ],
400
- successMessage: 'Installed Cursor compatibility instructions to AGENTS.md',
561
+ successMessage: 'Installed Cursor skill to .cursor/skills/inspecto-onboarding/SKILL.md',
401
562
  nextStep: 'Open a new Cursor chat.',
402
563
  }
403
564
  default:
@@ -441,9 +602,9 @@ async function downloadAsset(source: string): Promise<string> {
441
602
  response = await fetch(source)
442
603
  } catch (error) {
443
604
  const reason = error instanceof Error ? error.message : String(error)
444
- throw new Error(`Failed to download ${source}: ${reason}`, {
445
- cause: error,
446
- })
605
+ const wrappedError = new Error(`Failed to download ${source}: ${reason}`)
606
+ ;(wrappedError as Error & { cause?: unknown }).cause = error
607
+ throw wrappedError
447
608
  }
448
609
 
449
610
  if (!response.ok) {