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.
Files changed (100) hide show
  1. package/README.md +88 -1
  2. package/dist/cli/v4/aidenCLI.js +37 -6
  3. package/dist/cli/v4/chatSession.js +53 -13
  4. package/dist/cli/v4/commands/daemon.js +53 -3
  5. package/dist/cli/v4/commands/daemonDoctor.js +212 -0
  6. package/dist/cli/v4/commands/daemonStatus.js +45 -26
  7. package/dist/cli/v4/commands/help.js +5 -0
  8. package/dist/cli/v4/commands/hooks.js +466 -0
  9. package/dist/cli/v4/commands/hooksSlash.js +33 -0
  10. package/dist/cli/v4/commands/index.js +13 -1
  11. package/dist/cli/v4/commands/mcp.js +89 -1
  12. package/dist/cli/v4/commands/mcpClientInstall.js +359 -0
  13. package/dist/cli/v4/commands/memory.js +707 -0
  14. package/dist/cli/v4/commands/memorySlash.js +38 -0
  15. package/dist/cli/v4/commands/recovery.js +1 -1
  16. package/dist/cli/v4/commands/skin.js +7 -0
  17. package/dist/cli/v4/commands/theme.js +217 -0
  18. package/dist/cli/v4/commands/trigger.js +1 -1
  19. package/dist/cli/v4/design/tokens.js +52 -4
  20. package/dist/cli/v4/display.js +39 -26
  21. package/dist/cli/v4/replyRenderer.js +6 -5
  22. package/dist/cli/v4/skinEngine.js +67 -0
  23. package/dist/cli/v4/ui/progressBar.js +179 -0
  24. package/dist/cli/v4/util/closestAction.js +48 -0
  25. package/dist/core/v4/aidenAgent.js +45 -2
  26. package/dist/core/v4/daemon/api/runs.js +131 -0
  27. package/dist/core/v4/daemon/bootstrap.js +368 -13
  28. package/dist/core/v4/daemon/db/migrations.js +169 -0
  29. package/dist/core/v4/daemon/idempotency/runIdempotencyStore.js +128 -0
  30. package/dist/core/v4/daemon/incarnationStore.js +47 -0
  31. package/dist/core/v4/daemon/runs/attemptStore.js +67 -0
  32. package/dist/core/v4/daemon/runs/reclaim.js +88 -0
  33. package/dist/core/v4/daemon/runs/retryPolicy.js +73 -0
  34. package/dist/core/v4/daemon/runs/runWithRetry.js +80 -0
  35. package/dist/core/v4/daemon/runs/stuckAttemptWatchdog.js +72 -0
  36. package/dist/core/v4/daemon/spans/spanHelpers.js +272 -0
  37. package/dist/core/v4/daemon/spans/spanStore.js +113 -0
  38. package/dist/core/v4/daemon/triggerBus.js +50 -19
  39. package/dist/core/v4/hooks/auditQuery.js +67 -0
  40. package/dist/core/v4/hooks/dispatcher.js +286 -0
  41. package/dist/core/v4/hooks/index.js +46 -0
  42. package/dist/core/v4/hooks/lifecycle.js +27 -0
  43. package/dist/core/v4/hooks/manifest.js +142 -0
  44. package/dist/core/v4/hooks/registry.js +149 -0
  45. package/dist/core/v4/hooks/runtime/subprocessRunner.js +188 -0
  46. package/dist/core/v4/hooks/toolHookGate.js +76 -0
  47. package/dist/core/v4/hooks/trust.js +14 -0
  48. package/dist/core/v4/identity/contextManager.js +83 -0
  49. package/dist/core/v4/identity/daemonId.js +85 -0
  50. package/dist/core/v4/identity/enforcement.js +103 -0
  51. package/dist/core/v4/identity/executionContext.js +153 -0
  52. package/dist/core/v4/identity/hookExecution.js +62 -0
  53. package/dist/core/v4/identity/httpContext.js +68 -0
  54. package/dist/core/v4/identity/ids.js +185 -0
  55. package/dist/core/v4/identity/index.js +60 -0
  56. package/dist/core/v4/identity/subprocessContext.js +98 -0
  57. package/dist/core/v4/identity/traceparent.js +114 -0
  58. package/dist/core/v4/logger/index.js +3 -1
  59. package/dist/core/v4/logger/logger.js +28 -1
  60. package/dist/core/v4/logger/redact.js +149 -0
  61. package/dist/core/v4/logger/sinks/fileSink.js +13 -0
  62. package/dist/core/v4/logger/sinks/stdSink.js +19 -1
  63. package/dist/core/v4/mcp/install/backup.js +78 -0
  64. package/dist/core/v4/mcp/install/clientPaths.js +90 -0
  65. package/dist/core/v4/mcp/install/clients.js +203 -0
  66. package/dist/core/v4/mcp/install/healthCheck.js +83 -0
  67. package/dist/core/v4/mcp/install/jsoncMerge.js +174 -0
  68. package/dist/core/v4/mcp/install/profiles.js +109 -0
  69. package/dist/core/v4/mcp/install/wslDetect.js +62 -0
  70. package/dist/core/v4/memory/namespaceRegistry.js +117 -0
  71. package/dist/core/v4/memory/projectRoot.js +76 -0
  72. package/dist/core/v4/memory/reviewer/index.js +162 -0
  73. package/dist/core/v4/memory/reviewer/pendingStore.js +136 -0
  74. package/dist/core/v4/memory/reviewer/prompt.js +105 -0
  75. package/dist/core/v4/memory/reviewer/skipRules.js +92 -0
  76. package/dist/core/v4/memoryManager.js +57 -10
  77. package/dist/core/v4/paths.js +2 -0
  78. package/dist/core/v4/subagent/spawnSubAgent.js +20 -7
  79. package/dist/core/v4/theme/bundledThemes.js +106 -0
  80. package/dist/core/v4/theme/themeLoader.js +160 -0
  81. package/dist/core/v4/theme/themeRegistry.js +97 -0
  82. package/dist/core/v4/theme/themeWatcher.js +95 -0
  83. package/dist/core/v4/toolRegistry.js +71 -8
  84. package/dist/core/v4/update/depWarningFilter.js +76 -0
  85. package/dist/core/v4/update/executeInstall.js +41 -35
  86. package/dist/core/v4/update/platformInstructions.js +128 -0
  87. package/dist/moat/approvalEngine.js +4 -0
  88. package/dist/moat/memoryGuard.js +8 -1
  89. package/dist/providers/v4/anthropicAdapter.js +10 -4
  90. package/dist/tools/v4/backends/local.js +19 -2
  91. package/dist/tools/v4/sessions/recallSession.js +6 -1
  92. package/package.json +3 -1
  93. package/plugins/aiden-plugin-chatgpt-plus/index.js +10 -1
  94. package/themes/default.yaml +52 -0
  95. package/themes/dracula.yaml +32 -0
  96. package/themes/light.yaml +32 -0
  97. package/themes/monochrome.yaml +31 -0
  98. package/themes/tokyo-night.yaml +32 -0
  99. package/dist/core/pluginSystem.js +0 -121
  100. package/dist/tools/v4/ui/_uiSmokeTool.js +0 -60
