aiden-runtime 4.8.1 → 4.9.1
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/README.md +88 -1
- package/dist/cli/v4/aidenCLI.js +37 -6
- package/dist/cli/v4/chatSession.js +53 -13
- package/dist/cli/v4/commands/daemon.js +53 -3
- package/dist/cli/v4/commands/daemonDoctor.js +212 -0
- package/dist/cli/v4/commands/daemonStatus.js +45 -26
- package/dist/cli/v4/commands/help.js +5 -0
- package/dist/cli/v4/commands/hooks.js +466 -0
- package/dist/cli/v4/commands/hooksSlash.js +33 -0
- package/dist/cli/v4/commands/index.js +13 -1
- package/dist/cli/v4/commands/mcp.js +89 -1
- package/dist/cli/v4/commands/mcpClientInstall.js +359 -0
- package/dist/cli/v4/commands/memory.js +707 -0
- package/dist/cli/v4/commands/memorySlash.js +38 -0
- package/dist/cli/v4/commands/recovery.js +1 -1
- package/dist/cli/v4/commands/skin.js +7 -0
- package/dist/cli/v4/commands/theme.js +217 -0
- package/dist/cli/v4/commands/trigger.js +1 -1
- package/dist/cli/v4/design/tokens.js +52 -4
- package/dist/cli/v4/display.js +39 -26
- package/dist/cli/v4/replyRenderer.js +6 -5
- package/dist/cli/v4/skinEngine.js +67 -0
- package/dist/cli/v4/ui/progressBar.js +179 -0
- package/dist/cli/v4/util/closestAction.js +48 -0
- package/dist/core/v4/aidenAgent.js +45 -2
- package/dist/core/v4/daemon/api/runs.js +131 -0
- package/dist/core/v4/daemon/bootstrap.js +368 -13
- package/dist/core/v4/daemon/db/migrations.js +169 -0
- package/dist/core/v4/daemon/idempotency/runIdempotencyStore.js +128 -0
- package/dist/core/v4/daemon/incarnationStore.js +47 -0
- package/dist/core/v4/daemon/runs/attemptStore.js +67 -0
- package/dist/core/v4/daemon/runs/reclaim.js +88 -0
- package/dist/core/v4/daemon/runs/retryPolicy.js +73 -0
- package/dist/core/v4/daemon/runs/runWithRetry.js +80 -0
- package/dist/core/v4/daemon/runs/stuckAttemptWatchdog.js +72 -0
- package/dist/core/v4/daemon/spans/spanHelpers.js +272 -0
- package/dist/core/v4/daemon/spans/spanStore.js +113 -0
- package/dist/core/v4/daemon/triggerBus.js +50 -19
- package/dist/core/v4/hooks/auditQuery.js +67 -0
- package/dist/core/v4/hooks/dispatcher.js +286 -0
- package/dist/core/v4/hooks/index.js +46 -0
- package/dist/core/v4/hooks/lifecycle.js +27 -0
- package/dist/core/v4/hooks/manifest.js +142 -0
- package/dist/core/v4/hooks/registry.js +149 -0
- package/dist/core/v4/hooks/runtime/subprocessRunner.js +188 -0
- package/dist/core/v4/hooks/toolHookGate.js +76 -0
- package/dist/core/v4/hooks/trust.js +14 -0
- package/dist/core/v4/identity/contextManager.js +83 -0
- package/dist/core/v4/identity/daemonId.js +85 -0
- package/dist/core/v4/identity/enforcement.js +103 -0
- package/dist/core/v4/identity/executionContext.js +153 -0
- package/dist/core/v4/identity/hookExecution.js +62 -0
- package/dist/core/v4/identity/httpContext.js +68 -0
- package/dist/core/v4/identity/ids.js +185 -0
- package/dist/core/v4/identity/index.js +60 -0
- package/dist/core/v4/identity/subprocessContext.js +98 -0
- package/dist/core/v4/identity/traceparent.js +114 -0
- package/dist/core/v4/logger/index.js +3 -1
- package/dist/core/v4/logger/logger.js +28 -1
- package/dist/core/v4/logger/redact.js +149 -0
- package/dist/core/v4/logger/sinks/fileSink.js +13 -0
- package/dist/core/v4/logger/sinks/stdSink.js +19 -1
- package/dist/core/v4/mcp/install/backup.js +78 -0
- package/dist/core/v4/mcp/install/clientPaths.js +90 -0
- package/dist/core/v4/mcp/install/clients.js +203 -0
- package/dist/core/v4/mcp/install/healthCheck.js +83 -0
- package/dist/core/v4/mcp/install/jsoncMerge.js +174 -0
- package/dist/core/v4/mcp/install/profiles.js +109 -0
- package/dist/core/v4/mcp/install/wslDetect.js +62 -0
- package/dist/core/v4/memory/namespaceRegistry.js +117 -0
- package/dist/core/v4/memory/projectRoot.js +76 -0
- package/dist/core/v4/memory/reviewer/index.js +162 -0
- package/dist/core/v4/memory/reviewer/pendingStore.js +136 -0
- package/dist/core/v4/memory/reviewer/prompt.js +105 -0
- package/dist/core/v4/memory/reviewer/skipRules.js +92 -0
- package/dist/core/v4/memoryManager.js +57 -10
- package/dist/core/v4/paths.js +2 -0
- package/dist/core/v4/subagent/spawnSubAgent.js +20 -7
- package/dist/core/v4/theme/bundledThemes.js +106 -0
- package/dist/core/v4/theme/themeLoader.js +160 -0
- package/dist/core/v4/theme/themeRegistry.js +97 -0
- package/dist/core/v4/theme/themeWatcher.js +95 -0
- package/dist/core/v4/toolRegistry.js +71 -8
- package/dist/core/v4/update/depWarningFilter.js +76 -0
- package/dist/core/v4/update/executeInstall.js +41 -35
- package/dist/core/v4/update/platformInstructions.js +128 -0
- package/dist/moat/approvalEngine.js +4 -0
- package/dist/moat/memoryGuard.js +8 -1
- package/dist/providers/v4/anthropicAdapter.js +10 -4
- package/dist/tools/v4/backends/local.js +19 -2
- package/dist/tools/v4/sessions/recallSession.js +6 -1
- package/package.json +3 -1
- package/plugins/aiden-plugin-chatgpt-plus/index.js +10 -1
- package/themes/default.yaml +52 -0
- package/themes/dracula.yaml +32 -0
- package/themes/light.yaml +32 -0
- package/themes/monochrome.yaml +31 -0
- package/themes/tokyo-night.yaml +32 -0
- package/dist/core/pluginSystem.js +0 -121
- package/dist/tools/v4/ui/_uiSmokeTool.js +0 -60
|
@@ -32,6 +32,39 @@
|
|
|
32
32
|
* tools by default; opting in means the user accepted server-side
|
|
33
33
|
* execution risk at config time.
|
|
34
34
|
*/
|
|
35
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
36
|
+
if (k2 === undefined) k2 = k;
|
|
37
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
38
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
39
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
40
|
+
}
|
|
41
|
+
Object.defineProperty(o, k2, desc);
|
|
42
|
+
}) : (function(o, m, k, k2) {
|
|
43
|
+
if (k2 === undefined) k2 = k;
|
|
44
|
+
o[k2] = m[k];
|
|
45
|
+
}));
|
|
46
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
47
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
48
|
+
}) : function(o, v) {
|
|
49
|
+
o["default"] = v;
|
|
50
|
+
});
|
|
51
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
52
|
+
var ownKeys = function(o) {
|
|
53
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
54
|
+
var ar = [];
|
|
55
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
56
|
+
return ar;
|
|
57
|
+
};
|
|
58
|
+
return ownKeys(o);
|
|
59
|
+
};
|
|
60
|
+
return function (mod) {
|
|
61
|
+
if (mod && mod.__esModule) return mod;
|
|
62
|
+
var result = {};
|
|
63
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
64
|
+
__setModuleDefault(result, mod);
|
|
65
|
+
return result;
|
|
66
|
+
};
|
|
67
|
+
})();
|
|
35
68
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
69
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
70
|
};
|
|
@@ -319,8 +352,55 @@ async function wireSubagentFanout(opts) {
|
|
|
319
352
|
async function runMcpSubcommand(action, opts = {}) {
|
|
320
353
|
const writeOut = opts.writeOut ?? ((t) => process.stdout.write(t));
|
|
321
354
|
const writeErr = opts.writeErr ?? ((t) => process.stderr.write(t));
|
|
355
|
+
const extraArgs = opts.args ?? [];
|
|
356
|
+
const target = opts.target;
|
|
322
357
|
switch (action) {
|
|
323
358
|
case 'serve': {
|
|
359
|
+
// v4.9.0 Slice 2a — `--health-check` flag: spawn the runtime,
|
|
360
|
+
// emit a single JSON status line on stdout, exit. Used by
|
|
361
|
+
// `aiden mcp init <client>` to confirm the wired entry actually
|
|
362
|
+
// launches successfully on the user's machine.
|
|
363
|
+
if (extraArgs.includes('--health-check') || target === '--health-check') {
|
|
364
|
+
try {
|
|
365
|
+
const { registry, skillLoader } = await buildMcpRuntime(opts);
|
|
366
|
+
const diag = await (0, diagnostics_1.collectMcpDiagnostics)(registry, skillLoader);
|
|
367
|
+
writeOut(JSON.stringify({
|
|
368
|
+
status: 'ok',
|
|
369
|
+
tools: diag.toolsExposed,
|
|
370
|
+
skills: diag.skillsTotal,
|
|
371
|
+
version: diag.build,
|
|
372
|
+
}) + '\n');
|
|
373
|
+
return 0;
|
|
374
|
+
}
|
|
375
|
+
catch (err) {
|
|
376
|
+
writeOut(JSON.stringify({
|
|
377
|
+
status: 'error',
|
|
378
|
+
error: err.message,
|
|
379
|
+
}) + '\n');
|
|
380
|
+
return 1;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// v4.9.0 Slice 2b — `--profile <name>` resolves a tool allowlist
|
|
384
|
+
// and installs it into the env BEFORE buildMcpRuntime runs (which
|
|
385
|
+
// internally calls readToolBridgeEnv). Flag wins over inherited
|
|
386
|
+
// env vars so the client-config-pinned profile is authoritative.
|
|
387
|
+
const profileIdx = extraArgs.indexOf('--profile');
|
|
388
|
+
if (profileIdx !== -1) {
|
|
389
|
+
const profileName = extraArgs[profileIdx + 1];
|
|
390
|
+
if (!profileName) {
|
|
391
|
+
writeErr('--profile requires a name.\n');
|
|
392
|
+
return 1;
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
const { resolveProfile, applyProfileToEnv } = await Promise.resolve().then(() => __importStar(require('../../../core/v4/mcp/install/profiles')));
|
|
396
|
+
const profile = resolveProfile(profileName, '');
|
|
397
|
+
applyProfileToEnv(profile);
|
|
398
|
+
}
|
|
399
|
+
catch (err) {
|
|
400
|
+
writeErr(`${err.message}\n`);
|
|
401
|
+
return 1;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
324
404
|
const { registry, skillLoader, toolContext, logger } = await buildMcpRuntime(opts);
|
|
325
405
|
await (0, stdioServer_1.startStdioMcpServer)({
|
|
326
406
|
registry,
|
|
@@ -375,9 +455,17 @@ async function runMcpSubcommand(action, opts = {}) {
|
|
|
375
455
|
}
|
|
376
456
|
return 0;
|
|
377
457
|
}
|
|
458
|
+
case 'init':
|
|
459
|
+
case 'doctor':
|
|
460
|
+
case 'repair':
|
|
461
|
+
case 'uninstall': {
|
|
462
|
+
// v4.9.0 Slice 2a / 2b — client-config install / diagnose / fix / remove.
|
|
463
|
+
const { runClientCommand } = await Promise.resolve().then(() => __importStar(require('./mcpClientInstall')));
|
|
464
|
+
return runClientCommand(action, target, extraArgs, { writeOut, writeErr });
|
|
465
|
+
}
|
|
378
466
|
default: {
|
|
379
467
|
writeErr(`Unknown 'aiden mcp' action: ${action}\n`);
|
|
380
|
-
writeErr(`Actions: serve | status | tools
|
|
468
|
+
writeErr(`Actions: serve | status | tools | init <client> | doctor <client> | repair <client> | uninstall <client>\n`);
|
|
381
469
|
return 1;
|
|
382
470
|
}
|
|
383
471
|
}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
4
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
5
|
+
*
|
|
6
|
+
* Aiden — local-first agent.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* cli/v4/commands/mcpClientInstall.ts — v4.9.0 Slice 2a.
|
|
10
|
+
*
|
|
11
|
+
* Handlers for `aiden mcp init|doctor|repair <client>`. Pure user-
|
|
12
|
+
* facing orchestration over the install primitives in
|
|
13
|
+
* `core/v4/mcp/install/`.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.runHealthCheck = exports.findLatestBackup = exports.countBackups = exports.detectWsl = exports.buildAidenEntry = exports.installClient = exports.planInstall = exports.readClient = exports.resolveClientPath = void 0;
|
|
17
|
+
exports.runClientCommand = runClientCommand;
|
|
18
|
+
const node_fs_1 = require("node:fs");
|
|
19
|
+
const clientPaths_1 = require("../../../core/v4/mcp/install/clientPaths");
|
|
20
|
+
Object.defineProperty(exports, "resolveClientPath", { enumerable: true, get: function () { return clientPaths_1.resolveClientPath; } });
|
|
21
|
+
const clients_1 = require("../../../core/v4/mcp/install/clients");
|
|
22
|
+
Object.defineProperty(exports, "installClient", { enumerable: true, get: function () { return clients_1.installClient; } });
|
|
23
|
+
Object.defineProperty(exports, "planInstall", { enumerable: true, get: function () { return clients_1.planInstall; } });
|
|
24
|
+
Object.defineProperty(exports, "readClient", { enumerable: true, get: function () { return clients_1.readClient; } });
|
|
25
|
+
const jsoncMerge_1 = require("../../../core/v4/mcp/install/jsoncMerge");
|
|
26
|
+
const wslDetect_1 = require("../../../core/v4/mcp/install/wslDetect");
|
|
27
|
+
Object.defineProperty(exports, "detectWsl", { enumerable: true, get: function () { return wslDetect_1.detectWsl; } });
|
|
28
|
+
Object.defineProperty(exports, "buildAidenEntry", { enumerable: true, get: function () { return wslDetect_1.buildAidenEntry; } });
|
|
29
|
+
const backup_1 = require("../../../core/v4/mcp/install/backup");
|
|
30
|
+
Object.defineProperty(exports, "countBackups", { enumerable: true, get: function () { return backup_1.countBackups; } });
|
|
31
|
+
Object.defineProperty(exports, "findLatestBackup", { enumerable: true, get: function () { return backup_1.findLatestBackup; } });
|
|
32
|
+
const healthCheck_1 = require("../../../core/v4/mcp/install/healthCheck");
|
|
33
|
+
Object.defineProperty(exports, "runHealthCheck", { enumerable: true, get: function () { return healthCheck_1.runHealthCheck; } });
|
|
34
|
+
const profiles_1 = require("../../../core/v4/mcp/install/profiles");
|
|
35
|
+
const SUPPORTED = new Set(['claude', 'cursor', 'vscode']);
|
|
36
|
+
function isClientId(s) {
|
|
37
|
+
return typeof s === 'string' && SUPPORTED.has(s);
|
|
38
|
+
}
|
|
39
|
+
function usage(io, action) {
|
|
40
|
+
io.writeErr(`Usage: aiden mcp ${action} <client> [--dry-run] [--print-snippet] [--yes] [--profile <name>]\n`);
|
|
41
|
+
io.writeErr(`Supported clients: ${Array.from(SUPPORTED).join(', ')}\n`);
|
|
42
|
+
io.writeErr(`Profiles: ${profiles_1.PROFILE_NAMES.join(', ')}\n`);
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
/** Pull `--profile <name>` from extraArgs if present. Throws on missing arg. */
|
|
46
|
+
function extractProfileFlag(extraArgs) {
|
|
47
|
+
const idx = extraArgs.indexOf('--profile');
|
|
48
|
+
if (idx === -1)
|
|
49
|
+
return undefined;
|
|
50
|
+
const value = extraArgs[idx + 1];
|
|
51
|
+
if (!value || value.startsWith('--')) {
|
|
52
|
+
throw new Error(`--profile requires a name. Available: ${profiles_1.PROFILE_NAMES.join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
async function runClientCommand(action, client, extraArgs, io) {
|
|
57
|
+
if (!isClientId(client))
|
|
58
|
+
return usage(io, action);
|
|
59
|
+
const dryRun = extraArgs.includes('--dry-run');
|
|
60
|
+
const printSnippet = extraArgs.includes('--print-snippet');
|
|
61
|
+
const yes = extraArgs.includes('--yes');
|
|
62
|
+
let profileName;
|
|
63
|
+
try {
|
|
64
|
+
profileName = extractProfileFlag(extraArgs);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
io.writeErr(`${err.message}\n`);
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
if (action === 'init')
|
|
71
|
+
return doInit(client, io, { dryRun, printSnippet, yes, profileName });
|
|
72
|
+
if (action === 'doctor')
|
|
73
|
+
return doDoctor(client, io);
|
|
74
|
+
if (action === 'repair')
|
|
75
|
+
return doRepair(client, io, { yes, profileName });
|
|
76
|
+
return doUninstall(client, io, { dryRun, yes });
|
|
77
|
+
}
|
|
78
|
+
async function doInit(client, io, opts) {
|
|
79
|
+
// v4.9.0 Slice 2b — resolve profile (explicit --profile or client default).
|
|
80
|
+
// Profile name flows into both the spawn args AND the _aiden.profile marker.
|
|
81
|
+
let profile;
|
|
82
|
+
try {
|
|
83
|
+
profile = (0, profiles_1.resolveProfile)(opts.profileName, client);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
io.writeErr(`${err.message}\n`);
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
const wsl = (0, wslDetect_1.detectWsl)();
|
|
90
|
+
const baseEntry = (0, wslDetect_1.buildAidenEntry)({ wsl, target: wsl.inWsl ? 'host' : undefined });
|
|
91
|
+
// Append `--profile <name>` to args so the wired serve command
|
|
92
|
+
// launches with the right tool allowlist.
|
|
93
|
+
const entry = {
|
|
94
|
+
command: baseEntry.command,
|
|
95
|
+
args: [...baseEntry.args, '--profile', profile.name],
|
|
96
|
+
};
|
|
97
|
+
const planned = (0, clients_1.planInstall)(client, { command: entry.command, args: entry.args, profile: profile.name });
|
|
98
|
+
if (!planned) {
|
|
99
|
+
io.writeErr(`Could not resolve config path for ${client}.\n`);
|
|
100
|
+
return 1;
|
|
101
|
+
}
|
|
102
|
+
if (opts.printSnippet) {
|
|
103
|
+
// Emit just the Aiden entry as a JSON blob the user can paste.
|
|
104
|
+
// Use the resolved schema so VS Code gets `servers.aiden` + type:'stdio'.
|
|
105
|
+
const obj = (0, jsoncMerge_1.buildAidenEntryObject)({
|
|
106
|
+
command: entry.command,
|
|
107
|
+
args: entry.args,
|
|
108
|
+
profile: profile.name,
|
|
109
|
+
schema: planned.resolution.schema,
|
|
110
|
+
});
|
|
111
|
+
const top = {};
|
|
112
|
+
top[planned.resolution.schema.topKey] = { aiden: obj };
|
|
113
|
+
io.writeOut(JSON.stringify(top, null, 2) + '\n');
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
if (planned.parentMissing) {
|
|
117
|
+
// v4.9.0 Slice 2a hotfix #1 — clearer diagnostic. If the user
|
|
118
|
+
// believes the client IS installed (file is right there in
|
|
119
|
+
// Explorer / Finder), the most likely cause is that they're
|
|
120
|
+
// running an older `aiden` bin from npm that doesn't have these
|
|
121
|
+
// commands at all — globally-installed aiden-runtime predates
|
|
122
|
+
// v4.9.0. Surface that hypothesis explicitly so they don't chase
|
|
123
|
+
// a phantom path-detection bug.
|
|
124
|
+
io.writeErr(`${planned.resolution.displayName} doesn't appear to be installed.\n` +
|
|
125
|
+
`Expected parent dir: ${planned.resolution.parentDir}\n` +
|
|
126
|
+
`Install ${planned.resolution.displayName} first, or run with --print-snippet to copy the entry manually.\n` +
|
|
127
|
+
`\n` +
|
|
128
|
+
`If you're sure the client IS installed and you see this error,\n` +
|
|
129
|
+
`check that your \`aiden\` bin is the current dev build (not a\n` +
|
|
130
|
+
`published version): run \`which aiden\` (POSIX) or \`where aiden\`\n` +
|
|
131
|
+
`(Windows) and verify the resolved path matches your local repo.\n`);
|
|
132
|
+
return 1;
|
|
133
|
+
}
|
|
134
|
+
if (opts.dryRun) {
|
|
135
|
+
io.writeOut(`[dry-run] Would write to: ${planned.resolution.configPath}\n`);
|
|
136
|
+
io.writeOut(`[dry-run] New content (${planned.resolution.format}):\n`);
|
|
137
|
+
io.writeOut(planned.newText);
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
if (!opts.yes) {
|
|
141
|
+
io.writeOut(`About to write Aiden's MCP entry to:\n ${planned.resolution.configPath}\n`);
|
|
142
|
+
io.writeOut(`A timestamped backup will be created first. Continue? [Y/n]\n`);
|
|
143
|
+
// Best-effort confirmation: non-TTY callers (CI, npx) implicitly
|
|
144
|
+
// pass --yes; interactive callers can answer at the prompt below.
|
|
145
|
+
if (process.stdin.isTTY) {
|
|
146
|
+
const answer = await readOneLine();
|
|
147
|
+
if (answer && /^n/i.test(answer)) {
|
|
148
|
+
io.writeOut('Aborted.\n');
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const result = (0, clients_1.installClient)(client, { command: entry.command, args: entry.args });
|
|
154
|
+
if (result.outcome === 'error') {
|
|
155
|
+
io.writeErr(`Install failed: ${result.error}\n`);
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
158
|
+
if (result.outcome === 'noop') {
|
|
159
|
+
io.writeOut(`${planned.resolution.displayName}: Aiden entry already up to date.\n`);
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
io.writeOut(`✓ Wrote Aiden entry to ${result.configPath}\n`);
|
|
163
|
+
if (result.backupPath) {
|
|
164
|
+
io.writeOut(` Backup: ${result.backupPath}\n`);
|
|
165
|
+
}
|
|
166
|
+
// Connection health check — spawn the wired command + parse JSON.
|
|
167
|
+
io.writeOut('Running health check…\n');
|
|
168
|
+
const health = await (0, healthCheck_1.runHealthCheck)({ command: entry.command, args: entry.args });
|
|
169
|
+
if (health.ok) {
|
|
170
|
+
io.writeOut(`✓ Health check ok — ${health.tools ?? '?'} tools exposed, build ${health.version ?? '?'}\n`);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
io.writeOut(`⚠ Health check failed: ${health.error}\n`);
|
|
174
|
+
io.writeOut(` The entry is written; you can retry with /aiden mcp doctor ${client}.\n`);
|
|
175
|
+
}
|
|
176
|
+
// Restart guidance — per-client hint.
|
|
177
|
+
io.writeOut(`\nRestart ${planned.resolution.displayName} to load Aiden as an MCP server.\n`);
|
|
178
|
+
if (client === 'cursor') {
|
|
179
|
+
io.writeOut(' (In Cursor: Developer → Reload Window also picks up the change.)\n');
|
|
180
|
+
}
|
|
181
|
+
if (client === 'vscode') {
|
|
182
|
+
io.writeOut(' (Run VS Code\'s "Developer: Reload Window" command to reload the workspace MCP config without restarting VS Code.)\n');
|
|
183
|
+
}
|
|
184
|
+
io.writeOut(`Profile: ${profile.name} — ${profile.description}\n`);
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
async function doDoctor(client, io) {
|
|
188
|
+
const { resolution, entry, exists, text } = (0, clients_1.readClient)(client);
|
|
189
|
+
io.writeOut(`${resolution.displayName} MCP diagnosis\n`);
|
|
190
|
+
io.writeOut(` Config path: ${resolution.configPath}\n`);
|
|
191
|
+
if (!exists) {
|
|
192
|
+
io.writeOut(` ✗ Config file does not exist.\n`);
|
|
193
|
+
io.writeOut(` Hint: run "aiden mcp init ${client}" to create it.\n`);
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
io.writeOut(` ✓ Config file exists\n`);
|
|
197
|
+
// Validate JSON / JSONC parses.
|
|
198
|
+
let parseOk = false;
|
|
199
|
+
try {
|
|
200
|
+
if (resolution.format === 'json')
|
|
201
|
+
JSON.parse(text ?? '');
|
|
202
|
+
// jsonc-parser tolerates everything; only plain-JSON.parse can fail
|
|
203
|
+
parseOk = true;
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
io.writeOut(` ✗ Config is malformed: ${err.message}\n`);
|
|
207
|
+
const latest = (0, backup_1.findLatestBackup)(resolution.configPath);
|
|
208
|
+
if (latest) {
|
|
209
|
+
io.writeOut(` Latest backup: ${latest}\n`);
|
|
210
|
+
io.writeOut(` Hint: aiden mcp repair ${client} — restores from the latest backup.\n`);
|
|
211
|
+
}
|
|
212
|
+
return 1;
|
|
213
|
+
}
|
|
214
|
+
if (parseOk)
|
|
215
|
+
io.writeOut(` ✓ Config parses cleanly\n`);
|
|
216
|
+
if (!entry) {
|
|
217
|
+
io.writeOut(` ✗ Aiden entry missing under mcpServers.aiden\n`);
|
|
218
|
+
io.writeOut(` Hint: aiden mcp init ${client}\n`);
|
|
219
|
+
return 1;
|
|
220
|
+
}
|
|
221
|
+
io.writeOut(` ✓ Aiden entry present\n`);
|
|
222
|
+
const wsl = (0, wslDetect_1.detectWsl)();
|
|
223
|
+
const expected = (0, wslDetect_1.buildAidenEntry)({ wsl, target: wsl.inWsl ? 'host' : undefined });
|
|
224
|
+
const cmdOk = entry.command === expected.command;
|
|
225
|
+
const argsOk = JSON.stringify(entry.args) === JSON.stringify(expected.args);
|
|
226
|
+
io.writeOut(` ${cmdOk ? '✓' : '✗'} command: ${entry.command} (expected ${expected.command})\n`);
|
|
227
|
+
io.writeOut(` ${argsOk ? '✓' : '✗'} args: ${JSON.stringify(entry.args)} (expected ${JSON.stringify(expected.args)})\n`);
|
|
228
|
+
const managed = entry._aiden?.managed === true;
|
|
229
|
+
io.writeOut(` ${managed ? '✓' : '⚠'} Managed by Aiden: ${managed ? 'yes' : 'no — entry exists but was authored externally'}\n`);
|
|
230
|
+
if (entry._aiden?.profile) {
|
|
231
|
+
io.writeOut(` Profile (pinned): ${entry._aiden.profile}\n`);
|
|
232
|
+
}
|
|
233
|
+
io.writeOut(` Backups: ${(0, backup_1.countBackups)(resolution.configPath)} file(s)\n`);
|
|
234
|
+
// Health check.
|
|
235
|
+
const health = await (0, healthCheck_1.runHealthCheck)({ command: entry.command, args: entry.args });
|
|
236
|
+
if (health.ok) {
|
|
237
|
+
io.writeOut(` ✓ Health check: ${health.tools ?? '?'} tools, build ${health.version ?? '?'}\n`);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
io.writeOut(` ✗ Health check: ${health.error}\n`);
|
|
241
|
+
}
|
|
242
|
+
const allGood = cmdOk && argsOk && managed && health.ok;
|
|
243
|
+
return allGood ? 0 : 1;
|
|
244
|
+
}
|
|
245
|
+
async function doRepair(client, io, opts) {
|
|
246
|
+
const { resolution, entry, exists, text } = (0, clients_1.readClient)(client);
|
|
247
|
+
if (!exists) {
|
|
248
|
+
io.writeOut(`${resolution.displayName}: config doesn't exist. Running init instead.\n`);
|
|
249
|
+
return doInit(client, io, { dryRun: false, printSnippet: false, yes: opts.yes, profileName: opts.profileName });
|
|
250
|
+
}
|
|
251
|
+
// Try to parse the config; if it's broken, offer a restore.
|
|
252
|
+
let parseOk = true;
|
|
253
|
+
if (resolution.format === 'json') {
|
|
254
|
+
try {
|
|
255
|
+
JSON.parse(text ?? '');
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
parseOk = false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (!parseOk) {
|
|
262
|
+
const latest = (0, backup_1.findLatestBackup)(resolution.configPath);
|
|
263
|
+
if (!latest) {
|
|
264
|
+
io.writeErr(`Config is malformed and no backup is available. Aborting.\n`);
|
|
265
|
+
return 1;
|
|
266
|
+
}
|
|
267
|
+
io.writeOut(`Config is malformed. Restoring from backup: ${latest}\n`);
|
|
268
|
+
try {
|
|
269
|
+
(0, node_fs_1.copyFileSync)(latest, resolution.configPath);
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
io.writeErr(`Restore failed: ${err.message}\n`);
|
|
273
|
+
return 1;
|
|
274
|
+
}
|
|
275
|
+
io.writeOut(`✓ Restored. Re-running init to ensure Aiden entry is present.\n`);
|
|
276
|
+
return doInit(client, io, { dryRun: false, printSnippet: false, yes: true });
|
|
277
|
+
}
|
|
278
|
+
const wsl = (0, wslDetect_1.detectWsl)();
|
|
279
|
+
let profile;
|
|
280
|
+
try {
|
|
281
|
+
profile = (0, profiles_1.resolveProfile)(opts.profileName ?? entry?._aiden?.profile, client);
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
io.writeErr(`${err.message}\n`);
|
|
285
|
+
return 1;
|
|
286
|
+
}
|
|
287
|
+
const expectedBase = (0, wslDetect_1.buildAidenEntry)({ wsl, target: wsl.inWsl ? 'host' : undefined });
|
|
288
|
+
const expectedArgs = [...expectedBase.args, '--profile', profile.name];
|
|
289
|
+
const cmdOk = entry?.command === expectedBase.command;
|
|
290
|
+
const argsOk = JSON.stringify(entry?.args) === JSON.stringify(expectedArgs);
|
|
291
|
+
const profileOk = entry?._aiden?.profile === profile.name;
|
|
292
|
+
if (entry && cmdOk && argsOk && profileOk) {
|
|
293
|
+
io.writeOut(`${resolution.displayName}: entry already correct, nothing to repair.\n`);
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
296
|
+
io.writeOut(`${resolution.displayName}: updating stale entry to current values (profile: ${profile.name}).\n`);
|
|
297
|
+
return doInit(client, io, { dryRun: false, printSnippet: false, yes: true, profileName: profile.name });
|
|
298
|
+
}
|
|
299
|
+
async function doUninstall(client, io, opts) {
|
|
300
|
+
const planned = (0, clients_1.planUninstall)(client);
|
|
301
|
+
if (!planned.willRemove) {
|
|
302
|
+
io.writeOut(`Aiden not configured for ${planned.resolution.displayName}. Nothing to do.\n`);
|
|
303
|
+
return 0;
|
|
304
|
+
}
|
|
305
|
+
const managed = planned.entry?._aiden?.managed === true;
|
|
306
|
+
if (!managed && !opts.yes) {
|
|
307
|
+
io.writeOut(`The aiden entry in ${planned.resolution.displayName}'s config wasn't installed by\n`);
|
|
308
|
+
io.writeOut(`'aiden mcp init' (no _aiden.managed marker). Proceed anyway? [y/N]\n`);
|
|
309
|
+
if (process.stdin.isTTY) {
|
|
310
|
+
const answer = await readOneLine();
|
|
311
|
+
if (!answer || !/^y/i.test(answer)) {
|
|
312
|
+
io.writeOut('Aborted.\n');
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
io.writeOut('Non-interactive shell — pass --yes to confirm removal of an unmanaged entry.\n');
|
|
318
|
+
return 1;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (opts.dryRun) {
|
|
322
|
+
io.writeOut(`[dry-run] Would remove aiden entry from: ${planned.resolution.configPath}\n`);
|
|
323
|
+
io.writeOut(`[dry-run] Managed by Aiden: ${managed ? 'yes' : 'no (unmanaged)'}\n`);
|
|
324
|
+
io.writeOut(`[dry-run] New content (${planned.resolution.format}):\n`);
|
|
325
|
+
io.writeOut(planned.newText);
|
|
326
|
+
return 0;
|
|
327
|
+
}
|
|
328
|
+
const result = (0, clients_1.uninstallClient)(client);
|
|
329
|
+
if (result.outcome === 'error') {
|
|
330
|
+
io.writeErr(`Uninstall failed: ${result.error}\n`);
|
|
331
|
+
return 1;
|
|
332
|
+
}
|
|
333
|
+
if (result.outcome === 'noop') {
|
|
334
|
+
io.writeOut(`Aiden not configured for ${planned.resolution.displayName}. Nothing to do.\n`);
|
|
335
|
+
return 0;
|
|
336
|
+
}
|
|
337
|
+
io.writeOut(`✓ Removed aiden entry from ${result.configPath}\n`);
|
|
338
|
+
if (result.backupPath)
|
|
339
|
+
io.writeOut(` Backup: ${result.backupPath}\n`);
|
|
340
|
+
io.writeOut(`\nRestart ${planned.resolution.displayName} to drop the now-disconnected Aiden MCP server.\n`);
|
|
341
|
+
return 0;
|
|
342
|
+
}
|
|
343
|
+
/** Read one line from stdin. Best-effort; tolerates non-TTY callers. */
|
|
344
|
+
function readOneLine() {
|
|
345
|
+
return new Promise((resolve) => {
|
|
346
|
+
let buf = '';
|
|
347
|
+
const onData = (chunk) => {
|
|
348
|
+
buf += chunk.toString();
|
|
349
|
+
if (buf.includes('\n')) {
|
|
350
|
+
process.stdin.removeListener('data', onData);
|
|
351
|
+
resolve(buf.split('\n')[0].trim());
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
process.stdin.on('data', onData);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
// Silence unused-import lint when only used as type.
|
|
358
|
+
void node_fs_1.readFileSync;
|
|
359
|
+
void node_fs_1.existsSync;
|