aiden-runtime 4.8.0 → 4.9.0
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 +35 -4
- package/dist/cli/v4/chatSession.js +43 -16
- package/dist/cli/v4/commands/daemon.js +47 -2
- package/dist/cli/v4/commands/daemonDoctor.js +212 -0
- package/dist/cli/v4/commands/daemonStatus.js +1 -1
- package/dist/cli/v4/commands/help.js +2 -0
- package/dist/cli/v4/commands/hooks.js +428 -0
- package/dist/cli/v4/commands/index.js +5 -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 +702 -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/commands/update.js +14 -2
- package/dist/cli/v4/design/tokens.js +52 -4
- package/dist/cli/v4/display.js +102 -46
- package/dist/cli/v4/pasteIntercept.js +214 -70
- package/dist/cli/v4/replyRenderer.js +145 -5
- package/dist/cli/v4/skinEngine.js +67 -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/promptBuilder.js +6 -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/executeInstall.js +10 -6
- package/dist/core/v4/update/installMethodDetect.js +7 -0
- package/dist/core/version.js +67 -2
- 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 -3
- 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
|
@@ -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;
|