@@ -22,44 +22,63 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
22
  return (mod && mod.__esModule) ? mod : { "default": mod };
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.daemonStatus = void 0;
25
+ exports.daemonStatus = exports.DAEMON_SHELL_ONLY = void 0;
26
+ exports.dispatchDaemonSlash = dispatchDaemonSlash;
26
27
  const node_fs_1 = __importDefault(require("node:fs"));
27
28
  const node_os_1 = __importDefault(require("node:os"));
28
29
  const node_path_1 = __importDefault(require("node:path"));
29
- const daemon_1 = require("../../../core/v4/daemon");
30
+ const daemon_1 = require("./daemon");
31
+ const daemon_2 = require("../../../core/v4/daemon");
30
32
  const paths_1 = require("../../../core/v4/paths");
33
+ /**
34
+ * v4.9.1 amendment — `/daemon` defaults to `doctor` and routes
35
+ * `doctor` / `logs` to the existing `runDaemonSubcommand`. The pure-
36
+ * REPL `/daemon status` (in-process snapshot) stays inline. Lifecycle
37
+ * ops shell-hint — they need terminal control we can't grant inside chat.
38
+ */
39
+ exports.DAEMON_SHELL_ONLY = new Set(['install', 'uninstall', 'start', 'stop', 'restart']);
40
+ async function dispatchDaemonSlash(opts) {
41
+ const a = (opts.action || 'doctor').toLowerCase();
42
+ if (exports.DAEMON_SHELL_ONLY.has(a)) {
43
+ opts.write(`⚠ /daemon ${a} not available inside chat (requires terminal control)\n`);
44
+ opts.write(' Quit (/quit) and run from shell:\n\n');
45
+ const tail = opts.args.length > 0 ? ' ' + opts.args.join(' ') : '';
46
+ opts.write(` aiden daemon ${a}${tail}\n`);
47
+ return;
48
+ }
49
+ if (a === 'status' && opts.paintStatus) {
50
+ try {
51
+ opts.paintStatus();
52
+ }
53
+ catch (e) {
54
+ opts.warn(`/daemon status: failed to read state (${e instanceof Error ? e.message : String(e)})`);
55
+ }
56
+ return;
57
+ }
58
+ await opts.runDaemon(a, opts.args, { writeOut: opts.write, writeErr: opts.write });
59
+ }
31
60
  exports.daemonStatus = {
32
61
  name: 'daemon',
33
- description: 'Show v4.5 daemon status (read-only). Use `aiden daemon` for lifecycle.',
62
+ description: 'Daemon diagnostics (doctor / status / logs).',
34
63
  category: 'system',
35
64
  icon: '⚙',
36
65
  handler: async (ctx) => {
37
- const sub = (ctx.args[0] ?? 'status').toLowerCase();
38
- if (sub !== 'status') {
39
- ctx.display.printError('Usage: /daemon status\n' +
40
- 'For lifecycle commands (install / start / stop / restart / logs), use the top-level CLI:\n' +
41
- ' aiden daemon install\n' +
42
- ' aiden daemon start\n' +
43
- ' aiden daemon stop\n' +
44
- ' aiden daemon status\n' +
45
- ' aiden daemon logs');
46
- return {};
47
- }
48
- try {
49
- const snapshot = readSnapshot();
50
- printSnapshot(snapshot, ctx);
51
- }
52
- catch (e) {
53
- ctx.display.warn(`/daemon status: failed to read state (${e instanceof Error ? e.message : String(e)})`);
54
- }
66
+ await dispatchDaemonSlash({
67
+ action: ctx.args[0] ?? 'doctor',
68
+ args: ctx.args.slice(1),
69
+ write: (s) => ctx.display.write(s),
70
+ warn: (s) => ctx.display.warn(s),
71
+ runDaemon: daemon_1.runDaemonSubcommand,
72
+ paintStatus: () => printSnapshot(readSnapshot(), ctx),
73
+ });
55
74
  return {};
56
75
  },
57
76
  };
