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,428 @@
|
|
|
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/hooks.ts — v4.9.0 Slice 12b.
|
|
10
|
+
*
|
|
11
|
+
* aiden hooks alias for `list`
|
|
12
|
+
* aiden hooks list [--json]
|
|
13
|
+
* aiden hooks show <hook_id>
|
|
14
|
+
* aiden hooks trust <hook_id> [--yes] (risk warning + y/N prompt unless --yes)
|
|
15
|
+
* aiden hooks revoke <hook_id> [--yes]
|
|
16
|
+
* aiden hooks rescan
|
|
17
|
+
* aiden hooks test <hook_id> [--event <name>] [--payload <json>]
|
|
18
|
+
* aiden hooks doctor [--json] [--fix]
|
|
19
|
+
* aiden hooks audit [--hook id] [--event n] [--since iso]
|
|
20
|
+
* [--status s] [--limit n] [--json]
|
|
21
|
+
*
|
|
22
|
+
* Doctor's `--fix` is intentionally conservative: it creates the hooks
|
|
23
|
+
* directory and runs a rescan, but NEVER auto-trusts anything (trust
|
|
24
|
+
* remains an explicit, deliberate user action).
|
|
25
|
+
*/
|
|
26
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
|
+
};
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.runHooksSubcommand = runHooksSubcommand;
|
|
31
|
+
const node_fs_1 = require("node:fs");
|
|
32
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
33
|
+
const node_readline_1 = __importDefault(require("node:readline"));
|
|
34
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
35
|
+
const paths_1 = require("../../../core/v4/paths");
|
|
36
|
+
const daemonConfig_1 = require("../../../core/v4/daemon/daemonConfig");
|
|
37
|
+
const migrations_1 = require("../../../core/v4/daemon/db/migrations");
|
|
38
|
+
const registry_1 = require("../../../core/v4/hooks/registry");
|
|
39
|
+
const trust_1 = require("../../../core/v4/hooks/trust");
|
|
40
|
+
const dispatcher_1 = require("../../../core/v4/hooks/dispatcher");
|
|
41
|
+
const auditQuery_1 = require("../../../core/v4/hooks/auditQuery");
|
|
42
|
+
const projectRoot_1 = require("../../../core/v4/memory/projectRoot");
|
|
43
|
+
const noopOut = (s) => { process.stdout.write(s); };
|
|
44
|
+
const noopErr = (s) => { process.stderr.write(s); };
|
|
45
|
+
async function runHooksSubcommand(action, args, opts = {}) {
|
|
46
|
+
const out = opts.writeOut ?? noopOut;
|
|
47
|
+
const err = opts.writeErr ?? noopErr;
|
|
48
|
+
const json = args.includes('--json');
|
|
49
|
+
const yes = args.includes('--yes');
|
|
50
|
+
const positional = args.filter((a) => !a.startsWith('--'));
|
|
51
|
+
const paths = (0, paths_1.resolveAidenPaths)(opts.rootDir ? { rootOverride: opts.rootDir } : {});
|
|
52
|
+
let db;
|
|
53
|
+
try {
|
|
54
|
+
const dbp = opts.dbPath ?? (0, daemonConfig_1.daemonDbPath)(paths.root);
|
|
55
|
+
await node_fs_1.promises.mkdir(node_path_1.default.dirname(dbp), { recursive: true });
|
|
56
|
+
db = new better_sqlite3_1.default(dbp);
|
|
57
|
+
db.pragma('foreign_keys = ON');
|
|
58
|
+
(0, migrations_1.runMigrations)(db);
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
err(`hooks: cannot open daemon db: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
const ctx = {
|
|
65
|
+
out, err, db, paths, json, yes, positional, args,
|
|
66
|
+
promptYesNo: opts.promptYesNo ?? defaultPromptYesNo,
|
|
67
|
+
};
|
|
68
|
+
const effective = action || 'list';
|
|
69
|
+
try {
|
|
70
|
+
switch (effective) {
|
|
71
|
+
case 'list': return await cmdList(ctx);
|
|
72
|
+
case 'show': return await cmdShow(ctx);
|
|
73
|
+
case 'trust': return await cmdTrust(ctx);
|
|
74
|
+
case 'revoke': return await cmdRevoke(ctx);
|
|
75
|
+
case 'rescan': return await cmdRescan(ctx);
|
|
76
|
+
case 'test': return await cmdTest(ctx);
|
|
77
|
+
case 'doctor': return await cmdDoctor(ctx);
|
|
78
|
+
case 'audit': return await cmdAudit(ctx);
|
|
79
|
+
case '--help':
|
|
80
|
+
case 'help': return cmdHelp(out);
|
|
81
|
+
default:
|
|
82
|
+
err(`Unknown hooks action: ${effective}\n`);
|
|
83
|
+
cmdHelp(err);
|
|
84
|
+
return 2;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
try {
|
|
89
|
+
db.close();
|
|
90
|
+
}
|
|
91
|
+
catch { /* noop */ }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function cmdHelp(w) {
|
|
95
|
+
w('Usage: aiden hooks <action> [args...] [--json] [--yes]\n\n' +
|
|
96
|
+
'Manage Aiden\'s manifest-driven hook subsystem.\n\n' +
|
|
97
|
+
'Actions:\n' +
|
|
98
|
+
' list All discovered hooks (default).\n' +
|
|
99
|
+
' show <hook_id> Hook manifest + subscriptions + recent executions.\n' +
|
|
100
|
+
' trust <hook_id> [--yes] Mark trusted + enabled (confirmation required).\n' +
|
|
101
|
+
' revoke <hook_id> [--yes] Mark revoked + disabled (confirmation required).\n' +
|
|
102
|
+
' rescan Walk hook dirs, surface new + drifted.\n' +
|
|
103
|
+
' test <hook_id> [--event N] [--payload JSON]\n' +
|
|
104
|
+
' Dry-run invocation (no counter mutation).\n' +
|
|
105
|
+
' doctor [--json] [--fix] Pre-flight diagnostics.\n' +
|
|
106
|
+
' audit [--hook id] [--event N] [--status S] [--since ISO] [--limit N] [--json]\n' +
|
|
107
|
+
' Recent hook_executions rows.\n');
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
function defaultPromptYesNo(question) {
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
const rl = node_readline_1.default.createInterface({ input: process.stdin, output: process.stderr });
|
|
113
|
+
rl.question(question, (ans) => {
|
|
114
|
+
rl.close();
|
|
115
|
+
resolve(ans.trim().toLowerCase() === 'y' || ans.trim().toLowerCase() === 'yes');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function flagValue(args, name) {
|
|
120
|
+
const i = args.indexOf(name);
|
|
121
|
+
if (i < 0 || i + 1 >= args.length)
|
|
122
|
+
return undefined;
|
|
123
|
+
return args[i + 1];
|
|
124
|
+
}
|
|
125
|
+
function hooksWithSubs(db) {
|
|
126
|
+
const hooks = db.prepare(`SELECT *, consecutive_failures FROM hooks ORDER BY name`).all();
|
|
127
|
+
return hooks.map((h) => ({
|
|
128
|
+
...h,
|
|
129
|
+
subs: db.prepare(`SELECT * FROM hook_subscriptions WHERE hook_id = ? ORDER BY event, priority DESC`).all(h.hook_id),
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
async function cmdList(ctx) {
|
|
133
|
+
const rows = hooksWithSubs(ctx.db);
|
|
134
|
+
if (ctx.json) {
|
|
135
|
+
ctx.out(JSON.stringify(rows, null, 2) + '\n');
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
if (rows.length === 0) {
|
|
139
|
+
ctx.out('no hooks discovered (drop a HOOK.yaml under ~/.aiden/hooks/<name>/ then run `aiden hooks rescan`)\n');
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
for (const h of rows) {
|
|
143
|
+
const stateLabel = `${h.trust_state}, ${h.enabled ? 'enabled' : 'disabled'}`;
|
|
144
|
+
ctx.out(`${h.hook_id} ${h.name.padEnd(24)} ${stateLabel.padEnd(24)} ${h.source} (${h.subs.length} subscription${h.subs.length === 1 ? '' : 's'})\n`);
|
|
145
|
+
for (const s of h.subs) {
|
|
146
|
+
ctx.out(` ${s.event.padEnd(20)} authority=${s.authority.padEnd(18)} mode=${s.mode}\n`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
async function cmdShow(ctx) {
|
|
152
|
+
const hookId = ctx.positional[0];
|
|
153
|
+
if (!hookId) {
|
|
154
|
+
ctx.err('show: missing <hook_id>\n');
|
|
155
|
+
return 2;
|
|
156
|
+
}
|
|
157
|
+
const h = ctx.db.prepare(`SELECT *, consecutive_failures FROM hooks WHERE hook_id = ?`).get(hookId);
|
|
158
|
+
if (!h) {
|
|
159
|
+
ctx.err(`show: hook not found: ${hookId}\n`);
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
const subs = ctx.db.prepare(`SELECT * FROM hook_subscriptions WHERE hook_id = ? ORDER BY event, priority DESC`)
|
|
163
|
+
.all(hookId);
|
|
164
|
+
const grants = ctx.db.prepare(`SELECT capability, scope_json FROM hook_capability_grants WHERE hook_id = ?`)
|
|
165
|
+
.all(hookId);
|
|
166
|
+
const recent = (0, auditQuery_1.queryHookExecutions)(ctx.db, { hookId, limit: 10 });
|
|
167
|
+
if (ctx.json) {
|
|
168
|
+
ctx.out(JSON.stringify({ hook: h, subscriptions: subs, capabilities: grants, recent }, null, 2) + '\n');
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
ctx.out(`Hook: ${h.name} (${h.hook_id})\n`);
|
|
172
|
+
ctx.out(` state: ${h.trust_state}, ${h.enabled ? 'enabled' : 'disabled'}\n`);
|
|
173
|
+
ctx.out(` source: ${h.source}\n`);
|
|
174
|
+
ctx.out(` manifest: ${h.manifest_path}\n`);
|
|
175
|
+
ctx.out(` hash: ${h.code_hash.slice(0, 16)}...\n`);
|
|
176
|
+
ctx.out(` consecutive failures: ${h.consecutive_failures}\n`);
|
|
177
|
+
ctx.out('Subscriptions:\n');
|
|
178
|
+
for (const s of subs) {
|
|
179
|
+
ctx.out(` ${s.event} authority=${s.authority} mode=${s.mode} priority=${s.priority}\n`);
|
|
180
|
+
ctx.out(` timeout=${s.timeout_ms}ms on_error=${s.on_error} on_timeout=${s.on_timeout}\n`);
|
|
181
|
+
if (s.matcher_json)
|
|
182
|
+
ctx.out(` matcher=${s.matcher_json}\n`);
|
|
183
|
+
}
|
|
184
|
+
if (grants.length > 0) {
|
|
185
|
+
ctx.out('Capabilities (declared, not enforced):\n');
|
|
186
|
+
for (const g of grants)
|
|
187
|
+
ctx.out(` ${g.capability} ${g.scope_json}\n`);
|
|
188
|
+
}
|
|
189
|
+
ctx.out(`Recent executions (last ${recent.length}):\n`);
|
|
190
|
+
for (const r of recent) {
|
|
191
|
+
ctx.out(` ${r.started_at} ${r.event.padEnd(20)} ${r.status.padEnd(8)} ${r.elapsed_ms}ms\n`);
|
|
192
|
+
}
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
async function cmdTrust(ctx) {
|
|
196
|
+
const hookId = ctx.positional[0];
|
|
197
|
+
if (!hookId) {
|
|
198
|
+
ctx.err('trust: missing <hook_id>\n');
|
|
199
|
+
return 2;
|
|
200
|
+
}
|
|
201
|
+
const h = ctx.db.prepare(`SELECT * FROM hooks WHERE hook_id = ?`).get(hookId);
|
|
202
|
+
if (!h) {
|
|
203
|
+
ctx.err(`trust: hook not found: ${hookId}\n`);
|
|
204
|
+
return 1;
|
|
205
|
+
}
|
|
206
|
+
if (!ctx.yes) {
|
|
207
|
+
ctx.err(`Hook: ${h.name} (${h.hook_id})\n manifest: ${h.manifest_path}\n current state: ${h.trust_state}\n`);
|
|
208
|
+
ctx.err('RISK WARNING: This hook can affect tool execution. Only trust hooks you\'ve reviewed.\n');
|
|
209
|
+
const ok = await ctx.promptYesNo('Trust this hook? [y/N] ');
|
|
210
|
+
if (!ok) {
|
|
211
|
+
if (ctx.json)
|
|
212
|
+
ctx.out(JSON.stringify({ ok: false, reason: 'declined' }) + '\n');
|
|
213
|
+
else
|
|
214
|
+
ctx.err('trust: declined\n');
|
|
215
|
+
return 1;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
(0, trust_1.markTrusted)(ctx.db, hookId);
|
|
219
|
+
if (ctx.json)
|
|
220
|
+
ctx.out(JSON.stringify({ ok: true, hook_id: hookId, trust_state: 'trusted', enabled: 1 }) + '\n');
|
|
221
|
+
else
|
|
222
|
+
ctx.out(`trusted ${h.name} (${hookId})\n`);
|
|
223
|
+
return 0;
|
|
224
|
+
}
|
|
225
|
+
async function cmdRevoke(ctx) {
|
|
226
|
+
const hookId = ctx.positional[0];
|
|
227
|
+
if (!hookId) {
|
|
228
|
+
ctx.err('revoke: missing <hook_id>\n');
|
|
229
|
+
return 2;
|
|
230
|
+
}
|
|
231
|
+
const h = ctx.db.prepare(`SELECT * FROM hooks WHERE hook_id = ?`).get(hookId);
|
|
232
|
+
if (!h) {
|
|
233
|
+
ctx.err(`revoke: hook not found: ${hookId}\n`);
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
if (!ctx.yes) {
|
|
237
|
+
ctx.err(`Hook: ${h.name} (${h.hook_id}) current state: ${h.trust_state}\n`);
|
|
238
|
+
const ok = await ctx.promptYesNo('Revoke this hook? [y/N] ');
|
|
239
|
+
if (!ok) {
|
|
240
|
+
if (ctx.json)
|
|
241
|
+
ctx.out(JSON.stringify({ ok: false, reason: 'declined' }) + '\n');
|
|
242
|
+
else
|
|
243
|
+
ctx.err('revoke: declined\n');
|
|
244
|
+
return 1;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
(0, trust_1.markRevoked)(ctx.db, hookId);
|
|
248
|
+
if (ctx.json)
|
|
249
|
+
ctx.out(JSON.stringify({ ok: true, hook_id: hookId, trust_state: 'revoked', enabled: 0 }) + '\n');
|
|
250
|
+
else
|
|
251
|
+
ctx.out(`revoked ${h.name} (${hookId})\n`);
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
async function cmdRescan(ctx) {
|
|
255
|
+
const projectRoot = (0, projectRoot_1.findProjectRoot)(process.cwd());
|
|
256
|
+
const r = await (0, registry_1.scanAndLoadHooks)(ctx.db, { aidenRoot: ctx.paths.root, projectRoot });
|
|
257
|
+
if (ctx.json) {
|
|
258
|
+
ctx.out(JSON.stringify(r, null, 2) + '\n');
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
ctx.out(`scanned: loaded=${r.loaded} errored=${r.errored} drifted=${r.drifted}\n`);
|
|
262
|
+
for (const e of r.errors)
|
|
263
|
+
ctx.out(` ERR ${e.path}: ${e.message}\n`);
|
|
264
|
+
return 0;
|
|
265
|
+
}
|
|
266
|
+
async function cmdTest(ctx) {
|
|
267
|
+
const hookId = ctx.positional[0];
|
|
268
|
+
if (!hookId) {
|
|
269
|
+
ctx.err('test: missing <hook_id>\n');
|
|
270
|
+
return 2;
|
|
271
|
+
}
|
|
272
|
+
const h = ctx.db.prepare(`SELECT * FROM hooks WHERE hook_id = ?`).get(hookId);
|
|
273
|
+
if (!h) {
|
|
274
|
+
ctx.err(`test: hook not found: ${hookId}\n`);
|
|
275
|
+
return 1;
|
|
276
|
+
}
|
|
277
|
+
// Temporarily ignore enabled/trust_state by flipping to trusted+enabled
|
|
278
|
+
// ONLY in-memory for the test dispatch? Cleaner: dispatch reads its
|
|
279
|
+
// own subs filter; we'll invoke against a synthetic event matching
|
|
280
|
+
// the hook's first subscription. The CLI is opinionated: pick the
|
|
281
|
+
// hook's first subscription as the test target.
|
|
282
|
+
const sub = ctx.db.prepare(`SELECT event FROM hook_subscriptions WHERE hook_id = ? ORDER BY priority DESC LIMIT 1`)
|
|
283
|
+
.get(hookId);
|
|
284
|
+
const event = flagValue(ctx.args, '--event') ?? sub?.event ?? 'tool.call.pre';
|
|
285
|
+
let payload = { tool_name: 'echo', synthetic: true };
|
|
286
|
+
const payloadRaw = flagValue(ctx.args, '--payload');
|
|
287
|
+
if (payloadRaw) {
|
|
288
|
+
try {
|
|
289
|
+
payload = JSON.parse(payloadRaw);
|
|
290
|
+
}
|
|
291
|
+
catch (e) {
|
|
292
|
+
ctx.err(`test: invalid --payload JSON: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
293
|
+
return 2;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// To test even when untrusted/disabled, briefly flip state in-memory.
|
|
297
|
+
// We use a savepoint to ensure the flip is rolled back after.
|
|
298
|
+
const restoreState = h.trust_state;
|
|
299
|
+
const restoreEnabled = h.enabled;
|
|
300
|
+
ctx.db.prepare(`UPDATE hooks SET trust_state='trusted', enabled=1 WHERE hook_id=?`).run(hookId);
|
|
301
|
+
try {
|
|
302
|
+
const result = await (0, dispatcher_1.dispatchHook)(ctx.db, event, payload, { testMode: true, runId: 'test', traceId: 'test' });
|
|
303
|
+
const fired = result.fired.find((f) => f.hookId === hookId);
|
|
304
|
+
const exec = ctx.db.prepare(`SELECT * FROM hook_executions WHERE hook_id = ? ORDER BY started_at DESC LIMIT 1`).get(hookId);
|
|
305
|
+
if (ctx.json) {
|
|
306
|
+
ctx.out(JSON.stringify({ event, payload, dispatched: result, exec, fired }, null, 2) + '\n');
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
ctx.out(`Test fired ${h.name} on ${event}:\n`);
|
|
310
|
+
ctx.out(` status: ${fired?.status ?? 'n/a'}\n`);
|
|
311
|
+
ctx.out(` decision: ${fired?.decision ?? 'n/a'}\n`);
|
|
312
|
+
ctx.out(` elapsed: ${fired?.elapsedMs ?? 0}ms\n`);
|
|
313
|
+
if (exec?.error_message)
|
|
314
|
+
ctx.out(` error: ${exec.error_message}\n`);
|
|
315
|
+
if (result.decision === 'block')
|
|
316
|
+
ctx.out(` AGGREGATE: block (reason=${result.reason ?? 'n/a'})\n`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
finally {
|
|
320
|
+
ctx.db.prepare(`UPDATE hooks SET trust_state=?, enabled=? WHERE hook_id=?`).run(restoreState, restoreEnabled, hookId);
|
|
321
|
+
}
|
|
322
|
+
return 0;
|
|
323
|
+
}
|
|
324
|
+
async function cmdDoctor(ctx) {
|
|
325
|
+
const fix = ctx.args.includes('--fix');
|
|
326
|
+
const checks = [];
|
|
327
|
+
const hooksDir = node_path_1.default.join(ctx.paths.root, 'hooks');
|
|
328
|
+
// 1. hooks dir exists
|
|
329
|
+
let dirExists = false;
|
|
330
|
+
try {
|
|
331
|
+
await node_fs_1.promises.access(hooksDir);
|
|
332
|
+
dirExists = true;
|
|
333
|
+
}
|
|
334
|
+
catch { /* missing */ }
|
|
335
|
+
if (!dirExists && fix) {
|
|
336
|
+
await node_fs_1.promises.mkdir(hooksDir, { recursive: true });
|
|
337
|
+
dirExists = true;
|
|
338
|
+
}
|
|
339
|
+
checks.push({
|
|
340
|
+
name: 'hooks_dir_exists',
|
|
341
|
+
status: dirExists ? 'ok' : 'warn',
|
|
342
|
+
detail: dirExists ? hooksDir : `missing: ${hooksDir} (run with --fix to create)`,
|
|
343
|
+
fixable: !dirExists,
|
|
344
|
+
});
|
|
345
|
+
// 2. schema v12
|
|
346
|
+
const v = ctx.db.prepare(`SELECT version FROM schema_version`).get();
|
|
347
|
+
checks.push({
|
|
348
|
+
name: 'schema_v12_current',
|
|
349
|
+
status: v && v.version >= 12 ? 'ok' : 'error',
|
|
350
|
+
detail: `db schema version = ${v?.version ?? 0}, latest = ${migrations_1.LATEST_SCHEMA_VERSION}`,
|
|
351
|
+
fixable: false,
|
|
352
|
+
});
|
|
353
|
+
// 3. parseable hooks
|
|
354
|
+
const rows = (0, registry_1.listHooks)(ctx.db);
|
|
355
|
+
const drifted = rows.filter((r) => r.trust_state === 'drifted');
|
|
356
|
+
const untrusted = rows.filter((r) => r.trust_state === 'untrusted');
|
|
357
|
+
const trustedEnabled = rows.filter((r) => r.trust_state === 'trusted' && r.enabled === 1);
|
|
358
|
+
checks.push({
|
|
359
|
+
name: 'drift_count',
|
|
360
|
+
status: drifted.length === 0 ? 'ok' : 'warn',
|
|
361
|
+
detail: `${drifted.length} drifted hook(s) (re-trust to re-enable)`,
|
|
362
|
+
fixable: false,
|
|
363
|
+
});
|
|
364
|
+
checks.push({
|
|
365
|
+
name: 'untrusted_count',
|
|
366
|
+
status: untrusted.length === 0 ? 'ok' : 'warn',
|
|
367
|
+
detail: `${untrusted.length} untrusted hook(s) installed but not yet enabled`,
|
|
368
|
+
fixable: false,
|
|
369
|
+
});
|
|
370
|
+
checks.push({
|
|
371
|
+
name: 'trusted_enabled_count',
|
|
372
|
+
status: 'ok',
|
|
373
|
+
detail: `${trustedEnabled.length} trusted + enabled hook(s)`,
|
|
374
|
+
fixable: false,
|
|
375
|
+
});
|
|
376
|
+
// 4. auto-disabled last 24h — revoked + non-zero recent executions
|
|
377
|
+
const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
|
378
|
+
const recentStatus = (0, auditQuery_1.countByStatus)(ctx.db, since);
|
|
379
|
+
const recentNonOk = (recentStatus.crash ?? 0) + (recentStatus.timeout ?? 0) + (recentStatus.malformed_output ?? 0);
|
|
380
|
+
const recentRevoked = ctx.db.prepare(`SELECT COUNT(*) AS n FROM hooks WHERE trust_state='revoked' AND updated_at >= ?`).get(since);
|
|
381
|
+
checks.push({
|
|
382
|
+
name: 'auto_disabled_24h',
|
|
383
|
+
status: recentRevoked.n === 0 ? 'ok' : 'warn',
|
|
384
|
+
detail: `${recentRevoked.n} hook(s) revoked in last 24h (${recentNonOk} non-ok executions)`,
|
|
385
|
+
fixable: false,
|
|
386
|
+
});
|
|
387
|
+
// 5. high-failure-rate hooks
|
|
388
|
+
const fr = (0, auditQuery_1.failureRates)(ctx.db, 100).filter((f) => f.failureRate > 0.1);
|
|
389
|
+
checks.push({
|
|
390
|
+
name: 'high_failure_rate',
|
|
391
|
+
status: fr.length === 0 ? 'ok' : 'warn',
|
|
392
|
+
detail: fr.length === 0 ? 'no hooks > 10% failure last 100 executions' : `${fr.length} hook(s) > 10% failure: ${fr.map((r) => r.hook_id).join(', ')}`,
|
|
393
|
+
fixable: false,
|
|
394
|
+
});
|
|
395
|
+
if (ctx.json) {
|
|
396
|
+
ctx.out(JSON.stringify({ checks, fix }, null, 2) + '\n');
|
|
397
|
+
return checks.some((c) => c.status === 'error') ? 1 : 0;
|
|
398
|
+
}
|
|
399
|
+
for (const c of checks) {
|
|
400
|
+
const icon = c.status === 'ok' ? '[ok] ' : c.status === 'warn' ? '[warn] ' : '[ERR] ';
|
|
401
|
+
ctx.out(`${icon}${c.name.padEnd(24)} ${c.detail}\n`);
|
|
402
|
+
}
|
|
403
|
+
return checks.some((c) => c.status === 'error') ? 1 : 0;
|
|
404
|
+
}
|
|
405
|
+
async function cmdAudit(ctx) {
|
|
406
|
+
const limitRaw = flagValue(ctx.args, '--limit');
|
|
407
|
+
const q = {
|
|
408
|
+
hookId: flagValue(ctx.args, '--hook'),
|
|
409
|
+
event: flagValue(ctx.args, '--event'),
|
|
410
|
+
status: flagValue(ctx.args, '--status'),
|
|
411
|
+
since: flagValue(ctx.args, '--since'),
|
|
412
|
+
limit: limitRaw ? Number.parseInt(limitRaw, 10) : 50,
|
|
413
|
+
};
|
|
414
|
+
const rows = (0, auditQuery_1.queryHookExecutions)(ctx.db, q);
|
|
415
|
+
if (ctx.json) {
|
|
416
|
+
ctx.out(JSON.stringify(rows, null, 2) + '\n');
|
|
417
|
+
return 0;
|
|
418
|
+
}
|
|
419
|
+
if (rows.length === 0) {
|
|
420
|
+
ctx.out('no executions match\n');
|
|
421
|
+
return 0;
|
|
422
|
+
}
|
|
423
|
+
ctx.out(`${'started_at'.padEnd(26)} ${'hook'.padEnd(18)} ${'event'.padEnd(20)} ${'status'.padEnd(10)} ${'decision'.padEnd(10)} elapsed\n`);
|
|
424
|
+
for (const r of rows) {
|
|
425
|
+
ctx.out(`${r.started_at.padEnd(26)} ${(r.hook_name ?? r.hook_id).slice(0, 18).padEnd(18)} ${r.event.padEnd(20)} ${r.status.padEnd(10)} ${(r.decision ?? '-').padEnd(10)} ${r.elapsed_ms}ms\n`);
|
|
426
|
+
}
|
|
427
|
+
return 0;
|
|
428
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* and registers each on the global CommandRegistry at boot.
|
|
13
13
|
*/
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.allCommands = exports.walkthrough = exports.recovery = exports.spawnPause = exports.plannerGuard = exports.suggestions = exports.daemonStatus = exports.browserDepth = exports.tce = exports.sandbox = exports.update = exports.reloadSoul = exports.history = exports.show = exports.status = exports.voice = exports.channel = exports.setup = exports.cron = exports.doctor = exports.license = exports.auth = exports.plugins = exports.streaming = exports.debugPrompt = exports.identity = exports.providers = exports.quit = exports.clear = exports.verbose = exports.reasoning = exports.reloadMcp = exports.skills = exports.skin = exports.yolo = exports.usage = exports.compress = exports.title = exports.save = exports.personality = exports.model = exports.tools = exports.help = void 0;
|
|
15
|
+
exports.allCommands = exports.walkthrough = exports.recovery = exports.spawnPause = exports.plannerGuard = exports.suggestions = exports.daemonStatus = exports.browserDepth = exports.tce = exports.sandbox = exports.update = exports.reloadSoul = exports.history = exports.show = exports.status = exports.voice = exports.channel = exports.setup = exports.cron = exports.doctor = exports.license = exports.auth = exports.plugins = exports.streaming = exports.debugPrompt = exports.identity = exports.providers = exports.quit = exports.clear = exports.verbose = exports.reasoning = exports.reloadMcp = exports.skills = exports.theme = exports.skin = exports.yolo = exports.usage = exports.compress = exports.title = exports.save = exports.personality = exports.model = exports.tools = exports.help = void 0;
|
|
16
16
|
const help_1 = require("./help");
|
|
17
17
|
Object.defineProperty(exports, "help", { enumerable: true, get: function () { return help_1.help; } });
|
|
18
18
|
const tools_1 = require("./tools");
|
|
@@ -33,6 +33,9 @@ const yolo_1 = require("./yolo");
|
|
|
33
33
|
Object.defineProperty(exports, "yolo", { enumerable: true, get: function () { return yolo_1.yolo; } });
|
|
34
34
|
const skin_1 = require("./skin");
|
|
35
35
|
Object.defineProperty(exports, "skin", { enumerable: true, get: function () { return skin_1.skin; } });
|
|
36
|
+
// v4.9.0 Slice 1a — unified theme system.
|
|
37
|
+
const theme_1 = require("./theme");
|
|
38
|
+
Object.defineProperty(exports, "theme", { enumerable: true, get: function () { return theme_1.theme; } });
|
|
36
39
|
const skills_1 = require("./skills");
|
|
37
40
|
Object.defineProperty(exports, "skills", { enumerable: true, get: function () { return skills_1.skills; } });
|
|
38
41
|
const reloadMcp_1 = require("./reloadMcp");
|
|
@@ -119,6 +122,7 @@ exports.allCommands = [
|
|
|
119
122
|
usage_1.usage,
|
|
120
123
|
yolo_1.yolo,
|
|
121
124
|
skin_1.skin,
|
|
125
|
+
theme_1.theme,
|
|
122
126
|
skills_1.skills,
|
|
123
127
|
plugins_1.plugins,
|
|
124
128
|
auth_1.auth,
|
|
@@ -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
|
}
|