@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.
- package/.turbo/turbo-build.log +7 -7
- package/.turbo/turbo-test.log +2653 -92
- package/CHANGELOG.md +12 -0
- package/README.md +126 -6
- package/dist/bin.js +59 -361
- package/dist/{chunk-PSYZB5GI.js → chunk-LJOKPCPD.js} +1389 -91
- 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/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 -101
- package/src/commands/onboard.ts +19 -0
- package/src/index.ts +1 -0
- package/src/inject/extension.ts +144 -24
- package/src/integrations/capabilities.ts +131 -0
- package/src/utils/process.ts +3 -0
- package/tests/extension-installer.test.ts +205 -0
- package/tests/install-wrapper.test.ts +45 -4
- 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 +118 -0
- package/tests/shared-capabilities.test.ts +45 -0
|
@@ -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: '
|
|
60
|
-
preferredInstall:
|
|
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:
|
|
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: '
|
|
73
|
-
installTarget: '.github/
|
|
74
|
-
preferredInstall:
|
|
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: '
|
|
80
|
-
installTarget: '.cursor/
|
|
81
|
-
preferredInstall:
|
|
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: '
|
|
87
|
-
installTarget: '
|
|
88
|
-
preferredInstall:
|
|
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: '
|
|
94
|
-
installTarget: '
|
|
95
|
-
preferredInstall:
|
|
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: '
|
|
101
|
-
installTarget: '
|
|
102
|
-
preferredInstall:
|
|
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<
|
|
130
|
+
): Promise<IntegrationInstallResult> {
|
|
111
131
|
const plan = resolveInstallPlan(assistant, options)
|
|
132
|
+
const manifest = getIntegrationManifest(assistant)
|
|
133
|
+
const silent = options.json ?? false
|
|
112
134
|
|
|
113
|
-
|
|
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
|
-
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
`
|
|
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
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
267
|
+
if (automationResult.status === 'launched') {
|
|
268
|
+
log.ready(automationResult.message)
|
|
269
|
+
return result
|
|
128
270
|
}
|
|
129
271
|
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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.
|
|
139
|
-
|
|
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}/
|
|
206
|
-
target: '
|
|
207
|
-
localSource: '
|
|
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
|
|
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}/
|
|
218
|
-
target: '
|
|
219
|
-
localSource: '
|
|
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
|
|
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}/
|
|
230
|
-
target: '
|
|
231
|
-
localSource: '
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 ?? '
|
|
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 '
|
|
343
|
-
|
|
344
|
-
|
|
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}/
|
|
359
|
-
target: '
|
|
360
|
-
localSource: '
|
|
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
|
|
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 ?? '
|
|
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
|
|
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 '
|
|
380
|
-
|
|
381
|
-
|
|
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}/
|
|
396
|
-
target: '
|
|
397
|
-
localSource: '
|
|
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
|
|
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
|
-
|
|
445
|
-
|
|
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) {
|