@inspecto-dev/cli 0.3.8 → 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 +206 -6777
- package/CHANGELOG.md +8 -0
- package/dist/bin.js +12 -1
- package/dist/{chunk-B5JDHSP7.js → chunk-T46P6RD7.js} +422 -56
- 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/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/integration-install.test.ts +16 -0
- package/tests/mcp.test.ts +197 -0
- package/tests/onboard.test.ts +15 -0
|
@@ -2497,7 +2497,7 @@ function buildUmiMountSnippet() {
|
|
|
2497
2497
|
" if (process.env.NODE_ENV !== 'production') {",
|
|
2498
2498
|
" import('@inspecto-dev/core').then(({ mountInspector }) => {",
|
|
2499
2499
|
" mountInspector({",
|
|
2500
|
-
" serverUrl: 'http://
|
|
2500
|
+
" serverUrl: 'http://0.0.0.0:' + ((window as { __AI_INSPECTOR_PORT__?: number }).__AI_INSPECTOR_PORT__ || 5678),",
|
|
2501
2501
|
" })",
|
|
2502
2502
|
" })",
|
|
2503
2503
|
" }",
|
|
@@ -3379,21 +3379,29 @@ function resolveOnboardingTarget(input) {
|
|
|
3379
3379
|
import prompts from "prompts";
|
|
3380
3380
|
async function promptIDEChoice(detections) {
|
|
3381
3381
|
if (!process.stdin.isTTY) {
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3382
|
+
if (detections.length > 0) {
|
|
3383
|
+
log.warn("Multiple IDEs detected but stdin is not interactive");
|
|
3384
|
+
log.hint(`Using: ${detections[0].ide} (first match)`);
|
|
3385
|
+
return detections[0];
|
|
3386
|
+
}
|
|
3387
|
+
return { ide: "none", supported: true };
|
|
3385
3388
|
}
|
|
3389
|
+
const choices = detections.map((d) => ({
|
|
3390
|
+
title: `${d.ide} ${d.supported ? "(supported)" : "(unsupported/limited)"}`,
|
|
3391
|
+
value: d
|
|
3392
|
+
}));
|
|
3393
|
+
choices.push({
|
|
3394
|
+
title: "none (Standalone / MCP / Browser-only)",
|
|
3395
|
+
value: { ide: "none", supported: true }
|
|
3396
|
+
});
|
|
3386
3397
|
const { choice } = await prompts({
|
|
3387
3398
|
type: "select",
|
|
3388
3399
|
name: "choice",
|
|
3389
|
-
message: "Detected multiple IDEs, please choose one:",
|
|
3390
|
-
choices
|
|
3391
|
-
title: `${d.ide} ${d.supported ? "(supported)" : "(unsupported/limited)"}`,
|
|
3392
|
-
value: i
|
|
3393
|
-
}))
|
|
3400
|
+
message: "Detected multiple IDEs, please choose one (or select none for standalone use):",
|
|
3401
|
+
choices
|
|
3394
3402
|
});
|
|
3395
3403
|
if (choice === void 0) return null;
|
|
3396
|
-
return
|
|
3404
|
+
return choice;
|
|
3397
3405
|
}
|
|
3398
3406
|
async function promptProviderChoice(detections) {
|
|
3399
3407
|
if (!process.stdin.isTTY) {
|
|
@@ -3568,7 +3576,7 @@ function printNextJsManualInstructions() {
|
|
|
3568
3576
|
" useEffect(() => {",
|
|
3569
3577
|
" if (process.env.NODE_ENV !== 'production') {",
|
|
3570
3578
|
" import('@inspecto-dev/core').then(({ mountInspector }) => {",
|
|
3571
|
-
" mountInspector({ serverUrl: 'http://
|
|
3579
|
+
" mountInspector({ serverUrl: 'http://0.0.0.0:5678' })",
|
|
3572
3580
|
" })",
|
|
3573
3581
|
" }",
|
|
3574
3582
|
" }, [])",
|
|
@@ -3724,10 +3732,14 @@ async function init(options) {
|
|
|
3724
3732
|
"Please refer to the manual setup guide: https://inspecto-dev.github.io/inspecto/guide/manual-installation"
|
|
3725
3733
|
);
|
|
3726
3734
|
}
|
|
3727
|
-
let selectedIDE
|
|
3735
|
+
let selectedIDE;
|
|
3728
3736
|
if (ideProbe.detected.length === 0) {
|
|
3729
|
-
|
|
3730
|
-
|
|
3737
|
+
if (process.stdin.isTTY) {
|
|
3738
|
+
log.warn("No IDE detected in current project");
|
|
3739
|
+
selectedIDE = await promptIDEChoice([]);
|
|
3740
|
+
} else {
|
|
3741
|
+
selectedIDE = { ide: "none", supported: true };
|
|
3742
|
+
}
|
|
3731
3743
|
} else if (ideProbe.detected.length === 1) {
|
|
3732
3744
|
selectedIDE = ideProbe.detected[0];
|
|
3733
3745
|
} else {
|
|
@@ -4303,8 +4315,311 @@ async function doctor(options = {}) {
|
|
|
4303
4315
|
return writeCommandOutput(result, json, printDoctorResult);
|
|
4304
4316
|
}
|
|
4305
4317
|
|
|
4306
|
-
// src/
|
|
4318
|
+
// src/commands/mcp.ts
|
|
4319
|
+
import fs6 from "fs";
|
|
4320
|
+
import os from "os";
|
|
4307
4321
|
import path17 from "path";
|
|
4322
|
+
import crypto from "crypto";
|
|
4323
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4324
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4325
|
+
import { INSPECTO_API_PATHS } from "@inspecto-dev/types";
|
|
4326
|
+
import { z } from "zod";
|
|
4327
|
+
var DEFAULT_MCP_SERVER_VERSION = "0.0.0";
|
|
4328
|
+
var INSPECTO_MCP_TOOLS = [
|
|
4329
|
+
{
|
|
4330
|
+
name: "inspecto_get_session",
|
|
4331
|
+
description: "Return one Inspecto annotation session by sessionId."
|
|
4332
|
+
},
|
|
4333
|
+
{
|
|
4334
|
+
name: "inspecto_claim_next",
|
|
4335
|
+
description: "Wait for the next unclaimed Inspecto annotation session, mark it acknowledged, and return full context."
|
|
4336
|
+
},
|
|
4337
|
+
{
|
|
4338
|
+
name: "inspecto_reply",
|
|
4339
|
+
description: "Append an agent reply to an Inspecto annotation session."
|
|
4340
|
+
},
|
|
4341
|
+
{
|
|
4342
|
+
name: "inspecto_resolve",
|
|
4343
|
+
description: "Resolve an Inspecto annotation session with an optional final message."
|
|
4344
|
+
},
|
|
4345
|
+
{
|
|
4346
|
+
name: "inspecto_dismiss",
|
|
4347
|
+
description: "Dismiss an Inspecto annotation session with an optional final message."
|
|
4348
|
+
}
|
|
4349
|
+
];
|
|
4350
|
+
async function startMcpServer(options = {}) {
|
|
4351
|
+
const baseUrl = options.serverUrl ?? resolveInspectoServerBaseUrl(process.cwd());
|
|
4352
|
+
if (!baseUrl) {
|
|
4353
|
+
throw new Error(
|
|
4354
|
+
"Could not find a running Inspecto dev server. Start your local dev server or pass --server-url <url>."
|
|
4355
|
+
);
|
|
4356
|
+
}
|
|
4357
|
+
const server = createInspectoMcpServer({
|
|
4358
|
+
baseUrl,
|
|
4359
|
+
...options.version ? { version: options.version } : {}
|
|
4360
|
+
});
|
|
4361
|
+
const transport = new StdioServerTransport();
|
|
4362
|
+
await server.connect(transport);
|
|
4363
|
+
}
|
|
4364
|
+
function createInspectoMcpServer(options) {
|
|
4365
|
+
const runtime = createInspectoMcpRuntime(options.baseUrl);
|
|
4366
|
+
const server = new McpServer({
|
|
4367
|
+
name: "inspecto-mcp",
|
|
4368
|
+
version: options.version ?? DEFAULT_MCP_SERVER_VERSION
|
|
4369
|
+
});
|
|
4370
|
+
server.registerTool(
|
|
4371
|
+
"inspecto_get_session",
|
|
4372
|
+
{
|
|
4373
|
+
description: getToolDescription("inspecto_get_session"),
|
|
4374
|
+
inputSchema: {
|
|
4375
|
+
sessionId: z.string().min(1)
|
|
4376
|
+
}
|
|
4377
|
+
},
|
|
4378
|
+
async ({ sessionId }) => {
|
|
4379
|
+
try {
|
|
4380
|
+
const result = await runtime.getSession({ sessionId });
|
|
4381
|
+
return toolSuccess(result);
|
|
4382
|
+
} catch (error) {
|
|
4383
|
+
return toolError(error);
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4386
|
+
);
|
|
4387
|
+
server.registerTool(
|
|
4388
|
+
"inspecto_claim_next",
|
|
4389
|
+
{
|
|
4390
|
+
description: getToolDescription("inspecto_claim_next"),
|
|
4391
|
+
inputSchema: {
|
|
4392
|
+
timeoutMs: z.number().int().nonnegative().optional()
|
|
4393
|
+
}
|
|
4394
|
+
},
|
|
4395
|
+
async ({ timeoutMs }) => {
|
|
4396
|
+
try {
|
|
4397
|
+
const result = await runtime.claimNext({
|
|
4398
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {}
|
|
4399
|
+
});
|
|
4400
|
+
return toolSuccess(result);
|
|
4401
|
+
} catch (error) {
|
|
4402
|
+
return toolError(error);
|
|
4403
|
+
}
|
|
4404
|
+
}
|
|
4405
|
+
);
|
|
4406
|
+
server.registerTool(
|
|
4407
|
+
"inspecto_reply",
|
|
4408
|
+
{
|
|
4409
|
+
description: getToolDescription("inspecto_reply"),
|
|
4410
|
+
inputSchema: {
|
|
4411
|
+
sessionId: z.string().min(1),
|
|
4412
|
+
text: z.string().min(1)
|
|
4413
|
+
}
|
|
4414
|
+
},
|
|
4415
|
+
async ({ sessionId, text }) => {
|
|
4416
|
+
try {
|
|
4417
|
+
const result = await runtime.reply({ sessionId, text });
|
|
4418
|
+
return toolSuccess(result);
|
|
4419
|
+
} catch (error) {
|
|
4420
|
+
return toolError(error);
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
);
|
|
4424
|
+
server.registerTool(
|
|
4425
|
+
"inspecto_resolve",
|
|
4426
|
+
{
|
|
4427
|
+
description: getToolDescription("inspecto_resolve"),
|
|
4428
|
+
inputSchema: {
|
|
4429
|
+
sessionId: z.string().min(1),
|
|
4430
|
+
message: z.string().optional()
|
|
4431
|
+
}
|
|
4432
|
+
},
|
|
4433
|
+
async ({ sessionId, message: message2 }) => {
|
|
4434
|
+
try {
|
|
4435
|
+
const result = await runtime.resolve({ sessionId, ...message2 ? { message: message2 } : {} });
|
|
4436
|
+
return toolSuccess(result);
|
|
4437
|
+
} catch (error) {
|
|
4438
|
+
return toolError(error);
|
|
4439
|
+
}
|
|
4440
|
+
}
|
|
4441
|
+
);
|
|
4442
|
+
server.registerTool(
|
|
4443
|
+
"inspecto_dismiss",
|
|
4444
|
+
{
|
|
4445
|
+
description: getToolDescription("inspecto_dismiss"),
|
|
4446
|
+
inputSchema: {
|
|
4447
|
+
sessionId: z.string().min(1),
|
|
4448
|
+
message: z.string().optional()
|
|
4449
|
+
}
|
|
4450
|
+
},
|
|
4451
|
+
async ({ sessionId, message: message2 }) => {
|
|
4452
|
+
try {
|
|
4453
|
+
const result = await runtime.dismiss({ sessionId, ...message2 ? { message: message2 } : {} });
|
|
4454
|
+
return toolSuccess(result);
|
|
4455
|
+
} catch (error) {
|
|
4456
|
+
return toolError(error);
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
);
|
|
4460
|
+
return server;
|
|
4461
|
+
}
|
|
4462
|
+
function getToolDescription(name) {
|
|
4463
|
+
return INSPECTO_MCP_TOOLS.find((tool) => tool.name === name)?.description ?? name;
|
|
4464
|
+
}
|
|
4465
|
+
function createInspectoMcpRuntime(baseUrl) {
|
|
4466
|
+
return {
|
|
4467
|
+
async getSession(args) {
|
|
4468
|
+
return getSession(baseUrl, args.sessionId);
|
|
4469
|
+
},
|
|
4470
|
+
async claimNext(args = {}) {
|
|
4471
|
+
return await postJson(`${baseUrl}${INSPECTO_API_PATHS.SESSION_CLAIM_NEXT}`, {
|
|
4472
|
+
...args.timeoutMs !== void 0 ? { timeoutMs: args.timeoutMs } : {}
|
|
4473
|
+
});
|
|
4474
|
+
},
|
|
4475
|
+
async reply(args) {
|
|
4476
|
+
return postJson(
|
|
4477
|
+
`${baseUrl}${INSPECTO_API_PATHS.SESSIONS}/${args.sessionId}${INSPECTO_API_PATHS.SESSION_REPLY_SUFFIX}`,
|
|
4478
|
+
{
|
|
4479
|
+
role: "agent",
|
|
4480
|
+
text: args.text.trim()
|
|
4481
|
+
}
|
|
4482
|
+
);
|
|
4483
|
+
},
|
|
4484
|
+
async resolve(args) {
|
|
4485
|
+
return postJson(
|
|
4486
|
+
`${baseUrl}${INSPECTO_API_PATHS.SESSIONS}/${args.sessionId}${INSPECTO_API_PATHS.SESSION_RESOLVE_SUFFIX}`,
|
|
4487
|
+
args.message?.trim() ? { message: args.message.trim() } : {}
|
|
4488
|
+
);
|
|
4489
|
+
},
|
|
4490
|
+
async dismiss(args) {
|
|
4491
|
+
return postJson(
|
|
4492
|
+
`${baseUrl}${INSPECTO_API_PATHS.SESSIONS}/${args.sessionId}${INSPECTO_API_PATHS.SESSION_DISMISS_SUFFIX}`,
|
|
4493
|
+
args.message?.trim() ? { message: args.message.trim() } : {}
|
|
4494
|
+
);
|
|
4495
|
+
}
|
|
4496
|
+
};
|
|
4497
|
+
}
|
|
4498
|
+
function resolveInspectoServerBaseUrl(cwd) {
|
|
4499
|
+
const ports = resolveServerPorts(cwd);
|
|
4500
|
+
const port = ports[0];
|
|
4501
|
+
return port ? `http://0.0.0.0:${port}` : null;
|
|
4502
|
+
}
|
|
4503
|
+
function resolveServerPorts(cwd) {
|
|
4504
|
+
const prioritized = readProjectScopedPorts(cwd);
|
|
4505
|
+
if (prioritized.length > 0) return prioritized;
|
|
4506
|
+
const legacyPortFile = path17.join(os.tmpdir(), "inspecto.port");
|
|
4507
|
+
try {
|
|
4508
|
+
const raw = fs6.readFileSync(legacyPortFile, "utf-8").trim();
|
|
4509
|
+
const port = parseInt(raw, 10);
|
|
4510
|
+
if (Number.isInteger(port) && port > 0 && port < 65536) {
|
|
4511
|
+
return [port];
|
|
4512
|
+
}
|
|
4513
|
+
} catch {
|
|
4514
|
+
}
|
|
4515
|
+
return Array.from({ length: 23 }, (_, index) => 5678 + index);
|
|
4516
|
+
}
|
|
4517
|
+
function toolSuccess(value) {
|
|
4518
|
+
return {
|
|
4519
|
+
content: [
|
|
4520
|
+
{
|
|
4521
|
+
type: "text",
|
|
4522
|
+
text: JSON.stringify(value, null, 2)
|
|
4523
|
+
}
|
|
4524
|
+
],
|
|
4525
|
+
structuredContent: value
|
|
4526
|
+
};
|
|
4527
|
+
}
|
|
4528
|
+
function toolError(error) {
|
|
4529
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
4530
|
+
return {
|
|
4531
|
+
content: [
|
|
4532
|
+
{
|
|
4533
|
+
type: "text",
|
|
4534
|
+
text: message2
|
|
4535
|
+
}
|
|
4536
|
+
],
|
|
4537
|
+
structuredContent: {
|
|
4538
|
+
success: false,
|
|
4539
|
+
error: message2
|
|
4540
|
+
},
|
|
4541
|
+
isError: true
|
|
4542
|
+
};
|
|
4543
|
+
}
|
|
4544
|
+
async function getJson(url) {
|
|
4545
|
+
const response = await fetch(url);
|
|
4546
|
+
const payload = await response.json().catch(() => ({}));
|
|
4547
|
+
if (!response.ok) {
|
|
4548
|
+
throw new Error(String(payload["error"] ?? `Request failed with status ${response.status}.`));
|
|
4549
|
+
}
|
|
4550
|
+
return payload;
|
|
4551
|
+
}
|
|
4552
|
+
async function getSession(baseUrl, sessionId) {
|
|
4553
|
+
const trimmed = sessionId.trim();
|
|
4554
|
+
if (!trimmed) {
|
|
4555
|
+
throw new Error("Session id is required.");
|
|
4556
|
+
}
|
|
4557
|
+
const payload = await getJson(
|
|
4558
|
+
`${baseUrl}${INSPECTO_API_PATHS.SESSIONS}/${encodeURIComponent(trimmed)}`
|
|
4559
|
+
);
|
|
4560
|
+
if (!payload.success || !payload.session) {
|
|
4561
|
+
throw new Error(payload.error ?? "Session not found.");
|
|
4562
|
+
}
|
|
4563
|
+
return {
|
|
4564
|
+
success: true,
|
|
4565
|
+
session: payload.session
|
|
4566
|
+
};
|
|
4567
|
+
}
|
|
4568
|
+
async function postJson(url, body) {
|
|
4569
|
+
const response = await fetch(url, {
|
|
4570
|
+
method: "POST",
|
|
4571
|
+
headers: { "Content-Type": "application/json" },
|
|
4572
|
+
body: JSON.stringify(body)
|
|
4573
|
+
});
|
|
4574
|
+
const payload = await response.json().catch(() => ({}));
|
|
4575
|
+
if (!response.ok || payload["success"] === false) {
|
|
4576
|
+
throw new Error(String(payload["error"] ?? `Request failed with status ${response.status}.`));
|
|
4577
|
+
}
|
|
4578
|
+
return payload;
|
|
4579
|
+
}
|
|
4580
|
+
function readProjectScopedPorts(cwd) {
|
|
4581
|
+
const portFile = path17.join(os.tmpdir(), "inspecto.port.json");
|
|
4582
|
+
try {
|
|
4583
|
+
const raw = fs6.readFileSync(portFile, "utf-8").trim();
|
|
4584
|
+
const portData = JSON.parse(raw);
|
|
4585
|
+
const currentRootHashes = resolveCandidateRootHashes(cwd);
|
|
4586
|
+
const prioritized = [];
|
|
4587
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4588
|
+
for (const rootHash of currentRootHashes) {
|
|
4589
|
+
const currentPort = portData[rootHash];
|
|
4590
|
+
if (currentPort && !seen.has(currentPort)) {
|
|
4591
|
+
prioritized.push(currentPort);
|
|
4592
|
+
seen.add(currentPort);
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
for (const port of Object.values(portData)) {
|
|
4596
|
+
if (!seen.has(port)) {
|
|
4597
|
+
prioritized.push(port);
|
|
4598
|
+
seen.add(port);
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
4601
|
+
return prioritized;
|
|
4602
|
+
} catch {
|
|
4603
|
+
return [];
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
function resolveCandidateRootHashes(cwd) {
|
|
4607
|
+
const normalized = path17.resolve(cwd);
|
|
4608
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
4609
|
+
let currentDir = normalized;
|
|
4610
|
+
while (true) {
|
|
4611
|
+
candidates.add(crypto.createHash("md5").update(currentDir).digest("hex"));
|
|
4612
|
+
const parentDir = path17.dirname(currentDir);
|
|
4613
|
+
if (parentDir === currentDir) {
|
|
4614
|
+
break;
|
|
4615
|
+
}
|
|
4616
|
+
currentDir = parentDir;
|
|
4617
|
+
}
|
|
4618
|
+
return [...candidates];
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4621
|
+
// src/onboarding/session.ts
|
|
4622
|
+
import path18 from "path";
|
|
4308
4623
|
function normalizePackagePath2(packagePath) {
|
|
4309
4624
|
if (!packagePath || packagePath === ".") return "";
|
|
4310
4625
|
return packagePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
@@ -4328,7 +4643,7 @@ function getVerificationCommand(packageManager) {
|
|
|
4328
4643
|
}
|
|
4329
4644
|
}
|
|
4330
4645
|
async function buildVerification(projectRoot, packageManager) {
|
|
4331
|
-
const packageJson = await readJSON(
|
|
4646
|
+
const packageJson = await readJSON(path18.join(projectRoot, "package.json"));
|
|
4332
4647
|
if (packageJson?.scripts?.dev) {
|
|
4333
4648
|
const devCommand = getVerificationCommand(packageManager);
|
|
4334
4649
|
return {
|
|
@@ -4386,7 +4701,7 @@ async function detectFrameworkSupportByPackage2(repoRoot, context) {
|
|
|
4386
4701
|
await Promise.all(
|
|
4387
4702
|
Array.from(packagePaths).map(async (packagePath) => {
|
|
4388
4703
|
const frameworkResult = await detectFrameworks(
|
|
4389
|
-
packagePath ?
|
|
4704
|
+
packagePath ? path18.join(repoRoot, packagePath) : repoRoot
|
|
4390
4705
|
);
|
|
4391
4706
|
supportByPackage[packagePath] = frameworkResult.supported;
|
|
4392
4707
|
})
|
|
@@ -4395,7 +4710,7 @@ async function detectFrameworkSupportByPackage2(repoRoot, context) {
|
|
|
4395
4710
|
}
|
|
4396
4711
|
async function buildTargetedContext(rootContext, target) {
|
|
4397
4712
|
const packagePath = normalizePackagePath2(target.packagePath);
|
|
4398
|
-
const projectRoot = packagePath ?
|
|
4713
|
+
const projectRoot = packagePath ? path18.join(rootContext.root, packagePath) : rootContext.root;
|
|
4399
4714
|
const [frameworks, ides, providers] = await Promise.all([
|
|
4400
4715
|
detectFrameworks(projectRoot),
|
|
4401
4716
|
detectIDE(projectRoot),
|
|
@@ -4470,7 +4785,21 @@ function buildConfirmation(plan2, summary, session, options) {
|
|
|
4470
4785
|
question: "Proceed with Inspecto onboarding using the proposed default target and settings?"
|
|
4471
4786
|
};
|
|
4472
4787
|
}
|
|
4473
|
-
function
|
|
4788
|
+
async function buildDailyUsageHandoff(projectRoot) {
|
|
4789
|
+
const localSettings = await readJSON(path18.join(projectRoot, ".inspecto", "settings.local.json")) ?? {};
|
|
4790
|
+
const sharedSettings = await readJSON(path18.join(projectRoot, ".inspecto", "settings.json")) ?? {};
|
|
4791
|
+
const annotateDeliveryMode = localSettings["annotate.deliveryMode"] ?? sharedSettings["annotate.deliveryMode"];
|
|
4792
|
+
if (annotateDeliveryMode !== "agent") {
|
|
4793
|
+
return void 0;
|
|
4794
|
+
}
|
|
4795
|
+
return {
|
|
4796
|
+
mode: "agent",
|
|
4797
|
+
skill: "inspecto-agent",
|
|
4798
|
+
prompt: "Use $inspecto-agent to claim Inspecto tasks continuously",
|
|
4799
|
+
requiresMcp: true
|
|
4800
|
+
};
|
|
4801
|
+
}
|
|
4802
|
+
function buildPreApplyResult(status, session, dailyUsage) {
|
|
4474
4803
|
const diagnostics = session.summary.risks.length > 0 || session.summary.manualFollowUp.length > 0 || session.plan.blockers.length > 0 ? {
|
|
4475
4804
|
warnings: session.summary.risks,
|
|
4476
4805
|
errors: session.plan.blockers.map((item) => item.message),
|
|
@@ -4496,6 +4825,7 @@ function buildPreApplyResult(status, session) {
|
|
|
4496
4825
|
...session.pendingSteps ? { pendingSteps: session.pendingSteps } : {},
|
|
4497
4826
|
...session.assistantPrompt ? { assistantPrompt: session.assistantPrompt } : {},
|
|
4498
4827
|
...session.patches ? { patches: session.patches } : {},
|
|
4828
|
+
...dailyUsage ? { dailyUsage } : {},
|
|
4499
4829
|
...diagnostics ? { diagnostics } : {}
|
|
4500
4830
|
};
|
|
4501
4831
|
}
|
|
@@ -4536,6 +4866,7 @@ function buildExecutionDiagnostics(session, applyResult) {
|
|
|
4536
4866
|
async function resolveOnboardingSession(root, options = {}) {
|
|
4537
4867
|
const rootContext = await buildOnboardingContext(root);
|
|
4538
4868
|
const rootVerification = await buildVerification(root, rootContext.packageManager);
|
|
4869
|
+
const rootDailyUsage = await buildDailyUsageHandoff(root);
|
|
4539
4870
|
const frameworkSupportByPackage = await detectFrameworkSupportByPackage2(root, rootContext);
|
|
4540
4871
|
const target = resolveOnboardingTarget({
|
|
4541
4872
|
repoRoot: root,
|
|
@@ -4569,7 +4900,8 @@ async function resolveOnboardingSession(root, options = {}) {
|
|
|
4569
4900
|
...plan3.autoApplied ? { autoApplied: plan3.autoApplied } : {},
|
|
4570
4901
|
...plan3.pendingSteps ? { pendingSteps: plan3.pendingSteps } : {},
|
|
4571
4902
|
...plan3.assistantPrompt ? { assistantPrompt: plan3.assistantPrompt } : {},
|
|
4572
|
-
...plan3.patches ? { patches: plan3.patches } : {}
|
|
4903
|
+
...plan3.patches ? { patches: plan3.patches } : {},
|
|
4904
|
+
...rootDailyUsage ? { dailyUsage: rootDailyUsage } : {}
|
|
4573
4905
|
};
|
|
4574
4906
|
}
|
|
4575
4907
|
if (target.status === "needs_selection") {
|
|
@@ -4593,6 +4925,7 @@ async function resolveOnboardingSession(root, options = {}) {
|
|
|
4593
4925
|
}
|
|
4594
4926
|
const context = await buildTargetedContext(rootContext, target.selected);
|
|
4595
4927
|
const verification = await buildVerification(context.root, context.packageManager);
|
|
4928
|
+
const dailyUsage = await buildDailyUsageHandoff(context.root);
|
|
4596
4929
|
const plan2 = createPlanResult(context);
|
|
4597
4930
|
const summary = buildOnboardingSummary(plan2, context.root);
|
|
4598
4931
|
const confirmation = buildConfirmation(
|
|
@@ -4636,11 +4969,13 @@ async function resolveOnboardingSession(root, options = {}) {
|
|
|
4636
4969
|
...plan2.autoApplied ? { autoApplied: plan2.autoApplied } : {},
|
|
4637
4970
|
...plan2.pendingSteps ? { pendingSteps: plan2.pendingSteps } : {},
|
|
4638
4971
|
...plan2.assistantPrompt ? { assistantPrompt: plan2.assistantPrompt } : {},
|
|
4639
|
-
...plan2.patches ? { patches: plan2.patches } : {}
|
|
4972
|
+
...plan2.patches ? { patches: plan2.patches } : {},
|
|
4973
|
+
...dailyUsage ? { dailyUsage } : {}
|
|
4640
4974
|
};
|
|
4641
4975
|
}
|
|
4642
4976
|
async function applyResolvedOnboardingSession(session, options = {}) {
|
|
4643
4977
|
const verification = await buildVerification(session.projectRoot, session.context.packageManager);
|
|
4978
|
+
const dailyUsage = await buildDailyUsageHandoff(session.projectRoot);
|
|
4644
4979
|
const applyResult = await applyOnboardingPlan({
|
|
4645
4980
|
repoRoot: process.cwd(),
|
|
4646
4981
|
projectRoot: session.projectRoot,
|
|
@@ -4681,11 +5016,16 @@ async function applyResolvedOnboardingSession(session, options = {}) {
|
|
|
4681
5016
|
...session.pendingSteps ? { pendingSteps: session.pendingSteps } : {},
|
|
4682
5017
|
...session.assistantPrompt ? { assistantPrompt: session.assistantPrompt } : {},
|
|
4683
5018
|
...session.patches ? { patches: session.patches } : {},
|
|
5019
|
+
...dailyUsage ? { dailyUsage } : {},
|
|
4684
5020
|
...diagnostics ? { diagnostics } : {}
|
|
4685
5021
|
};
|
|
4686
5022
|
}
|
|
4687
|
-
function buildDeferredOnboardResult(session) {
|
|
4688
|
-
return buildPreApplyResult(
|
|
5023
|
+
async function buildDeferredOnboardResult(session) {
|
|
5024
|
+
return buildPreApplyResult(
|
|
5025
|
+
session.status,
|
|
5026
|
+
session,
|
|
5027
|
+
await buildDailyUsageHandoff(session.projectRoot)
|
|
5028
|
+
);
|
|
4689
5029
|
}
|
|
4690
5030
|
|
|
4691
5031
|
// src/commands/onboard.ts
|
|
@@ -4715,7 +5055,8 @@ function buildAssistantHandoff(result) {
|
|
|
4715
5055
|
...result.autoApplied ? { autoApplied: result.autoApplied } : {},
|
|
4716
5056
|
...result.pendingSteps ? { pendingSteps: result.pendingSteps } : {},
|
|
4717
5057
|
...result.assistantPrompt ? { assistantPrompt: result.assistantPrompt } : {},
|
|
4718
|
-
...result.patches ? { patches: result.patches } : {}
|
|
5058
|
+
...result.patches ? { patches: result.patches } : {},
|
|
5059
|
+
...result.dailyUsage ? { dailyUsage: result.dailyUsage } : {}
|
|
4719
5060
|
};
|
|
4720
5061
|
}
|
|
4721
5062
|
function normalizeOnboardResult(result) {
|
|
@@ -4763,6 +5104,9 @@ function printOnboardResult(result) {
|
|
|
4763
5104
|
if (normalized.handoff?.assistantPrompt) {
|
|
4764
5105
|
log.hint(normalized.handoff.assistantPrompt);
|
|
4765
5106
|
}
|
|
5107
|
+
if (normalized.handoff?.dailyUsage?.prompt) {
|
|
5108
|
+
log.hint(normalized.handoff.dailyUsage.prompt);
|
|
5109
|
+
}
|
|
4766
5110
|
if (normalized.confirmation.required && normalized.confirmation.question) {
|
|
4767
5111
|
log.warn(normalized.confirmation.question);
|
|
4768
5112
|
}
|
|
@@ -4777,7 +5121,7 @@ async function onboard(options = {}) {
|
|
|
4777
5121
|
const session = await resolveOnboardingSession(root, options);
|
|
4778
5122
|
if (session.status === "error" || session.status === "needs_target_selection" || session.status === "needs_confirmation") {
|
|
4779
5123
|
return writeCommandOutput(
|
|
4780
|
-
normalizeOnboardResult(buildDeferredOnboardResult(session)),
|
|
5124
|
+
normalizeOnboardResult(await buildDeferredOnboardResult(session)),
|
|
4781
5125
|
options.json ?? false,
|
|
4782
5126
|
printOnboardResult
|
|
4783
5127
|
);
|
|
@@ -4820,11 +5164,11 @@ async function plan(json = false) {
|
|
|
4820
5164
|
}
|
|
4821
5165
|
|
|
4822
5166
|
// src/commands/teardown.ts
|
|
4823
|
-
import
|
|
5167
|
+
import path19 from "path";
|
|
4824
5168
|
async function teardown() {
|
|
4825
5169
|
const root = process.cwd();
|
|
4826
5170
|
log.header("Inspecto Teardown");
|
|
4827
|
-
const lockPath =
|
|
5171
|
+
const lockPath = path19.join(root, ".inspecto", "install.lock");
|
|
4828
5172
|
const lock = await readJSON(lockPath);
|
|
4829
5173
|
if (!lock) {
|
|
4830
5174
|
log.warn("No .inspecto/install.lock found. Running in best-effort mode.");
|
|
@@ -4837,8 +5181,8 @@ async function teardown() {
|
|
|
4837
5181
|
} catch {
|
|
4838
5182
|
log.warn("Could not remove @inspecto-dev/plugin (may not be installed)");
|
|
4839
5183
|
}
|
|
4840
|
-
if (await exists(
|
|
4841
|
-
await removeDir(
|
|
5184
|
+
if (await exists(path19.join(root, ".inspecto"))) {
|
|
5185
|
+
await removeDir(path19.join(root, ".inspecto"));
|
|
4842
5186
|
log.success("Deleted .inspecto/ directory");
|
|
4843
5187
|
}
|
|
4844
5188
|
await cleanGitignore(root);
|
|
@@ -4887,7 +5231,7 @@ async function teardown() {
|
|
|
4887
5231
|
}
|
|
4888
5232
|
}
|
|
4889
5233
|
}
|
|
4890
|
-
await removeDir(
|
|
5234
|
+
await removeDir(path19.join(root, ".inspecto"));
|
|
4891
5235
|
log.success("Deleted .inspecto/ directory");
|
|
4892
5236
|
await cleanGitignore(root);
|
|
4893
5237
|
log.blank();
|
|
@@ -4896,13 +5240,13 @@ async function teardown() {
|
|
|
4896
5240
|
}
|
|
4897
5241
|
|
|
4898
5242
|
// src/commands/integration-install.ts
|
|
4899
|
-
import
|
|
5243
|
+
import fs7 from "fs/promises";
|
|
4900
5244
|
import { homedir as homedir2 } from "os";
|
|
4901
|
-
import
|
|
5245
|
+
import path22 from "path";
|
|
4902
5246
|
import { fileURLToPath } from "url";
|
|
4903
5247
|
|
|
4904
5248
|
// src/commands/integration-host-ide.ts
|
|
4905
|
-
import
|
|
5249
|
+
import path20 from "path";
|
|
4906
5250
|
async function resolveIntegrationHostIde(options = {}) {
|
|
4907
5251
|
if (isSupportedHostIde(options.explicitIde)) {
|
|
4908
5252
|
return {
|
|
@@ -4967,8 +5311,8 @@ async function resolveIntegrationHostIde(options = {}) {
|
|
|
4967
5311
|
}
|
|
4968
5312
|
async function resolveConfiguredIde(cwd) {
|
|
4969
5313
|
const settingsPaths = [
|
|
4970
|
-
|
|
4971
|
-
|
|
5314
|
+
path20.join(cwd, ".inspecto", "settings.local.json"),
|
|
5315
|
+
path20.join(cwd, ".inspecto", "settings.json")
|
|
4972
5316
|
];
|
|
4973
5317
|
for (const settingsPath of settingsPaths) {
|
|
4974
5318
|
const settings = await readJSON(settingsPath);
|
|
@@ -5019,7 +5363,7 @@ async function detectArtifactHostIdes(cwd) {
|
|
|
5019
5363
|
|
|
5020
5364
|
// src/commands/integration-dispatch-mode.ts
|
|
5021
5365
|
import { homedir } from "os";
|
|
5022
|
-
import
|
|
5366
|
+
import path21 from "path";
|
|
5023
5367
|
async function resolveIntegrationDispatchMode(options) {
|
|
5024
5368
|
const assistantRule = getDualModeAssistantCapability(options.assistant);
|
|
5025
5369
|
const home = options.homeDir ?? homedir();
|
|
@@ -5062,7 +5406,7 @@ async function isIdeExtensionInstalled(extensionId, extensionsDir) {
|
|
|
5062
5406
|
} catch {
|
|
5063
5407
|
return false;
|
|
5064
5408
|
}
|
|
5065
|
-
const obsoletePath =
|
|
5409
|
+
const obsoletePath = path21.join(extensionsDir, ".obsolete");
|
|
5066
5410
|
let obsoleteFolders = /* @__PURE__ */ new Set();
|
|
5067
5411
|
if (await exists(obsoletePath)) {
|
|
5068
5412
|
const obsolete = await readJSON(obsoletePath);
|
|
@@ -5526,7 +5870,7 @@ async function installIntegration(assistant, options = {}) {
|
|
|
5526
5870
|
if (await exists(asset.target)) {
|
|
5527
5871
|
if (options.force) {
|
|
5528
5872
|
} else if (manifest.type === "context-template") {
|
|
5529
|
-
const originalContent = await
|
|
5873
|
+
const originalContent = await fs7.readFile(asset.target, "utf-8");
|
|
5530
5874
|
existingFiles.set(asset.target, originalContent);
|
|
5531
5875
|
if (!silent) {
|
|
5532
5876
|
log.info(`File ${asset.target} already exists. Content will be appended safely.`);
|
|
@@ -5563,7 +5907,7 @@ ${content}`;
|
|
|
5563
5907
|
for (const { asset, content } of downloadedAssets) {
|
|
5564
5908
|
await writeFile(asset.target, content);
|
|
5565
5909
|
if (asset.executable) {
|
|
5566
|
-
await
|
|
5910
|
+
await fs7.chmod(asset.target, 493);
|
|
5567
5911
|
}
|
|
5568
5912
|
}
|
|
5569
5913
|
}
|
|
@@ -5678,23 +6022,30 @@ async function resolveProviderDefaultForAssistant(assistant, ide) {
|
|
|
5678
6022
|
return `${assistant}.${mode}`;
|
|
5679
6023
|
}
|
|
5680
6024
|
async function persistProjectOnboardingDefaults(assistant, options) {
|
|
5681
|
-
const settingsPath =
|
|
6025
|
+
const settingsPath = path22.join(process.cwd(), ".inspecto", "settings.local.json");
|
|
5682
6026
|
const existingSettings = await readJSON(settingsPath);
|
|
5683
6027
|
const resolvedHostIde = await resolveIntegrationHostIde({
|
|
5684
6028
|
explicitIde: options.ide,
|
|
5685
6029
|
cwd: process.cwd()
|
|
5686
6030
|
});
|
|
5687
6031
|
const providerDefault = resolvedHostIde.ide && resolvedHostIde.confidence !== "low" ? await resolveProviderDefaultForAssistant(assistant, resolvedHostIde.ide) : void 0;
|
|
6032
|
+
const annotateDeliveryMode = resolveAnnotateDefaultDeliveryForAssistant(assistant);
|
|
5688
6033
|
const mergedSettings = existingSettings && typeof existingSettings === "object" ? {
|
|
5689
6034
|
...existingSettings,
|
|
5690
6035
|
ide: options.ide,
|
|
5691
|
-
...providerDefault ? { "provider.default": providerDefault } : {}
|
|
6036
|
+
...providerDefault ? { "provider.default": providerDefault } : {},
|
|
6037
|
+
"annotate.deliveryMode": annotateDeliveryMode
|
|
5692
6038
|
} : {
|
|
5693
6039
|
ide: options.ide,
|
|
5694
|
-
...providerDefault ? { "provider.default": providerDefault } : {}
|
|
6040
|
+
...providerDefault ? { "provider.default": providerDefault } : {},
|
|
6041
|
+
"annotate.deliveryMode": annotateDeliveryMode
|
|
5695
6042
|
};
|
|
5696
6043
|
await writeJSON(settingsPath, mergedSettings);
|
|
5697
6044
|
}
|
|
6045
|
+
function resolveAnnotateDefaultDeliveryForAssistant(assistant) {
|
|
6046
|
+
void assistant;
|
|
6047
|
+
return "both";
|
|
6048
|
+
}
|
|
5698
6049
|
function shouldSkipAutomationForInstall(options) {
|
|
5699
6050
|
return options.scope === "user" && !options.preview;
|
|
5700
6051
|
}
|
|
@@ -5845,28 +6196,39 @@ function resolveCodexPlan(options) {
|
|
|
5845
6196
|
if (options.mode !== void 0) {
|
|
5846
6197
|
throw new Error("`--mode` is not supported for codex.");
|
|
5847
6198
|
}
|
|
5848
|
-
const baseDir = scope === "user" ?
|
|
6199
|
+
const baseDir = scope === "user" ? path22.join(homedir2(), ".agents/skills/inspecto-onboarding-codex") : ".agents/skills/inspecto-onboarding-codex";
|
|
6200
|
+
const agentDir = scope === "user" ? path22.join(homedir2(), ".agents/skills/inspecto-agent-codex") : ".agents/skills/inspecto-agent-codex";
|
|
5849
6201
|
return {
|
|
5850
6202
|
assets: [
|
|
5851
6203
|
{
|
|
5852
6204
|
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-codex/SKILL.md`,
|
|
5853
|
-
target:
|
|
6205
|
+
target: path22.join(baseDir, "SKILL.md"),
|
|
5854
6206
|
localSource: "skills/inspecto-onboarding-codex/SKILL.md"
|
|
5855
6207
|
},
|
|
5856
6208
|
{
|
|
5857
6209
|
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-codex/agents/openai.yaml`,
|
|
5858
|
-
target:
|
|
6210
|
+
target: path22.join(baseDir, "agents/openai.yaml"),
|
|
5859
6211
|
localSource: "skills/inspecto-onboarding-codex/agents/openai.yaml"
|
|
5860
6212
|
},
|
|
5861
6213
|
{
|
|
5862
6214
|
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-codex/scripts/run-inspecto.sh`,
|
|
5863
|
-
target:
|
|
6215
|
+
target: path22.join(baseDir, "scripts/run-inspecto.sh"),
|
|
5864
6216
|
executable: true,
|
|
5865
6217
|
localSource: "skills/inspecto-onboarding-codex/scripts/run-inspecto.sh"
|
|
6218
|
+
},
|
|
6219
|
+
{
|
|
6220
|
+
source: `${REPO_RAW_BASE}/skills/inspecto-agent-codex/SKILL.md`,
|
|
6221
|
+
target: path22.join(agentDir, "SKILL.md"),
|
|
6222
|
+
localSource: "skills/inspecto-agent-codex/SKILL.md"
|
|
6223
|
+
},
|
|
6224
|
+
{
|
|
6225
|
+
source: `${REPO_RAW_BASE}/skills/inspecto-agent-codex/agents/openai.yaml`,
|
|
6226
|
+
target: path22.join(agentDir, "agents/openai.yaml"),
|
|
6227
|
+
localSource: "skills/inspecto-agent-codex/agents/openai.yaml"
|
|
5866
6228
|
}
|
|
5867
6229
|
],
|
|
5868
|
-
successMessage: `Installed Codex
|
|
5869
|
-
nextStep: "Restart Codex or start a new Codex session to load the
|
|
6230
|
+
successMessage: `Installed Codex skills to ${baseDir} and ${agentDir}`,
|
|
6231
|
+
nextStep: "Restart Codex or start a new Codex session to load the new skills."
|
|
5870
6232
|
};
|
|
5871
6233
|
}
|
|
5872
6234
|
function resolveClaudeCodePlan(options) {
|
|
@@ -5880,22 +6242,22 @@ function resolveClaudeCodePlan(options) {
|
|
|
5880
6242
|
if (scope !== "project" && scope !== "user") {
|
|
5881
6243
|
throw new Error(`Unknown Claude Code scope: ${scope}`);
|
|
5882
6244
|
}
|
|
5883
|
-
const baseDir = scope === "user" ?
|
|
6245
|
+
const baseDir = scope === "user" ? path22.join(homedir2(), ".claude/skills/inspecto-onboarding-claude-code") : ".claude/skills/inspecto-onboarding-claude-code";
|
|
5884
6246
|
return {
|
|
5885
6247
|
assets: [
|
|
5886
6248
|
{
|
|
5887
6249
|
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-claude-code/SKILL.md`,
|
|
5888
|
-
target:
|
|
6250
|
+
target: path22.join(baseDir, "SKILL.md"),
|
|
5889
6251
|
localSource: "skills/inspecto-onboarding-claude-code/SKILL.md"
|
|
5890
6252
|
},
|
|
5891
6253
|
{
|
|
5892
6254
|
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-claude-code/agents/openai.yaml`,
|
|
5893
|
-
target:
|
|
6255
|
+
target: path22.join(baseDir, "agents/openai.yaml"),
|
|
5894
6256
|
localSource: "skills/inspecto-onboarding-claude-code/agents/openai.yaml"
|
|
5895
6257
|
},
|
|
5896
6258
|
{
|
|
5897
6259
|
source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh`,
|
|
5898
|
-
target:
|
|
6260
|
+
target: path22.join(baseDir, "scripts/run-inspecto.sh"),
|
|
5899
6261
|
executable: true,
|
|
5900
6262
|
localSource: "skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh"
|
|
5901
6263
|
}
|
|
@@ -5960,20 +6322,20 @@ async function loadAsset(asset) {
|
|
|
5960
6322
|
if (asset.localSource) {
|
|
5961
6323
|
const localPath = await resolveBundledAssetPath(asset.localSource);
|
|
5962
6324
|
if (localPath) {
|
|
5963
|
-
return await
|
|
6325
|
+
return await fs7.readFile(localPath, "utf-8");
|
|
5964
6326
|
}
|
|
5965
6327
|
}
|
|
5966
6328
|
return await downloadAsset(asset.source);
|
|
5967
6329
|
}
|
|
5968
6330
|
async function resolveBundledAssetPath(relativePath) {
|
|
5969
|
-
const startDir =
|
|
6331
|
+
const startDir = path22.dirname(fileURLToPath(import.meta.url));
|
|
5970
6332
|
let currentDir = startDir;
|
|
5971
6333
|
for (let depth = 0; depth < 8; depth += 1) {
|
|
5972
|
-
const candidate =
|
|
6334
|
+
const candidate = path22.join(currentDir, relativePath);
|
|
5973
6335
|
if (await exists(candidate)) {
|
|
5974
6336
|
return candidate;
|
|
5975
6337
|
}
|
|
5976
|
-
const parent =
|
|
6338
|
+
const parent = path22.dirname(currentDir);
|
|
5977
6339
|
if (parent === currentDir) break;
|
|
5978
6340
|
currentDir = parent;
|
|
5979
6341
|
}
|
|
@@ -6087,6 +6449,10 @@ export {
|
|
|
6087
6449
|
init,
|
|
6088
6450
|
collectDoctorResult,
|
|
6089
6451
|
doctor,
|
|
6452
|
+
startMcpServer,
|
|
6453
|
+
createInspectoMcpServer,
|
|
6454
|
+
createInspectoMcpRuntime,
|
|
6455
|
+
resolveInspectoServerBaseUrl,
|
|
6090
6456
|
onboard,
|
|
6091
6457
|
plan,
|
|
6092
6458
|
teardown,
|