@inspecto-dev/cli 0.3.7 → 0.3.9
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 +43 -42
- package/CHANGELOG.md +16 -0
- package/dist/bin.js +12 -1
- package/dist/{chunk-LLQA5L7E.js → chunk-T46P6RD7.js} +450 -74
- package/dist/index.d.ts +51 -2
- package/dist/index.js +9 -1
- package/package.json +5 -3
- package/src/bin.ts +20 -0
- package/src/commands/init.ts +7 -3
- package/src/commands/integration-automation.ts +4 -2
- package/src/commands/integration-host-ide.ts +10 -3
- package/src/commands/integration-install.ts +40 -5
- package/src/commands/mcp.ts +386 -0
- package/src/commands/onboard.ts +5 -1
- package/src/detect/framework.ts +9 -6
- package/src/detect/ide.ts +12 -10
- package/src/detect/provider.ts +1 -1
- package/src/index.ts +6 -0
- package/src/inject/strategies/esbuild.ts +2 -2
- package/src/inject/strategies/rollup.ts +2 -2
- package/src/inject/strategies/rsbuild.ts +2 -2
- package/src/inject/strategies/rspack.ts +2 -2
- package/src/inject/strategies/vite.ts +2 -2
- package/src/inject/strategies/webpack.ts +2 -2
- package/src/instructions.ts +1 -1
- package/src/onboarding/session.ts +40 -3
- package/src/onboarding/umi-guidance.ts +1 -1
- package/src/prompts.ts +19 -9
- package/src/types.ts +10 -0
- package/tests/detect.test.ts +2 -2
- package/tests/framework.test.ts +27 -3
- package/tests/integration-install.test.ts +16 -0
- package/tests/mcp.test.ts +197 -0
- package/tests/onboard.test.ts +15 -0
- package/tests/plan.test.ts +16 -16
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import crypto from 'node:crypto'
|
|
5
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
6
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
7
|
+
import { INSPECTO_API_PATHS } from '@inspecto-dev/types'
|
|
8
|
+
import { z } from 'zod'
|
|
9
|
+
|
|
10
|
+
const DEFAULT_MCP_SERVER_VERSION = '0.0.0'
|
|
11
|
+
|
|
12
|
+
export interface McpCommandOptions {
|
|
13
|
+
serverUrl?: string
|
|
14
|
+
version?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface McpToolResult<T extends Record<string, unknown>> {
|
|
18
|
+
[key: string]: unknown
|
|
19
|
+
content: Array<{ type: 'text'; text: string }>
|
|
20
|
+
structuredContent: T
|
|
21
|
+
isError?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface InspectoMcpRuntime {
|
|
25
|
+
getSession(args: { sessionId: string }): Promise<Record<string, unknown>>
|
|
26
|
+
claimNext(args?: { timeoutMs?: number }): Promise<{
|
|
27
|
+
success: boolean
|
|
28
|
+
timedOut: boolean
|
|
29
|
+
matchedExisting: boolean
|
|
30
|
+
session?: Record<string, unknown>
|
|
31
|
+
event?: string
|
|
32
|
+
}>
|
|
33
|
+
reply(args: { sessionId: string; text: string }): Promise<Record<string, unknown>>
|
|
34
|
+
resolve(args: { sessionId: string; message?: string }): Promise<Record<string, unknown>>
|
|
35
|
+
dismiss(args: { sessionId: string; message?: string }): Promise<Record<string, unknown>>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface InspectoMcpToolDefinition {
|
|
39
|
+
name:
|
|
40
|
+
| 'inspecto_get_session'
|
|
41
|
+
| 'inspecto_claim_next'
|
|
42
|
+
| 'inspecto_reply'
|
|
43
|
+
| 'inspecto_resolve'
|
|
44
|
+
| 'inspecto_dismiss'
|
|
45
|
+
description: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const INSPECTO_MCP_TOOLS: InspectoMcpToolDefinition[] = [
|
|
49
|
+
{
|
|
50
|
+
name: 'inspecto_get_session',
|
|
51
|
+
description: 'Return one Inspecto annotation session by sessionId.',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'inspecto_claim_next',
|
|
55
|
+
description:
|
|
56
|
+
'Wait for the next unclaimed Inspecto annotation session, mark it acknowledged, and return full context.',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'inspecto_reply',
|
|
60
|
+
description: 'Append an agent reply to an Inspecto annotation session.',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'inspecto_resolve',
|
|
64
|
+
description: 'Resolve an Inspecto annotation session with an optional final message.',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'inspecto_dismiss',
|
|
68
|
+
description: 'Dismiss an Inspecto annotation session with an optional final message.',
|
|
69
|
+
},
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
export async function startMcpServer(options: McpCommandOptions = {}): Promise<void> {
|
|
73
|
+
const baseUrl = options.serverUrl ?? resolveInspectoServerBaseUrl(process.cwd())
|
|
74
|
+
if (!baseUrl) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
'Could not find a running Inspecto dev server. Start your local dev server or pass --server-url <url>.',
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const server = createInspectoMcpServer({
|
|
81
|
+
baseUrl,
|
|
82
|
+
...(options.version ? { version: options.version } : {}),
|
|
83
|
+
})
|
|
84
|
+
const transport = new StdioServerTransport()
|
|
85
|
+
await server.connect(transport)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function createInspectoMcpServer(options: { baseUrl: string; version?: string }): McpServer {
|
|
89
|
+
const runtime = createInspectoMcpRuntime(options.baseUrl)
|
|
90
|
+
const server = new McpServer({
|
|
91
|
+
name: 'inspecto-mcp',
|
|
92
|
+
version: options.version ?? DEFAULT_MCP_SERVER_VERSION,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
server.registerTool(
|
|
96
|
+
'inspecto_get_session',
|
|
97
|
+
{
|
|
98
|
+
description: getToolDescription('inspecto_get_session'),
|
|
99
|
+
inputSchema: {
|
|
100
|
+
sessionId: z.string().min(1),
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
async ({ sessionId }) => {
|
|
104
|
+
try {
|
|
105
|
+
const result = await runtime.getSession({ sessionId })
|
|
106
|
+
return toolSuccess(result)
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return toolError(error)
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
server.registerTool(
|
|
114
|
+
'inspecto_claim_next',
|
|
115
|
+
{
|
|
116
|
+
description: getToolDescription('inspecto_claim_next'),
|
|
117
|
+
inputSchema: {
|
|
118
|
+
timeoutMs: z.number().int().nonnegative().optional(),
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
async ({ timeoutMs }) => {
|
|
122
|
+
try {
|
|
123
|
+
const result = await runtime.claimNext({
|
|
124
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
125
|
+
})
|
|
126
|
+
return toolSuccess(result)
|
|
127
|
+
} catch (error) {
|
|
128
|
+
return toolError(error)
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
server.registerTool(
|
|
134
|
+
'inspecto_reply',
|
|
135
|
+
{
|
|
136
|
+
description: getToolDescription('inspecto_reply'),
|
|
137
|
+
inputSchema: {
|
|
138
|
+
sessionId: z.string().min(1),
|
|
139
|
+
text: z.string().min(1),
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
async ({ sessionId, text }) => {
|
|
143
|
+
try {
|
|
144
|
+
const result = await runtime.reply({ sessionId, text })
|
|
145
|
+
return toolSuccess(result)
|
|
146
|
+
} catch (error) {
|
|
147
|
+
return toolError(error)
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
server.registerTool(
|
|
153
|
+
'inspecto_resolve',
|
|
154
|
+
{
|
|
155
|
+
description: getToolDescription('inspecto_resolve'),
|
|
156
|
+
inputSchema: {
|
|
157
|
+
sessionId: z.string().min(1),
|
|
158
|
+
message: z.string().optional(),
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
async ({ sessionId, message }) => {
|
|
162
|
+
try {
|
|
163
|
+
const result = await runtime.resolve({ sessionId, ...(message ? { message } : {}) })
|
|
164
|
+
return toolSuccess(result)
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return toolError(error)
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
server.registerTool(
|
|
172
|
+
'inspecto_dismiss',
|
|
173
|
+
{
|
|
174
|
+
description: getToolDescription('inspecto_dismiss'),
|
|
175
|
+
inputSchema: {
|
|
176
|
+
sessionId: z.string().min(1),
|
|
177
|
+
message: z.string().optional(),
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
async ({ sessionId, message }) => {
|
|
181
|
+
try {
|
|
182
|
+
const result = await runtime.dismiss({ sessionId, ...(message ? { message } : {}) })
|
|
183
|
+
return toolSuccess(result)
|
|
184
|
+
} catch (error) {
|
|
185
|
+
return toolError(error)
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
return server
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function getToolDescription(name: InspectoMcpToolDefinition['name']): string {
|
|
194
|
+
return INSPECTO_MCP_TOOLS.find(tool => tool.name === name)?.description ?? name
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function createInspectoMcpRuntime(baseUrl: string): InspectoMcpRuntime {
|
|
198
|
+
return {
|
|
199
|
+
async getSession(args) {
|
|
200
|
+
return getSession(baseUrl, args.sessionId)
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
async claimNext(args = {}) {
|
|
204
|
+
return (await postJson(`${baseUrl}${INSPECTO_API_PATHS.SESSION_CLAIM_NEXT}`, {
|
|
205
|
+
...(args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {}),
|
|
206
|
+
})) as {
|
|
207
|
+
success: boolean
|
|
208
|
+
timedOut: boolean
|
|
209
|
+
matchedExisting: boolean
|
|
210
|
+
session?: Record<string, unknown>
|
|
211
|
+
event?: string
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
async reply(args) {
|
|
216
|
+
return postJson(
|
|
217
|
+
`${baseUrl}${INSPECTO_API_PATHS.SESSIONS}/${args.sessionId}${INSPECTO_API_PATHS.SESSION_REPLY_SUFFIX}`,
|
|
218
|
+
{
|
|
219
|
+
role: 'agent',
|
|
220
|
+
text: args.text.trim(),
|
|
221
|
+
},
|
|
222
|
+
) as Promise<Record<string, unknown>>
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
async resolve(args) {
|
|
226
|
+
return postJson(
|
|
227
|
+
`${baseUrl}${INSPECTO_API_PATHS.SESSIONS}/${args.sessionId}${INSPECTO_API_PATHS.SESSION_RESOLVE_SUFFIX}`,
|
|
228
|
+
args.message?.trim() ? { message: args.message.trim() } : {},
|
|
229
|
+
) as Promise<Record<string, unknown>>
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
async dismiss(args) {
|
|
233
|
+
return postJson(
|
|
234
|
+
`${baseUrl}${INSPECTO_API_PATHS.SESSIONS}/${args.sessionId}${INSPECTO_API_PATHS.SESSION_DISMISS_SUFFIX}`,
|
|
235
|
+
args.message?.trim() ? { message: args.message.trim() } : {},
|
|
236
|
+
) as Promise<Record<string, unknown>>
|
|
237
|
+
},
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function resolveInspectoServerBaseUrl(cwd: string): string | null {
|
|
242
|
+
const ports = resolveServerPorts(cwd)
|
|
243
|
+
const port = ports[0]
|
|
244
|
+
return port ? `http://0.0.0.0:${port}` : null
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function resolveServerPorts(cwd: string): number[] {
|
|
248
|
+
const prioritized = readProjectScopedPorts(cwd)
|
|
249
|
+
if (prioritized.length > 0) return prioritized
|
|
250
|
+
|
|
251
|
+
const legacyPortFile = path.join(os.tmpdir(), 'inspecto.port')
|
|
252
|
+
try {
|
|
253
|
+
const raw = fs.readFileSync(legacyPortFile, 'utf-8').trim()
|
|
254
|
+
const port = parseInt(raw, 10)
|
|
255
|
+
if (Number.isInteger(port) && port > 0 && port < 65536) {
|
|
256
|
+
return [port]
|
|
257
|
+
}
|
|
258
|
+
} catch {
|
|
259
|
+
// ignore
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return Array.from({ length: 23 }, (_, index) => 5678 + index)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function toolSuccess<T extends Record<string, unknown>>(value: T): McpToolResult<T> {
|
|
266
|
+
return {
|
|
267
|
+
content: [
|
|
268
|
+
{
|
|
269
|
+
type: 'text',
|
|
270
|
+
text: JSON.stringify(value, null, 2),
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
structuredContent: value,
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function toolError(error: unknown): McpToolResult<{ success: false; error: string }> {
|
|
278
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
279
|
+
return {
|
|
280
|
+
content: [
|
|
281
|
+
{
|
|
282
|
+
type: 'text',
|
|
283
|
+
text: message,
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
structuredContent: {
|
|
287
|
+
success: false,
|
|
288
|
+
error: message,
|
|
289
|
+
},
|
|
290
|
+
isError: true,
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function getJson(url: string): Promise<unknown> {
|
|
295
|
+
const response = await fetch(url)
|
|
296
|
+
const payload = (await response.json().catch(() => ({}))) as Record<string, unknown>
|
|
297
|
+
if (!response.ok) {
|
|
298
|
+
throw new Error(String(payload['error'] ?? `Request failed with status ${response.status}.`))
|
|
299
|
+
}
|
|
300
|
+
return payload
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function getSession(baseUrl: string, sessionId: string): Promise<Record<string, unknown>> {
|
|
304
|
+
const trimmed = sessionId.trim()
|
|
305
|
+
if (!trimmed) {
|
|
306
|
+
throw new Error('Session id is required.')
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const payload = (await getJson(
|
|
310
|
+
`${baseUrl}${INSPECTO_API_PATHS.SESSIONS}/${encodeURIComponent(trimmed)}`,
|
|
311
|
+
)) as {
|
|
312
|
+
success?: boolean
|
|
313
|
+
session?: Record<string, unknown>
|
|
314
|
+
error?: string
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (!payload.success || !payload.session) {
|
|
318
|
+
throw new Error(payload.error ?? 'Session not found.')
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
success: true,
|
|
323
|
+
session: payload.session,
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function postJson(url: string, body: Record<string, unknown>): Promise<unknown> {
|
|
328
|
+
const response = await fetch(url, {
|
|
329
|
+
method: 'POST',
|
|
330
|
+
headers: { 'Content-Type': 'application/json' },
|
|
331
|
+
body: JSON.stringify(body),
|
|
332
|
+
})
|
|
333
|
+
const payload = (await response.json().catch(() => ({}))) as Record<string, unknown>
|
|
334
|
+
if (!response.ok || payload['success'] === false) {
|
|
335
|
+
throw new Error(String(payload['error'] ?? `Request failed with status ${response.status}.`))
|
|
336
|
+
}
|
|
337
|
+
return payload
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function readProjectScopedPorts(cwd: string): number[] {
|
|
341
|
+
const portFile = path.join(os.tmpdir(), 'inspecto.port.json')
|
|
342
|
+
try {
|
|
343
|
+
const raw = fs.readFileSync(portFile, 'utf-8').trim()
|
|
344
|
+
const portData = JSON.parse(raw) as Record<string, number>
|
|
345
|
+
const currentRootHashes = resolveCandidateRootHashes(cwd)
|
|
346
|
+
|
|
347
|
+
const prioritized: number[] = []
|
|
348
|
+
const seen = new Set<number>()
|
|
349
|
+
|
|
350
|
+
for (const rootHash of currentRootHashes) {
|
|
351
|
+
const currentPort = portData[rootHash]
|
|
352
|
+
if (currentPort && !seen.has(currentPort)) {
|
|
353
|
+
prioritized.push(currentPort)
|
|
354
|
+
seen.add(currentPort)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
for (const port of Object.values(portData)) {
|
|
359
|
+
if (!seen.has(port)) {
|
|
360
|
+
prioritized.push(port)
|
|
361
|
+
seen.add(port)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return prioritized
|
|
366
|
+
} catch {
|
|
367
|
+
return []
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function resolveCandidateRootHashes(cwd: string): string[] {
|
|
372
|
+
const normalized = path.resolve(cwd)
|
|
373
|
+
const candidates = new Set<string>()
|
|
374
|
+
let currentDir = normalized
|
|
375
|
+
|
|
376
|
+
while (true) {
|
|
377
|
+
candidates.add(crypto.createHash('md5').update(currentDir).digest('hex'))
|
|
378
|
+
const parentDir = path.dirname(currentDir)
|
|
379
|
+
if (parentDir === currentDir) {
|
|
380
|
+
break
|
|
381
|
+
}
|
|
382
|
+
currentDir = parentDir
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return [...candidates]
|
|
386
|
+
}
|
package/src/commands/onboard.ts
CHANGED
|
@@ -57,6 +57,7 @@ function buildAssistantHandoff(
|
|
|
57
57
|
...(result.pendingSteps ? { pendingSteps: result.pendingSteps } : {}),
|
|
58
58
|
...(result.assistantPrompt ? { assistantPrompt: result.assistantPrompt } : {}),
|
|
59
59
|
...(result.patches ? { patches: result.patches } : {}),
|
|
60
|
+
...(result.dailyUsage ? { dailyUsage: result.dailyUsage } : {}),
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -110,6 +111,9 @@ function printOnboardResult(result: OnboardCommandResult): void {
|
|
|
110
111
|
if (normalized.handoff?.assistantPrompt) {
|
|
111
112
|
log.hint(normalized.handoff.assistantPrompt)
|
|
112
113
|
}
|
|
114
|
+
if (normalized.handoff?.dailyUsage?.prompt) {
|
|
115
|
+
log.hint(normalized.handoff.dailyUsage.prompt)
|
|
116
|
+
}
|
|
113
117
|
if (normalized.confirmation.required && normalized.confirmation.question) {
|
|
114
118
|
log.warn(normalized.confirmation.question)
|
|
115
119
|
}
|
|
@@ -139,7 +143,7 @@ export async function onboard(options: OnboardCommandOptions = {}): Promise<Onbo
|
|
|
139
143
|
session.status === 'needs_confirmation'
|
|
140
144
|
) {
|
|
141
145
|
return writeCommandOutput(
|
|
142
|
-
normalizeOnboardResult(buildDeferredOnboardResult(session)),
|
|
146
|
+
normalizeOnboardResult(await buildDeferredOnboardResult(session)),
|
|
143
147
|
options.json ?? false,
|
|
144
148
|
printOnboardResult,
|
|
145
149
|
)
|
package/src/detect/framework.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// ============================================================
|
|
2
2
|
// src/detect/framework.ts — Frontend framework detection
|
|
3
3
|
//
|
|
4
|
-
// v1 supported: React / Vue
|
|
5
|
-
// Recognized but unsupported:
|
|
4
|
+
// v1 supported: React / Vue / Svelte / Solid / Astro
|
|
5
|
+
// Recognized but unsupported: Angular, Preact, Lit
|
|
6
6
|
// ============================================================
|
|
7
7
|
import path from 'node:path'
|
|
8
8
|
import { createRequire } from 'node:module'
|
|
9
9
|
import { readJSON } from '../utils/fs.js'
|
|
10
10
|
|
|
11
|
-
export type Framework = 'react' | 'vue'
|
|
11
|
+
export type Framework = 'react' | 'vue' | 'svelte' | 'solid' | 'astro'
|
|
12
12
|
|
|
13
13
|
export interface FrameworkDetection {
|
|
14
14
|
supported: Framework[]
|
|
@@ -25,11 +25,14 @@ interface PackageJSON {
|
|
|
25
25
|
const META_FRAMEWORK_MAP: Record<string, Framework> = {
|
|
26
26
|
next: 'react',
|
|
27
27
|
nuxt: 'vue',
|
|
28
|
+
'@sveltejs/kit': 'svelte',
|
|
28
29
|
'@remix-run/react': 'react',
|
|
29
30
|
'@remix-run/dev': 'react',
|
|
30
31
|
'@vue/nuxt': 'vue',
|
|
31
32
|
'vite-plugin-vue': 'vue',
|
|
32
33
|
'@vitejs/plugin-vue': 'vue',
|
|
34
|
+
'@sveltejs/vite-plugin-svelte': 'svelte',
|
|
35
|
+
'vite-plugin-solid': 'solid',
|
|
33
36
|
'@vitejs/plugin-react': 'react',
|
|
34
37
|
'@vitejs/plugin-react-swc': 'react',
|
|
35
38
|
}
|
|
@@ -38,13 +41,13 @@ const META_FRAMEWORK_MAP: Record<string, Framework> = {
|
|
|
38
41
|
const SUPPORTED_FRAMEWORKS: { framework: Framework; deps: string[] }[] = [
|
|
39
42
|
{ framework: 'react', deps: ['react', 'react-dom'] },
|
|
40
43
|
{ framework: 'vue', deps: ['vue'] },
|
|
44
|
+
{ framework: 'svelte', deps: ['svelte'] },
|
|
45
|
+
{ framework: 'solid', deps: ['solid-js'] },
|
|
46
|
+
{ framework: 'astro', deps: ['astro'] },
|
|
41
47
|
]
|
|
42
48
|
|
|
43
49
|
/** Recognized but not supported in v1 — detect and warn */
|
|
44
50
|
const UNSUPPORTED_FRAMEWORKS: { name: string; dep: string }[] = [
|
|
45
|
-
{ name: 'Solid', dep: 'solid-js' },
|
|
46
|
-
{ name: 'Svelte', dep: 'svelte' },
|
|
47
|
-
{ name: 'SvelteKit', dep: '@sveltejs/kit' },
|
|
48
51
|
{ name: 'Angular', dep: '@angular/core' },
|
|
49
52
|
{ name: 'Preact', dep: 'preact' },
|
|
50
53
|
{ name: 'Lit', dep: 'lit' },
|
package/src/detect/ide.ts
CHANGED
|
@@ -51,7 +51,8 @@ export async function detectIDE(root: string): Promise<IDEProbeResult> {
|
|
|
51
51
|
if (
|
|
52
52
|
process.env.__CFBundleIdentifier === 'ai.codebuddy.mac.cn' ||
|
|
53
53
|
process.env.COCO_IDE_PLUGIN_TYPE === 'CodeBuddyCN' ||
|
|
54
|
-
(process.env.npm_config_user_agent &&
|
|
54
|
+
(process.env.npm_config_user_agent &&
|
|
55
|
+
process.env.npm_config_user_agent.includes('codebuddy-cn'))
|
|
55
56
|
) {
|
|
56
57
|
detected.set('CodeBuddy CN', { ide: 'codebuddy-cn', supported: true })
|
|
57
58
|
} else if (
|
|
@@ -83,15 +84,16 @@ export async function detectIDE(root: string): Promise<IDEProbeResult> {
|
|
|
83
84
|
// if (process.env.TERM_PROGRAM === 'vscode') { ... }
|
|
84
85
|
|
|
85
86
|
// 2. Check Directory Artifacts (Indicates project has been opened in these IDEs)
|
|
86
|
-
const [hasTrae, hasTraeCn, hasCursor, hasVscode, hasIdea, hasCodeBuddy, hasCodeBuddyCn] =
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
const [hasTrae, hasTraeCn, hasCursor, hasVscode, hasIdea, hasCodeBuddy, hasCodeBuddyCn] =
|
|
88
|
+
await Promise.all([
|
|
89
|
+
exists(path.join(root, '.trae')),
|
|
90
|
+
exists(path.join(root, '.trae-cn')),
|
|
91
|
+
exists(path.join(root, '.cursor')),
|
|
92
|
+
exists(path.join(root, '.vscode')),
|
|
93
|
+
exists(path.join(root, '.idea')),
|
|
94
|
+
exists(path.join(root, '.codebuddy')),
|
|
95
|
+
exists(path.join(root, '.codebuddy-cn')),
|
|
96
|
+
])
|
|
95
97
|
|
|
96
98
|
// If a directory artifact exists, add it to the detection list.
|
|
97
99
|
// This allows us to surface multiple options (e.g. if you are in Cursor but also have a .vscode folder).
|
package/src/detect/provider.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import path from 'node:path'
|
|
7
7
|
import { exists, readJSON } from '../utils/fs.js'
|
|
8
8
|
import { which } from '../utils/exec.js'
|
|
9
|
-
import type { Provider
|
|
9
|
+
import type { Provider } from '@inspecto-dev/types'
|
|
10
10
|
|
|
11
11
|
export interface ProviderDetection {
|
|
12
12
|
id: Provider
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,12 @@ export { devLink, devStatus, devUnlink } from './commands/dev-config.js'
|
|
|
4
4
|
export { init } from './commands/init.js'
|
|
5
5
|
export { collectDoctorResult, doctor } from './commands/doctor.js'
|
|
6
6
|
export { integrationDoctor } from './commands/integration-doctor.js'
|
|
7
|
+
export {
|
|
8
|
+
startMcpServer,
|
|
9
|
+
createInspectoMcpRuntime,
|
|
10
|
+
createInspectoMcpServer,
|
|
11
|
+
resolveInspectoServerBaseUrl,
|
|
12
|
+
} from './commands/mcp.js'
|
|
7
13
|
export { onboard } from './commands/onboard.js'
|
|
8
14
|
export { plan } from './commands/plan.js'
|
|
9
15
|
export { teardown } from './commands/teardown.js'
|
|
@@ -8,11 +8,11 @@ export class EsbuildStrategy implements InjectStrategy {
|
|
|
8
8
|
return tool === 'esbuild'
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
inject(
|
|
11
|
+
inject(_options: InjectOptions): void {
|
|
12
12
|
throw new Error('Esbuild requires manual plugin configuration')
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
getManualInstructions(detection: BuildToolDetection,
|
|
15
|
+
getManualInstructions(detection: BuildToolDetection, _reason: string): string[] {
|
|
16
16
|
return [
|
|
17
17
|
`1. Update your esbuild config (${detection.configPath}):`,
|
|
18
18
|
`import { esbuildPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
@@ -8,11 +8,11 @@ export class RollupStrategy implements InjectStrategy {
|
|
|
8
8
|
return tool === 'rollup'
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
inject(
|
|
11
|
+
inject(_options: InjectOptions): void {
|
|
12
12
|
throw new Error('Rollup requires manual plugin configuration')
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
getManualInstructions(detection: BuildToolDetection,
|
|
15
|
+
getManualInstructions(detection: BuildToolDetection, _reason: string): string[] {
|
|
16
16
|
return [
|
|
17
17
|
`1. Update your rollup config (${detection.configPath}):`,
|
|
18
18
|
`import { rollupPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
@@ -8,11 +8,11 @@ export class RsbuildStrategy implements InjectStrategy {
|
|
|
8
8
|
return tool === 'rsbuild'
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
inject(
|
|
11
|
+
inject(_options: InjectOptions): void {
|
|
12
12
|
throw new Error('Rsbuild requires manual plugin configuration due to nested structure')
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
getManualInstructions(
|
|
15
|
+
getManualInstructions(_detection: BuildToolDetection, _reason: string): string[] {
|
|
16
16
|
return [
|
|
17
17
|
`import { rspackPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
18
18
|
'',
|
|
@@ -8,11 +8,11 @@ export class RspackStrategy implements InjectStrategy {
|
|
|
8
8
|
return tool === 'rspack'
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
inject(
|
|
11
|
+
inject(_options: InjectOptions): void {
|
|
12
12
|
throw new Error('Rspack requires manual plugin configuration')
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
getManualInstructions(detection: BuildToolDetection,
|
|
15
|
+
getManualInstructions(detection: BuildToolDetection, _reason: string): string[] {
|
|
16
16
|
const importPkg = detection.isLegacyRspack
|
|
17
17
|
? '@inspecto-dev/plugin/legacy/rspack'
|
|
18
18
|
: '@inspecto-dev/plugin'
|
|
@@ -9,7 +9,7 @@ export class ViteStrategy implements InjectStrategy {
|
|
|
9
9
|
return tool === 'vite'
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
inject({ mod, detection }: InjectOptions): void {
|
|
12
|
+
inject({ mod, detection: _detection }: InjectOptions): void {
|
|
13
13
|
addVitePlugin(mod, {
|
|
14
14
|
from: '@inspecto-dev/plugin',
|
|
15
15
|
constructor: 'inspecto',
|
|
@@ -17,7 +17,7 @@ export class ViteStrategy implements InjectStrategy {
|
|
|
17
17
|
})
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
getManualInstructions(
|
|
20
|
+
getManualInstructions(_detection: BuildToolDetection, _reason: string): string[] {
|
|
21
21
|
return [
|
|
22
22
|
`import { vitePlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
23
23
|
'',
|
|
@@ -8,12 +8,12 @@ export class WebpackStrategy implements InjectStrategy {
|
|
|
8
8
|
return tool === 'webpack'
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
inject(
|
|
11
|
+
inject(_options: InjectOptions): void {
|
|
12
12
|
// AST manipulation for Webpack configs (often CommonJS or complex objects) is brittle in v1
|
|
13
13
|
throw new Error('Webpack requires manual plugin configuration')
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
getManualInstructions(detection: BuildToolDetection,
|
|
16
|
+
getManualInstructions(detection: BuildToolDetection, _reason: string): string[] {
|
|
17
17
|
const importPkg = detection.isLegacyWebpack
|
|
18
18
|
? '@inspecto-dev/plugin/legacy/webpack4'
|
|
19
19
|
: '@inspecto-dev/plugin'
|
package/src/instructions.ts
CHANGED
|
@@ -83,7 +83,7 @@ export function printNextJsManualInstructions() {
|
|
|
83
83
|
' useEffect(() => {',
|
|
84
84
|
" if (process.env.NODE_ENV !== 'production') {",
|
|
85
85
|
" import('@inspecto-dev/core').then(({ mountInspector }) => {",
|
|
86
|
-
" mountInspector({ serverUrl: 'http://
|
|
86
|
+
" mountInspector({ serverUrl: 'http://0.0.0.0:5678' })",
|
|
87
87
|
' })',
|
|
88
88
|
' }',
|
|
89
89
|
' }, [])',
|