58
77
  // ── Snapshot collector ─────────────────────────────────────────────────────
59
78
  function readSnapshot() {
60
79
  const aidenRoot = (0, paths_1.resolveAidenRoot)();
61
- const dbPath = (0, daemon_1.daemonDbPath)(aidenRoot);
62
- const lockPath = (0, daemon_1.daemonRuntimeLockPath)(aidenRoot);
80
+ const dbPath = (0, daemon_2.daemonDbPath)(aidenRoot);
81
+ const lockPath = (0, daemon_2.daemonRuntimeLockPath)(aidenRoot);
63
82
  // ── Liveness via the in-process bootstrap handle first, then fall
64
83
  // back to the runtime.lock PID check (covers the case where the
65
84
  // daemon is another process and we're a REPL inspecting its db).
@@ -67,11 +86,11 @@ function readSnapshot() {
67
86
  let instanceId = null;
68
87
  let port = null;
69
88
  let uptimeMs = null;
70
- const handle = (0, daemon_1.getDaemonHandle)();
89
+ const handle = (0, daemon_2.getDaemonHandle)();
71
90
  if (handle?.active && handle.instanceId) {
72
91
  running = true;
73
92
  instanceId = handle.instanceId;
74
- port = (0, daemon_1.getDaemonConfig)().port;
93
+ port = (0, daemon_2.getDaemonConfig)().port;
75
94
  if (handle.instanceTracker) {
76
95
  // instanceTracker has a `getStartedAt` if exposed; otherwise
77
96
  // derive from daemon_instances row below.
@@ -105,7 +124,7 @@ function readSnapshot() {
105
124
  dailyBudget: null,
106
125
  };
107
126
  }
108
- const db = (0, daemon_1.openDaemonDb)(dbPath);
127
+ const db = (0, daemon_2.openDaemonDb)(dbPath);
109
128
  // Uptime from the instance row when we have an instanceId.
110
129
  if (running && instanceId) {
111
130
  try {
@@ -31,6 +31,8 @@ exports.SUBSECTION_MAP = {
31
31
  providers: 'Configuration',
32
32
  personality: 'Configuration',
33
33
  skin: 'Configuration',
34
+ // v4.9.0 Slice 1a — unified theme system (parallel to /skin).
35
+ theme: 'Configuration',
34
36
  streaming: 'Configuration',
35
37
  reasoning: 'Configuration',
36
38
  verbose: 'Configuration',
@@ -74,6 +76,9 @@ exports.SUBSECTION_MAP = {
74
76
  recovery: 'System',
75
77
  // v4.6 ONB1 slice 10 — new-user guided tour.
76
78
  walkthrough: 'System',
79
+ // v4.9.1 amendment — REPL surfaces for memory + hooks (daemon already mapped).
80
+ memory: 'System',
81
+ hooks: 'System',
77
82
  // ── Authentication ──
78
83
  auth: 'Authentication',
79
84
  // ── Help ──
@@ -0,0 +1,466 @@
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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
27
+ if (k2 === undefined) k2 = k;
28
+ var desc = Object.getOwnPropertyDescriptor(m, k);
29
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
30
+ desc = { enumerable: true, get: function() { return m[k]; } };
31
+ }
32
+ Object.defineProperty(o, k2, desc);
33
+ }) : (function(o, m, k, k2) {
34
+ if (k2 === undefined) k2 = k;
35
+ o[k2] = m[k];
36
+ }));
37
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
38
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
39
+ }) : function(o, v) {
40
+ o["default"] = v;
41
+ });
42
+ var __importStar = (this && this.__importStar) || (function () {
43
+ var ownKeys = function(o) {
44
+ ownKeys = Object.getOwnPropertyNames || function (o) {
45
+ var ar = [];
46
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
47
+ return ar;
48
+ };
49
+ return ownKeys(o);
50
+ };
51
+ return function (mod) {
52
+ if (mod && mod.__esModule) return mod;
53
+ var result = {};
54
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
55
+ __setModuleDefault(result, mod);
56
+ return result;
57
+ };
58
+ })();
59
+ var __importDefault = (this && this.__importDefault) || function (mod) {
60
+ return (mod && mod.__esModule) ? mod : { "default": mod };
61
+ };
62
+ Object.defineProperty(exports, "__esModule", { value: true });
63
+ exports.runHooksSubcommand = runHooksSubcommand;
64
+ const node_fs_1 = require("node:fs");
65
+ const node_path_1 = __importDefault(require("node:path"));
66
+ const node_readline_1 = __importDefault(require("node:readline"));
67
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
68
+ const paths_1 = require("../../../core/v4/paths");
69
+ const daemonConfig_1 = require("../../../core/v4/daemon/daemonConfig");
70
+ const migrations_1 = require("../../../core/v4/daemon/db/migrations");
71
+ const registry_1 = require("../../../core/v4/hooks/registry");
72
+ const trust_1 = require("../../../core/v4/hooks/trust");
73
+ const dispatcher_1 = require("../../../core/v4/hooks/dispatcher");
74
+ const auditQuery_1 = require("../../../core/v4/hooks/auditQuery");
75
+ const projectRoot_1 = require("../../../core/v4/memory/projectRoot");
76
+ const noopOut = (s) => { process.stdout.write(s); };
77
+ const noopErr = (s) => { process.stderr.write(s); };
78
+ async function runHooksSubcommand(action, args, opts = {}) {
79
+ const out = opts.writeOut ?? noopOut;
80
+ const err = opts.writeErr ?? noopErr;
81
+ const json = args.includes('--json');
82
+ const yes = args.includes('--yes');
83
+ const positional = args.filter((a) => !a.startsWith('--'));
84
+ const paths = (0, paths_1.resolveAidenPaths)(opts.rootDir ? { rootOverride: opts.rootDir } : {});
85
+ let db;
86
+ try {
87
+ const dbp = opts.dbPath ?? (0, daemonConfig_1.daemonDbPath)(paths.root);
88
+ await node_fs_1.promises.mkdir(node_path_1.default.dirname(dbp), { recursive: true });
89
+ db = new better_sqlite3_1.default(dbp);
90
+ db.pragma('foreign_keys = ON');
91
+ (0, migrations_1.runMigrations)(db);
92
+ }
93
+ catch (e) {
94
+ err(`hooks: cannot open daemon db: ${e instanceof Error ? e.message : String(e)}\n`);
95
+ return 1;
96
+ }
97
+ const ctx = {
98
+ out, err, db, paths, json, yes, positional, args,
99
+ promptYesNo: opts.promptYesNo ?? defaultPromptYesNo,
100
+ };
101
+ const effective = action || 'list';
102
+ try {
103
+ switch (effective) {
104
+ case 'list': return await cmdList(ctx);
105
+ case 'show': return await cmdShow(ctx);
106
+ case 'trust': return await cmdTrust(ctx);
107
+ case 'revoke': return await cmdRevoke(ctx);
108
+ case 'rescan': return await cmdRescan(ctx);
109
+ case 'test': return await cmdTest(ctx);
110
+ case 'doctor': return await cmdDoctor(ctx);
111
+ case 'audit': return await cmdAudit(ctx);
112
+ case '--help':
113
+ case 'help': return cmdHelp(out);
114
+ default: {
115
+ err(`Unknown hooks action: ${effective}\n`);
116
+ const { closestAction } = await Promise.resolve().then(() => __importStar(require('../util/closestAction')));
117
+ const m = closestAction(effective, ['list', 'show', 'trust', 'revoke', 'rescan', 'test', 'doctor', 'audit']);
118
+ if (m)
119
+ err(`Did you mean: ${m}?\n\n`);
120
+ cmdHelp(err);
121
+ return 2;
122
+ }
123
+ }
124
+ }
125
+ finally {
126
+ try {
127
+ db.close();
128
+ }
129
+ catch { /* noop */ }
130
+ }
131
+ }
132
+ function cmdHelp(w) {
133
+ w('Usage: aiden hooks <action> [args...] [--json] [--yes]\n\n' +
134
+ 'Manage Aiden\'s manifest-driven hook subsystem.\n\n' +
135
+ 'Actions:\n' +
136
+ ' list All discovered hooks (default).\n' +
137
+ ' show <hook_id> Hook manifest + subscriptions + recent executions.\n' +
138
+ ' trust <hook_id> [--yes] Mark trusted + enabled (confirmation required).\n' +
139
+ ' revoke <hook_id> [--yes] Mark revoked + disabled (confirmation required).\n' +
140
+ ' rescan Walk hook dirs, surface new + drifted.\n' +
141
+ ' test <hook_id> [--event N] [--payload JSON]\n' +
142
+ ' Dry-run invocation (no counter mutation).\n' +
143
+ ' doctor [--json] [--fix] Pre-flight diagnostics.\n' +
144
+ ' audit [--hook id] [--event N] [--status S] [--since ISO] [--limit N] [--json]\n' +
145
+ ' Recent hook_executions rows.\n');
146
+ return 0;
147
+ }
148
+ function defaultPromptYesNo(question) {
149
+ return new Promise((resolve) => {
150
+ const rl = node_readline_1.default.createInterface({ input: process.stdin, output: process.stderr });
151
+ rl.question(question, (ans) => {
152
+ rl.close();
153
+ resolve(ans.trim().toLowerCase() === 'y' || ans.trim().toLowerCase() === 'yes');
154
+ });
155
+ });
156
+ }
157
+ function flagValue(args, name) {
158
+ const i = args.indexOf(name);
159
+ if (i < 0 || i + 1 >= args.length)
160
+ return undefined;
161
+ return args[i + 1];
162
+ }
163
+ function hooksWithSubs(db) {
164
+ const hooks = db.prepare(`SELECT *, consecutive_failures FROM hooks ORDER BY name`).all();
165
+ return hooks.map((h) => ({
166
+ ...h,
167
+ subs: db.prepare(`SELECT * FROM hook_subscriptions WHERE hook_id = ? ORDER BY event, priority DESC`).all(h.hook_id),
168
+ }));
169
+ }
170
+ async function cmdList(ctx) {
171
+ const rows = hooksWithSubs(ctx.db);
172
+ if (ctx.json) {
173
+ ctx.out(JSON.stringify(rows, null, 2) + '\n');
174
+ return 0;
175
+ }
176
+ if (rows.length === 0) {
177
+ ctx.out('no hooks discovered (drop a HOOK.yaml under ~/.aiden/hooks/<name>/ then run `aiden hooks rescan`)\n');
178
+ return 0;
179
+ }
180
+ for (const h of rows) {
181
+ const stateLabel = `${h.trust_state}, ${h.enabled ? 'enabled' : 'disabled'}`;
182
+ ctx.out(`${h.hook_id} ${h.name.padEnd(24)} ${stateLabel.padEnd(24)} ${h.source} (${h.subs.length} subscription${h.subs.length === 1 ? '' : 's'})\n`);
183
+ for (const s of h.subs) {
184
+ ctx.out(` ${s.event.padEnd(20)} authority=${s.authority.padEnd(18)} mode=${s.mode}\n`);
185
+ }
186
+ }
187
+ return 0;
188
+ }
189
+ async function cmdShow(ctx) {
190
+ const hookId = ctx.positional[0];
191
+ if (!hookId) {
192
+ ctx.err('show: missing <hook_id>\n');
193
+ return 2;
194
+ }
195
+ const h = ctx.db.prepare(`SELECT *, consecutive_failures FROM hooks WHERE hook_id = ?`).get(hookId);
196
+ if (!h) {
197
+ ctx.err(`show: hook not found: ${hookId}\n`);
198
+ return 1;
199
+ }
200
+ const subs = ctx.db.prepare(`SELECT * FROM hook_subscriptions WHERE hook_id = ? ORDER BY event, priority DESC`)
201
+ .all(hookId);
202
+ const grants = ctx.db.prepare(`SELECT capability, scope_json FROM hook_capability_grants WHERE hook_id = ?`)
203
+ .all(hookId);
204
+ const recent = (0, auditQuery_1.queryHookExecutions)(ctx.db, { hookId, limit: 10 });
205
+ if (ctx.json) {
206
+ ctx.out(JSON.stringify({ hook: h, subscriptions: subs, capabilities: grants, recent }, null, 2) + '\n');
207
+ return 0;
208
+ }
209
+ ctx.out(`Hook: ${h.name} (${h.hook_id})\n`);
210
+ ctx.out(` state: ${h.trust_state}, ${h.enabled ? 'enabled' : 'disabled'}\n`);
211
+ ctx.out(` source: ${h.source}\n`);
212
+ ctx.out(` manifest: ${h.manifest_path}\n`);
213
+ ctx.out(` hash: ${h.code_hash.slice(0, 16)}...\n`);
214
+ ctx.out(` consecutive failures: ${h.consecutive_failures}\n`);
215
+ ctx.out('Subscriptions:\n');
216
+ for (const s of subs) {
217
+ ctx.out(` ${s.event} authority=${s.authority} mode=${s.mode} priority=${s.priority}\n`);
218
+ ctx.out(` timeout=${s.timeout_ms}ms on_error=${s.on_error} on_timeout=${s.on_timeout}\n`);
219
+ if (s.matcher_json)
220
+ ctx.out(` matcher=${s.matcher_json}\n`);
221
+ }
222
+ if (grants.length > 0) {
223
+ ctx.out('Capabilities (declared, not enforced):\n');
224
+ for (const g of grants)
225
+ ctx.out(` ${g.capability} ${g.scope_json}\n`);
226
+ }
227
+ ctx.out(`Recent executions (last ${recent.length}):\n`);
228
+ for (const r of recent) {
229
+ ctx.out(` ${r.started_at} ${r.event.padEnd(20)} ${r.status.padEnd(8)} ${r.elapsed_ms}ms\n`);
230
+ }
231
+ return 0;
232
+ }
233
+ async function cmdTrust(ctx) {
234
+ const hookId = ctx.positional[0];
235
+ if (!hookId) {
236
+ ctx.err('trust: missing <hook_id>\n');
237
+ return 2;
238
+ }
239
+ const h = ctx.db.prepare(`SELECT * FROM hooks WHERE hook_id = ?`).get(hookId);
240
+ if (!h) {
241
+ ctx.err(`trust: hook not found: ${hookId}\n`);
242
+ return 1;
243
+ }
244
+ if (!ctx.yes) {
245
+ ctx.err(`Hook: ${h.name} (${h.hook_id})\n manifest: ${h.manifest_path}\n current state: ${h.trust_state}\n`);
246
+ ctx.err('RISK WARNING: This hook can affect tool execution. Only trust hooks you\'ve reviewed.\n');
247
+ const ok = await ctx.promptYesNo('Trust this hook? [y/N] ');
248
+ if (!ok) {
249
+ if (ctx.json)
250
+ ctx.out(JSON.stringify({ ok: false, reason: 'declined' }) + '\n');
251
+ else
252
+ ctx.err('trust: declined\n');
253
+ return 1;
254
+ }
255
+ }
256
+ (0, trust_1.markTrusted)(ctx.db, hookId);
257
+ if (ctx.json)
258
+ ctx.out(JSON.stringify({ ok: true, hook_id: hookId, trust_state: 'trusted', enabled: 1 }) + '\n');
259
+ else
260
+ ctx.out(`trusted ${h.name} (${hookId})\n`);
261
+ return 0;
262
+ }
263
+ async function cmdRevoke(ctx) {
264
+ const hookId = ctx.positional[0];
265
+ if (!hookId) {
266
+ ctx.err('revoke: missing <hook_id>\n');
267
+ return 2;
268
+ }
269
+ const h = ctx.db.prepare(`SELECT * FROM hooks WHERE hook_id = ?`).get(hookId);
270
+ if (!h) {
271
+ ctx.err(`revoke: hook not found: ${hookId}\n`);
272
+ return 1;
273
+ }
274
+ if (!ctx.yes) {
275
+ ctx.err(`Hook: ${h.name} (${h.hook_id}) current state: ${h.trust_state}\n`);
276
+ const ok = await ctx.promptYesNo('Revoke this hook? [y/N] ');
277
+ if (!ok) {
278
+ if (ctx.json)
279
+ ctx.out(JSON.stringify({ ok: false, reason: 'declined' }) + '\n');
280
+ else
281
+ ctx.err('revoke: declined\n');
282
+ return 1;
283
+ }
284
+ }
285
+ (0, trust_1.markRevoked)(ctx.db, hookId);
286
+ if (ctx.json)
287
+ ctx.out(JSON.stringify({ ok: true, hook_id: hookId, trust_state: 'revoked', enabled: 0 }) + '\n');
288
+ else
289
+ ctx.out(`revoked ${h.name} (${hookId})\n`);
290
+ return 0;
291
+ }
292
+ async function cmdRescan(ctx) {
293
+ const projectRoot = (0, projectRoot_1.findProjectRoot)(process.cwd());
294
+ const r = await (0, registry_1.scanAndLoadHooks)(ctx.db, { aidenRoot: ctx.paths.root, projectRoot });
295
+ if (ctx.json) {
296
+ ctx.out(JSON.stringify(r, null, 2) + '\n');
297
+ return 0;
298
+ }
299
+ ctx.out(`scanned: loaded=${r.loaded} errored=${r.errored} drifted=${r.drifted}\n`);
300
+ for (const e of r.errors)
301
+ ctx.out(` ERR ${e.path}: ${e.message}\n`);
302
+ return 0;
303
+ }
304
+ async function cmdTest(ctx) {
305
+ const hookId = ctx.positional[0];
306
+ if (!hookId) {
307
+ ctx.err('test: missing <hook_id>\n');
308
+ return 2;
309
+ }
310
+ const h = ctx.db.prepare(`SELECT * FROM hooks WHERE hook_id = ?`).get(hookId);
311
+ if (!h) {
312
+ ctx.err(`test: hook not found: ${hookId}\n`);
313
+ return 1;
314
+ }
315
+ // Temporarily ignore enabled/trust_state by flipping to trusted+enabled
316
+ // ONLY in-memory for the test dispatch? Cleaner: dispatch reads its
317
+ // own subs filter; we'll invoke against a synthetic event matching
318
+ // the hook's first subscription. The CLI is opinionated: pick the
319
+ // hook's first subscription as the test target.
320
+ const sub = ctx.db.prepare(`SELECT event FROM hook_subscriptions WHERE hook_id = ? ORDER BY priority DESC LIMIT 1`)
321
+ .get(hookId);
322
+ const event = flagValue(ctx.args, '--event') ?? sub?.event ?? 'tool.call.pre';
323
+ let payload = { tool_name: 'echo', synthetic: true };
324
+ const payloadRaw = flagValue(ctx.args, '--payload');
325
+ if (payloadRaw) {
326
+ try {
327
+ payload = JSON.parse(payloadRaw);
328
+ }
329
+ catch (e) {
330
+ ctx.err(`test: invalid --payload JSON: ${e instanceof Error ? e.message : String(e)}\n`);
331
+ return 2;
332
+ }
333
+ }
334
+ // To test even when untrusted/disabled, briefly flip state in-memory.
335
+ // We use a savepoint to ensure the flip is rolled back after.
336
+ const restoreState = h.trust_state;
337
+ const restoreEnabled = h.enabled;
338
+ ctx.db.prepare(`UPDATE hooks SET trust_state='trusted', enabled=1 WHERE hook_id=?`).run(hookId);
339
+ try {
340
+ const result = await (0, dispatcher_1.dispatchHook)(ctx.db, event, payload, { testMode: true, runId: 'test', traceId: 'test' });
341
+ const fired = result.fired.find((f) => f.hookId === hookId);
342
+ const exec = ctx.db.prepare(`SELECT * FROM hook_executions WHERE hook_id = ? ORDER BY started_at DESC LIMIT 1`).get(hookId);
343
+ if (ctx.json) {
344
+ ctx.out(JSON.stringify({ event, payload, dispatched: result, exec, fired }, null, 2) + '\n');
345
+ }
346
+ else {
347
+ ctx.out(`Test fired ${h.name} on ${event}:\n`);
348
+ ctx.out(` status: ${fired?.status ?? 'n/a'}\n`);
349
+ ctx.out(` decision: ${fired?.decision ?? 'n/a'}\n`);
350
+ ctx.out(` elapsed: ${fired?.elapsedMs ?? 0}ms\n`);
351
+ if (exec?.error_message)
352
+ ctx.out(` error: ${exec.error_message}\n`);
353
+ if (result.decision === 'block')
354
+ ctx.out(` AGGREGATE: block (reason=${result.reason ?? 'n/a'})\n`);
355
+ }
356
+ }
357
+ finally {
358
+ ctx.db.prepare(`UPDATE hooks SET trust_state=?, enabled=? WHERE hook_id=?`).run(restoreState, restoreEnabled, hookId);
359
+ }
360
+ return 0;
361
+ }
362
+ async function cmdDoctor(ctx) {
363
+ const fix = ctx.args.includes('--fix');
364
+ const checks = [];
365
+ const hooksDir = node_path_1.default.join(ctx.paths.root, 'hooks');
366
+ // 1. hooks dir exists
367
+ let dirExists = false;
368
+ try {
369
+ await node_fs_1.promises.access(hooksDir);
370
+ dirExists = true;
371
+ }
372
+ catch { /* missing */ }
373
+ if (!dirExists && fix) {
374
+ await node_fs_1.promises.mkdir(hooksDir, { recursive: true });
375
+ dirExists = true;
376
+ }
377
+ checks.push({
378
+ name: 'hooks_dir_exists',
379
+ status: dirExists ? 'ok' : 'warn',
380
+ detail: dirExists ? hooksDir : `missing: ${hooksDir} (run with --fix to create)`,
381
+ fixable: !dirExists,
382
+ });
383
+ // 2. schema v12
384
+ const v = ctx.db.prepare(`SELECT version FROM schema_version`).get();
385
+ checks.push({
386
+ name: 'schema_v12_current',
387
+ status: v && v.version >= 12 ? 'ok' : 'error',
388
+ detail: `db schema version = ${v?.version ?? 0}, latest = ${migrations_1.LATEST_SCHEMA_VERSION}`,
389
+ fixable: false,
390
+ });
391
+ // 3. parseable hooks
392
+ const rows = (0, registry_1.listHooks)(ctx.db);
393
+ const drifted = rows.filter((r) => r.trust_state === 'drifted');
394
+ const untrusted = rows.filter((r) => r.trust_state === 'untrusted');
395
+ const trustedEnabled = rows.filter((r) => r.trust_state === 'trusted' && r.enabled === 1);
396
+ checks.push({
397
+ name: 'drift_count',
398
+ status: drifted.length === 0 ? 'ok' : 'warn',
399
+ detail: `${drifted.length} drifted hook(s) (re-trust to re-enable)`,
400
+ fixable: false,
401
+ });
402
+ checks.push({
403
+ name: 'untrusted_count',
404
+ status: untrusted.length === 0 ? 'ok' : 'warn',
405
+ detail: `${untrusted.length} untrusted hook(s) installed but not yet enabled`,
406
+ fixable: false,
407
+ });
408
+ checks.push({
409
+ name: 'trusted_enabled_count',
410
+ status: 'ok',
411
+ detail: `${trustedEnabled.length} trusted + enabled hook(s)`,
412
+ fixable: false,
413
+ });
414
+ // 4. auto-disabled last 24h — revoked + non-zero recent executions
415
+ const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
416
+ const recentStatus = (0, auditQuery_1.countByStatus)(ctx.db, since);
417
+ const recentNonOk = (recentStatus.crash ?? 0) + (recentStatus.timeout ?? 0) + (recentStatus.malformed_output ?? 0);
418
+ const recentRevoked = ctx.db.prepare(`SELECT COUNT(*) AS n FROM hooks WHERE trust_state='revoked' AND updated_at >= ?`).get(since);
419
+ checks.push({
420
+ name: 'auto_disabled_24h',
421
+ status: recentRevoked.n === 0 ? 'ok' : 'warn',
422
+ detail: `${recentRevoked.n} hook(s) revoked in last 24h (${recentNonOk} non-ok executions)`,
423
+ fixable: false,
424
+ });
425
+ // 5. high-failure-rate hooks
426
+ const fr = (0, auditQuery_1.failureRates)(ctx.db, 100).filter((f) => f.failureRate > 0.1);
427
+ checks.push({
428
+ name: 'high_failure_rate',
429
+ status: fr.length === 0 ? 'ok' : 'warn',
430
+ detail: fr.length === 0 ? 'no hooks > 10% failure last 100 executions' : `${fr.length} hook(s) > 10% failure: ${fr.map((r) => r.hook_id).join(', ')}`,
431
+ fixable: false,
432
+ });
433
+ if (ctx.json) {
434
+ ctx.out(JSON.stringify({ checks, fix }, null, 2) + '\n');
435
+ return checks.some((c) => c.status === 'error') ? 1 : 0;
436
+ }
437
+ for (const c of checks) {
438
+ const icon = c.status === 'ok' ? '[ok] ' : c.status === 'warn' ? '[warn] ' : '[ERR] ';
439
+ ctx.out(`${icon}${c.name.padEnd(24)} ${c.detail}\n`);
440
+ }
441
+ return checks.some((c) => c.status === 'error') ? 1 : 0;
442
+ }
443
+ async function cmdAudit(ctx) {
444
+ const limitRaw = flagValue(ctx.args, '--limit');
445
+ const q = {
446
+ hookId: flagValue(ctx.args, '--hook'),
447
+ event: flagValue(ctx.args, '--event'),
448
+ status: flagValue(ctx.args, '--status'),
449
+ since: flagValue(ctx.args, '--since'),
450
+ limit: limitRaw ? Number.parseInt(limitRaw, 10) : 50,
451
+ };
452
+ const rows = (0, auditQuery_1.queryHookExecutions)(ctx.db, q);
453
+ if (ctx.json) {
454
+ ctx.out(JSON.stringify(rows, null, 2) + '\n');
455
+ return 0;
456
+ }
457
+ if (rows.length === 0) {
458
+ ctx.out('no executions match\n');
459
+ return 0;
460
+ }
461
+ ctx.out(`${'started_at'.padEnd(26)} ${'hook'.padEnd(18)} ${'event'.padEnd(20)} ${'status'.padEnd(10)} ${'decision'.padEnd(10)} elapsed\n`);
462
+ for (const r of rows) {
463
+ 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`);
464
+ }
465
+ return 0;
466
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hooks = exports.HOOKS_SHELL_ONLY = void 0;
4
+ exports.dispatchHooksSlash = dispatchHooksSlash;
5
+ const hooks_1 = require("./hooks");
6
+ /** Actions that need an interactive confirmation prompt. */
7
+ exports.HOOKS_SHELL_ONLY = new Set(['trust', 'revoke']);
8
+ async function dispatchHooksSlash(opts) {
9
+ const a = (opts.action || 'list').toLowerCase();
10
+ if (exports.HOOKS_SHELL_ONLY.has(a)) {
11
+ opts.write(`⚠ /hooks ${a} not available inside chat (needs confirmation prompt)\n`);
12
+ opts.write(' Quit (/quit) and run from shell:\n\n');
13
+ const tail = opts.args.length > 0 ? ' ' + opts.args.join(' ') : '';
14
+ opts.write(` aiden hooks ${a}${tail}\n`);
15
+ return;
16
+ }
17
+ await opts.runHooks(a, opts.args, { writeOut: opts.write, writeErr: opts.write });
18
+ }
19
+ exports.hooks = {
20
+ name: 'hooks',
21
+ description: 'Manage hooks (list / show / rescan / test / doctor / audit).',
22
+ category: 'system',
23
+ icon: '🪝',
24
+ handler: async (ctx) => {
25
+ await dispatchHooksSlash({
26
+ action: ctx.args[0] ?? 'list',
27
+ args: ctx.args.slice(1),
28
+ write: (s) => ctx.display.write(s),
29
+ runHooks: hooks_1.runHooksSubcommand,
30
+ });
31
+ return {};
32
+ },
33
+ };
@@ -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.hooks = exports.memory = 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");
@@ -103,6 +106,11 @@ Object.defineProperty(exports, "recovery", { enumerable: true, get: function ()
103
106
  // ONB1 slice 10 — new-user guided tour.
104
107
  const walkthrough_1 = require("./walkthrough");
105
108
  Object.defineProperty(exports, "walkthrough", { enumerable: true, get: function () { return walkthrough_1.walkthrough; } });
109
+ // v4.9.1 amendment — REPL slash surfaces for memory + hooks (mirrors CLI).
110
+ const memorySlash_1 = require("./memorySlash");
111
+ Object.defineProperty(exports, "memory", { enumerable: true, get: function () { return memorySlash_1.memory; } });
112
+ const hooksSlash_1 = require("./hooksSlash");
113
+ Object.defineProperty(exports, "hooks", { enumerable: true, get: function () { return hooksSlash_1.hooks; } });
106
114
  /** All built-in system commands, in canonical order. */
107
115
  exports.allCommands = [
108
116
  help_1.help,
@@ -119,6 +127,7 @@ exports.allCommands = [
119
127
  usage_1.usage,
120
128
  yolo_1.yolo,
121
129
  skin_1.skin,
130
+ theme_1.theme,
122
131
  skills_1.skills,
123
132
  plugins_1.plugins,
124
133
  auth_1.auth,
@@ -154,6 +163,9 @@ exports.allCommands = [
154
163
  recovery_1.recovery,
155
164
  // ONB1 slice 10 — new-user guided tour.
156
165
  walkthrough_1.walkthrough,
166
+ // v4.9.1 amendment — REPL slash surfaces mirroring CLI subcommands.
167
+ memorySlash_1.memory,
168
+ hooksSlash_1.hooks,
157
169
  clear_1.clear,
158
170
  quit_1.quit,
159
171
  ];