@inspecto-dev/cli 0.3.8 → 0.3.10
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 +206 -6777
- package/CHANGELOG.md +16 -0
- package/dist/bin.js +12 -1
- package/dist/{chunk-B5JDHSP7.js → chunk-WOZPOTWE.js} +425 -55
- package/dist/index.d.ts +50 -1
- 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-install.ts +27 -2
- package/src/commands/mcp.ts +386 -0
- package/src/commands/onboard.ts +5 -1
- package/src/index.ts +6 -0
- package/src/onboarding/session.ts +43 -3
- package/src/onboarding/umi-guidance.ts +1 -1
- package/src/prompts.ts +19 -9
- package/src/types.ts +10 -0
- package/tests/integration-install.test.ts +16 -0
- package/tests/mcp.test.ts +197 -0
- package/tests/onboard.test.ts +15 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
|
|
1
3
|
/** Package manager detection result */
|
|
2
4
|
type PackageManager = 'bun' | 'pnpm' | 'yarn' | 'npm';
|
|
3
5
|
/** Supported build tools (v1) */
|
|
@@ -111,6 +113,12 @@ interface OnboardingVerification {
|
|
|
111
113
|
devCommand?: string;
|
|
112
114
|
message: string;
|
|
113
115
|
}
|
|
116
|
+
interface OnboardingDailyUsageHandoff {
|
|
117
|
+
mode: 'agent';
|
|
118
|
+
skill: string;
|
|
119
|
+
prompt: string;
|
|
120
|
+
requiresMcp: boolean;
|
|
121
|
+
}
|
|
114
122
|
interface OnboardingAssistantHandoff {
|
|
115
123
|
framework?: string;
|
|
116
124
|
metaFramework?: string;
|
|
@@ -119,6 +127,7 @@ interface OnboardingAssistantHandoff {
|
|
|
119
127
|
pendingSteps?: string[];
|
|
120
128
|
assistantPrompt?: string;
|
|
121
129
|
patches?: OnboardingPatchPlan[];
|
|
130
|
+
dailyUsage?: OnboardingDailyUsageHandoff;
|
|
122
131
|
}
|
|
123
132
|
interface ResolvedOnboardingSession {
|
|
124
133
|
status: OnboardStatus;
|
|
@@ -141,6 +150,7 @@ interface ResolvedOnboardingSession {
|
|
|
141
150
|
pendingSteps?: string[];
|
|
142
151
|
assistantPrompt?: string;
|
|
143
152
|
patches?: OnboardingPatchPlan[];
|
|
153
|
+
dailyUsage?: OnboardingDailyUsageHandoff;
|
|
144
154
|
handoff?: OnboardingAssistantHandoff;
|
|
145
155
|
}
|
|
146
156
|
interface OnboardCommandResult {
|
|
@@ -159,6 +169,7 @@ interface OnboardCommandResult {
|
|
|
159
169
|
pendingSteps?: string[];
|
|
160
170
|
assistantPrompt?: string;
|
|
161
171
|
patches?: OnboardingPatchPlan[];
|
|
172
|
+
dailyUsage?: OnboardingDailyUsageHandoff;
|
|
162
173
|
handoff?: OnboardingAssistantHandoff;
|
|
163
174
|
}
|
|
164
175
|
/** Machine-readable detection output for skill-first onboarding */
|
|
@@ -383,6 +394,44 @@ interface IntegrationDoctorResult {
|
|
|
383
394
|
}
|
|
384
395
|
declare function integrationDoctor(assistant: string, options?: IntegrationDoctorOptions): Promise<IntegrationDoctorResult>;
|
|
385
396
|
|
|
397
|
+
interface McpCommandOptions {
|
|
398
|
+
serverUrl?: string;
|
|
399
|
+
version?: string;
|
|
400
|
+
}
|
|
401
|
+
interface InspectoMcpRuntime {
|
|
402
|
+
getSession(args: {
|
|
403
|
+
sessionId: string;
|
|
404
|
+
}): Promise<Record<string, unknown>>;
|
|
405
|
+
claimNext(args?: {
|
|
406
|
+
timeoutMs?: number;
|
|
407
|
+
}): Promise<{
|
|
408
|
+
success: boolean;
|
|
409
|
+
timedOut: boolean;
|
|
410
|
+
matchedExisting: boolean;
|
|
411
|
+
session?: Record<string, unknown>;
|
|
412
|
+
event?: string;
|
|
413
|
+
}>;
|
|
414
|
+
reply(args: {
|
|
415
|
+
sessionId: string;
|
|
416
|
+
text: string;
|
|
417
|
+
}): Promise<Record<string, unknown>>;
|
|
418
|
+
resolve(args: {
|
|
419
|
+
sessionId: string;
|
|
420
|
+
message?: string;
|
|
421
|
+
}): Promise<Record<string, unknown>>;
|
|
422
|
+
dismiss(args: {
|
|
423
|
+
sessionId: string;
|
|
424
|
+
message?: string;
|
|
425
|
+
}): Promise<Record<string, unknown>>;
|
|
426
|
+
}
|
|
427
|
+
declare function startMcpServer(options?: McpCommandOptions): Promise<void>;
|
|
428
|
+
declare function createInspectoMcpServer(options: {
|
|
429
|
+
baseUrl: string;
|
|
430
|
+
version?: string;
|
|
431
|
+
}): McpServer;
|
|
432
|
+
declare function createInspectoMcpRuntime(baseUrl: string): InspectoMcpRuntime;
|
|
433
|
+
declare function resolveInspectoServerBaseUrl(cwd: string): string | null;
|
|
434
|
+
|
|
386
435
|
interface OnboardCommandOptions {
|
|
387
436
|
json?: boolean;
|
|
388
437
|
target?: string;
|
|
@@ -407,4 +456,4 @@ declare function reportCommandError(error: unknown, options?: ReportCommandError
|
|
|
407
456
|
|
|
408
457
|
type Framework = 'react' | 'vue' | 'svelte' | 'solid' | 'astro';
|
|
409
458
|
|
|
410
|
-
export { type BuildTool, type DoctorDiagnostic, type DoctorResult, type Framework, type InitOptions, type InstallLock, type OnboardCommandResult, type OnboardStatus, type PackageManager, type ResolvedOnboardingSession, apply, collectDoctorResult, detect, devLink, devStatus, devUnlink, doctor, init, integrationDoctor, onboard, plan, reportCommandError, teardown, writeCommandOutput };
|
|
459
|
+
export { type BuildTool, type DoctorDiagnostic, type DoctorResult, type Framework, type InitOptions, type InstallLock, type OnboardCommandResult, type OnboardStatus, type PackageManager, type ResolvedOnboardingSession, apply, collectDoctorResult, createInspectoMcpRuntime, createInspectoMcpServer, detect, devLink, devStatus, devUnlink, doctor, init, integrationDoctor, onboard, plan, reportCommandError, resolveInspectoServerBaseUrl, startMcpServer, teardown, writeCommandOutput };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
apply,
|
|
3
3
|
collectDoctorResult,
|
|
4
|
+
createInspectoMcpRuntime,
|
|
5
|
+
createInspectoMcpServer,
|
|
4
6
|
detect,
|
|
5
7
|
devLink,
|
|
6
8
|
devStatus,
|
|
@@ -11,12 +13,16 @@ import {
|
|
|
11
13
|
onboard,
|
|
12
14
|
plan,
|
|
13
15
|
reportCommandError,
|
|
16
|
+
resolveInspectoServerBaseUrl,
|
|
17
|
+
startMcpServer,
|
|
14
18
|
teardown,
|
|
15
19
|
writeCommandOutput
|
|
16
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-WOZPOTWE.js";
|
|
17
21
|
export {
|
|
18
22
|
apply,
|
|
19
23
|
collectDoctorResult,
|
|
24
|
+
createInspectoMcpRuntime,
|
|
25
|
+
createInspectoMcpServer,
|
|
20
26
|
detect,
|
|
21
27
|
devLink,
|
|
22
28
|
devStatus,
|
|
@@ -27,6 +33,8 @@ export {
|
|
|
27
33
|
onboard,
|
|
28
34
|
plan,
|
|
29
35
|
reportCommandError,
|
|
36
|
+
resolveInspectoServerBaseUrl,
|
|
37
|
+
startMcpServer,
|
|
30
38
|
teardown,
|
|
31
39
|
writeCommandOutput
|
|
32
40
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inspecto-dev/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"description": "CLI tools for Inspecto onboarding and lifecycle management",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"inspecto",
|
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
"inspecto": "./bin/inspecto.js"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
18
19
|
"cac": "^6.7.14",
|
|
19
20
|
"magicast": "^0.5.2",
|
|
20
|
-
"ora": "^9.
|
|
21
|
+
"ora": "^9.4.0",
|
|
21
22
|
"picocolors": "^1.0.0",
|
|
22
23
|
"prompts": "^2.4.2",
|
|
23
|
-
"
|
|
24
|
+
"zod": "^4.1.12",
|
|
25
|
+
"@inspecto-dev/types": "0.3.10"
|
|
24
26
|
},
|
|
25
27
|
"devDependencies": {
|
|
26
28
|
"@types/node": "^20.19.39",
|
package/src/bin.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { detect } from './commands/detect.js'
|
|
|
11
11
|
import { devLink, devStatus, devUnlink } from './commands/dev-config.js'
|
|
12
12
|
import { init } from './commands/init.js'
|
|
13
13
|
import { doctor } from './commands/doctor.js'
|
|
14
|
+
import { startMcpServer } from './commands/mcp.js'
|
|
14
15
|
import { onboard } from './commands/onboard.js'
|
|
15
16
|
import { plan } from './commands/plan.js'
|
|
16
17
|
import { teardown } from './commands/teardown.js'
|
|
@@ -75,6 +76,10 @@ interface DevCommandOptions extends JsonCommandOptions {
|
|
|
75
76
|
repo?: string
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
interface McpCommandOptions extends GlobalOptions {
|
|
80
|
+
serverUrl?: string
|
|
81
|
+
}
|
|
82
|
+
|
|
78
83
|
const integrationScopes = ['project', 'user'] as const
|
|
79
84
|
const integrationModes = ['skills', 'instructions', 'agents', 'rules'] as const
|
|
80
85
|
|
|
@@ -128,6 +133,21 @@ export function createCli(_argv: readonly string[] = process.argv): CAC {
|
|
|
128
133
|
}
|
|
129
134
|
})
|
|
130
135
|
|
|
136
|
+
cli
|
|
137
|
+
.command('mcp', 'Run Inspecto as a minimal stdio MCP server for agents')
|
|
138
|
+
.option('--server-url <url>', 'Use an explicit Inspecto dev server base URL')
|
|
139
|
+
.option('--debug', 'Enable debug mode to show full error traces', { default: false })
|
|
140
|
+
.action(async (options: McpCommandOptions) => {
|
|
141
|
+
try {
|
|
142
|
+
await startMcpServer({
|
|
143
|
+
...(options.serverUrl ? { serverUrl: options.serverUrl } : {}),
|
|
144
|
+
version,
|
|
145
|
+
})
|
|
146
|
+
} catch (error) {
|
|
147
|
+
exitWithError(error, options)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
131
151
|
cli
|
|
132
152
|
.command('init', 'Set up Inspecto in your project')
|
|
133
153
|
.option('--shared', 'Share .inspecto/settings.json with your team via Git', { default: false })
|
package/src/commands/init.ts
CHANGED
|
@@ -202,11 +202,15 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
// IDE detection
|
|
205
|
-
let selectedIDE: { ide: string; supported: boolean } | null
|
|
205
|
+
let selectedIDE: { ide: string; supported: boolean } | null
|
|
206
206
|
|
|
207
207
|
if (ideProbe.detected.length === 0) {
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
if (process.stdin.isTTY) {
|
|
209
|
+
log.warn('No IDE detected in current project')
|
|
210
|
+
selectedIDE = await promptIDEChoice([])
|
|
211
|
+
} else {
|
|
212
|
+
selectedIDE = { ide: 'none', supported: true }
|
|
213
|
+
}
|
|
210
214
|
} else if (ideProbe.detected.length === 1) {
|
|
211
215
|
selectedIDE = ideProbe.detected[0]!
|
|
212
216
|
} else {
|
|
@@ -75,6 +75,7 @@ interface InstallPlan {
|
|
|
75
75
|
interface InspectoSettingsShape {
|
|
76
76
|
ide?: string
|
|
77
77
|
'provider.default'?: string
|
|
78
|
+
'annotate.deliveryMode'?: 'ide' | 'agent' | 'both'
|
|
78
79
|
[key: string]: unknown
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -376,21 +377,31 @@ async function persistProjectOnboardingDefaults(
|
|
|
376
377
|
resolvedHostIde.ide && resolvedHostIde.confidence !== 'low'
|
|
377
378
|
? await resolveProviderDefaultForAssistant(assistant, resolvedHostIde.ide)
|
|
378
379
|
: undefined
|
|
380
|
+
const annotateDeliveryMode = resolveAnnotateDefaultDeliveryForAssistant(assistant)
|
|
379
381
|
const mergedSettings =
|
|
380
382
|
existingSettings && typeof existingSettings === 'object'
|
|
381
383
|
? {
|
|
382
384
|
...existingSettings,
|
|
383
385
|
ide: options.ide,
|
|
384
386
|
...(providerDefault ? { 'provider.default': providerDefault } : {}),
|
|
387
|
+
'annotate.deliveryMode': annotateDeliveryMode,
|
|
385
388
|
}
|
|
386
389
|
: {
|
|
387
390
|
ide: options.ide,
|
|
388
391
|
...(providerDefault ? { 'provider.default': providerDefault } : {}),
|
|
392
|
+
'annotate.deliveryMode': annotateDeliveryMode,
|
|
389
393
|
}
|
|
390
394
|
|
|
391
395
|
await writeJSON(settingsPath, mergedSettings)
|
|
392
396
|
}
|
|
393
397
|
|
|
398
|
+
function resolveAnnotateDefaultDeliveryForAssistant(
|
|
399
|
+
assistant: AssistantId,
|
|
400
|
+
): 'ide' | 'agent' | 'both' {
|
|
401
|
+
void assistant
|
|
402
|
+
return 'both'
|
|
403
|
+
}
|
|
404
|
+
|
|
394
405
|
function shouldSkipAutomationForInstall(options: InstallIntegrationOptions): boolean {
|
|
395
406
|
return options.scope === 'user' && !options.preview
|
|
396
407
|
}
|
|
@@ -576,6 +587,10 @@ function resolveCodexPlan(options: InstallIntegrationOptions): InstallPlan {
|
|
|
576
587
|
scope === 'user'
|
|
577
588
|
? path.join(homedir(), '.agents/skills/inspecto-onboarding-codex')
|
|
578
589
|
: '.agents/skills/inspecto-onboarding-codex'
|
|
590
|
+
const agentDir =
|
|
591
|
+
scope === 'user'
|
|
592
|
+
? path.join(homedir(), '.agents/skills/inspecto-agent-codex')
|
|
593
|
+
: '.agents/skills/inspecto-agent-codex'
|
|
579
594
|
|
|
580
595
|
return {
|
|
581
596
|
assets: [
|
|
@@ -595,9 +610,19 @@ function resolveCodexPlan(options: InstallIntegrationOptions): InstallPlan {
|
|
|
595
610
|
executable: true,
|
|
596
611
|
localSource: 'skills/inspecto-onboarding-codex/scripts/run-inspecto.sh',
|
|
597
612
|
},
|
|
613
|
+
{
|
|
614
|
+
source: `${REPO_RAW_BASE}/skills/inspecto-agent-codex/SKILL.md`,
|
|
615
|
+
target: path.join(agentDir, 'SKILL.md'),
|
|
616
|
+
localSource: 'skills/inspecto-agent-codex/SKILL.md',
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
source: `${REPO_RAW_BASE}/skills/inspecto-agent-codex/agents/openai.yaml`,
|
|
620
|
+
target: path.join(agentDir, 'agents/openai.yaml'),
|
|
621
|
+
localSource: 'skills/inspecto-agent-codex/agents/openai.yaml',
|
|
622
|
+
},
|
|
598
623
|
],
|
|
599
|
-
successMessage: `Installed Codex
|
|
600
|
-
nextStep: 'Restart Codex or start a new Codex session to load the
|
|
624
|
+
successMessage: `Installed Codex skills to ${baseDir} and ${agentDir}`,
|
|
625
|
+
nextStep: 'Restart Codex or start a new Codex session to load the new skills.',
|
|
601
626
|
}
|
|
602
627
|
}
|
|
603
628
|
|
|
@@ -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://127.0.0.1:${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
|
)
|