aiden-runtime 4.0.2 → 4.1.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 (113) hide show
  1. package/README.md +19 -11
  2. package/config/hardware.json +2 -2
  3. package/dist/api/server.js +50 -52
  4. package/dist/cli/v4/aidenCLI.js +424 -7
  5. package/dist/cli/v4/aidenPrompt.js +317 -0
  6. package/dist/cli/v4/box.js +105 -39
  7. package/dist/cli/v4/callbacks.js +39 -6
  8. package/dist/cli/v4/chatSession.js +256 -55
  9. package/dist/cli/v4/citationFooter.js +97 -0
  10. package/dist/cli/v4/commands/channel.js +656 -0
  11. package/dist/cli/v4/commands/clear.js +1 -1
  12. package/dist/cli/v4/commands/compress.js +1 -1
  13. package/dist/cli/v4/commands/cron.js +44 -16
  14. package/dist/cli/v4/commands/fanout.js +236 -0
  15. package/dist/cli/v4/commands/help.js +15 -4
  16. package/dist/cli/v4/commands/history.js +84 -0
  17. package/dist/cli/v4/commands/index.js +16 -1
  18. package/dist/cli/v4/commands/mcp.js +358 -0
  19. package/dist/cli/v4/commands/show.js +43 -0
  20. package/dist/cli/v4/commands/skills.js +169 -4
  21. package/dist/cli/v4/commands/status.js +84 -0
  22. package/dist/cli/v4/commands/subagent.js +78 -0
  23. package/dist/cli/v4/commands/verbose.js +1 -1
  24. package/dist/cli/v4/commands/voice.js +218 -0
  25. package/dist/cli/v4/cronCli.js +103 -0
  26. package/dist/cli/v4/display.js +297 -13
  27. package/dist/cli/v4/doctor.js +102 -1
  28. package/dist/cli/v4/doctorLiveness.js +329 -0
  29. package/dist/cli/v4/envSources.js +105 -0
  30. package/dist/cli/v4/ghostMatch.js +74 -0
  31. package/dist/cli/v4/historyStore.js +163 -0
  32. package/dist/cli/v4/pasteCompression.js +124 -0
  33. package/dist/cli/v4/pasteIntercept.js +203 -0
  34. package/dist/cli/v4/replyRenderer.js +209 -0
  35. package/dist/cli/v4/resizeGuard.js +92 -0
  36. package/dist/cli/v4/shellInterpolation.js +139 -0
  37. package/dist/cli/v4/skinEngine.js +21 -1
  38. package/dist/cli/v4/streamingPrefix.js +121 -0
  39. package/dist/cli/v4/syntaxHighlight.js +345 -0
  40. package/dist/cli/v4/table.js +216 -0
  41. package/dist/cli/v4/themeDetect.js +81 -0
  42. package/dist/cli/v4/uiBuild.js +74 -0
  43. package/dist/cli/v4/voiceCli.js +113 -0
  44. package/dist/cli/v4/voicePromptApi.js +196 -0
  45. package/dist/core/channels/discord.js +16 -10
  46. package/dist/core/channels/email.js +13 -9
  47. package/dist/core/channels/imessage.js +13 -9
  48. package/dist/core/channels/manager.js +25 -7
  49. package/dist/core/channels/pdf-extract.js +180 -0
  50. package/dist/core/channels/photo-vision.js +157 -0
  51. package/dist/core/channels/signal.js +11 -7
  52. package/dist/core/channels/slack.js +13 -10
  53. package/dist/core/channels/telegram-commands.js +154 -0
  54. package/dist/core/channels/telegram-groups.js +198 -0
  55. package/dist/core/channels/telegram-rate-limit.js +124 -0
  56. package/dist/core/channels/telegram.js +1980 -0
  57. package/dist/core/channels/twilio.js +11 -7
  58. package/dist/core/channels/webhook.js +9 -5
  59. package/dist/core/channels/whatsapp.js +15 -11
  60. package/dist/core/channels/whisper-transcribe.js +163 -0
  61. package/dist/core/cronManager.js +33 -294
  62. package/dist/core/gateway.js +29 -8
  63. package/dist/core/playwrightBridge.js +90 -0
  64. package/dist/core/v4/aidenAgent.js +35 -0
  65. package/dist/core/v4/auxiliaryClient.js +2 -2
  66. package/dist/core/v4/cron/atomicWrite.js +18 -4
  67. package/dist/core/v4/cron/cronExecute.js +300 -0
  68. package/dist/core/v4/cron/cronManager.js +502 -0
  69. package/dist/core/v4/cron/cronState.js +314 -0
  70. package/dist/core/v4/cron/cronTick.js +90 -0
  71. package/dist/core/v4/cron/diagnostics.js +104 -0
  72. package/dist/core/v4/cron/graceWindow.js +79 -0
  73. package/dist/core/v4/logger/factory.js +110 -0
  74. package/dist/core/v4/logger/index.js +22 -0
  75. package/dist/core/v4/logger/logger.js +101 -0
  76. package/dist/core/v4/logger/sinks/fileSink.js +110 -0
  77. package/dist/core/v4/logger/sinks/multiSink.js +43 -0
  78. package/dist/core/v4/logger/sinks/nullSink.js +53 -0
  79. package/dist/core/v4/logger/sinks/stdSink.js +81 -0
  80. package/dist/core/v4/mcp/server/diagnostics.js +40 -0
  81. package/dist/core/v4/mcp/server/skillBridge.js +94 -0
  82. package/dist/core/v4/mcp/server/stdioServer.js +119 -0
  83. package/dist/core/v4/mcp/server/toolBridge.js +168 -0
  84. package/dist/core/v4/platformPaths.js +105 -0
  85. package/dist/core/v4/providerFallback.js +25 -0
  86. package/dist/core/v4/skillLoader.js +21 -5
  87. package/dist/core/v4/skillMining/candidateStore.js +164 -0
  88. package/dist/core/v4/skillMining/extractorPrompt.js +118 -0
  89. package/dist/core/v4/skillMining/proposalBuilder.js +140 -0
  90. package/dist/core/v4/skillMining/skillMiner.js +191 -0
  91. package/dist/core/v4/skillMining/traceFingerprint.js +51 -0
  92. package/dist/core/v4/subagent/budget.js +76 -0
  93. package/dist/core/v4/subagent/diagnostics.js +22 -0
  94. package/dist/core/v4/subagent/fanout.js +216 -0
  95. package/dist/core/v4/subagent/merger.js +148 -0
  96. package/dist/core/v4/subagent/providerRotation.js +54 -0
  97. package/dist/core/v4/voice/audioStream.js +373 -0
  98. package/dist/core/v4/voice/cliVoice.js +393 -0
  99. package/dist/core/v4/voice/diagnostics.js +66 -0
  100. package/dist/core/v4/voice/ttsStream.js +193 -0
  101. package/dist/core/version.js +1 -1
  102. package/dist/core/visionAnalyze.js +291 -90
  103. package/dist/core/voice/audio.js +61 -5
  104. package/dist/core/voice/audioBackend.js +134 -0
  105. package/dist/core/voice/stt.js +61 -6
  106. package/dist/core/voice/tts.js +19 -3
  107. package/dist/moat/dangerousPatterns.js +1 -1
  108. package/dist/providers/v4/codexResponsesAdapter.js +7 -2
  109. package/dist/providers/v4/errors.js +51 -1
  110. package/dist/providers/v4/ollamaPromptToolsAdapter.js +9 -2
  111. package/dist/tools/v4/index.js +32 -1
  112. package/dist/tools/v4/subagent/subagentFanout.js +190 -0
  113. package/package.json +11 -2
@@ -65,6 +65,7 @@ const node_fs_1 = require("node:fs");
65
65
  const fs = __importStar(require("node:fs"));
66
66
  const path = __importStar(require("node:path"));
67
67
  const os = __importStar(require("node:os"));
68
+ const table_1 = require("../table");
68
69
  const cronManager_1 = require("../../../core/cronManager");
69
70
  const NAME_RE = /^[A-Za-z0-9_-]+$/;
70
71
  const LOGS_DIR = path.join(os.homedir(), '.aiden', 'cron-logs');
@@ -128,7 +129,11 @@ function shortId(id) {
128
129
  function colourResult(ctx, result) {
129
130
  if (result === 'ok')
130
131
  return '✓';
131
- if (result === 'fail')
132
+ if (result === 'warn')
133
+ return '∼';
134
+ if (result === 'fail'
135
+ || result === 'error'
136
+ || result === 'timeout')
132
137
  return '✗';
133
138
  return '·';
134
139
  }
@@ -180,22 +185,18 @@ function cmdList(ctx) {
180
185
  schedule: j.schedule,
181
186
  enabled: j.enabled ? 'on' : 'off',
182
187
  lastRun: fmtTime(j.lastRun),
183
- glyph: colourResult(ctx, j.lastResult),
188
+ result: j.lastResult ?? '—',
184
189
  }));
185
- const widths = {
186
- id: Math.max(2, ...rows.map(r => r.id.length)),
187
- name: Math.max(4, ...rows.map(r => r.name.length)),
188
- schedule: Math.max(8, ...rows.map(r => r.schedule.length)),
189
- enabled: 3,
190
- lastRun: Math.max(7, ...rows.map(r => r.lastRun.length)),
191
- };
192
- ctx.display.write(` ${'ID'.padEnd(widths.id)} ${'NAME'.padEnd(widths.name)} ` +
193
- `${'SCHEDULE'.padEnd(widths.schedule)} EN ${'LAST RUN'.padEnd(widths.lastRun)} R\n`);
194
- for (const r of rows) {
195
- ctx.display.write(` ${r.id.padEnd(widths.id)} ${r.name.padEnd(widths.name)} ` +
196
- `${r.schedule.padEnd(widths.schedule)} ${r.enabled.padEnd(3)} ` +
197
- `${r.lastRun.padEnd(widths.lastRun)} ${r.glyph}\n`);
198
- }
190
+ ctx.display.write((0, table_1.renderTable)(rows, [
191
+ { key: 'id', header: 'ID', align: 'left' },
192
+ { key: 'name', header: 'Name', align: 'left', flex: true },
193
+ { key: 'schedule', header: 'Schedule', align: 'left' },
194
+ { key: 'enabled', header: 'En', align: 'center',
195
+ color: (v) => (v === 'on' ? 'success' : 'muted') },
196
+ { key: 'lastRun', header: 'Last Run', align: 'left' },
197
+ { key: 'result', header: 'R', align: 'center',
198
+ color: (v) => (v === 'ok' ? 'success' : v === 'fail' ? 'error' : 'muted') },
199
+ ]));
199
200
  }
200
201
  async function cmdRun(ctx, args) {
201
202
  const job = resolveJob(args[0] ?? '');
@@ -278,6 +279,29 @@ function cmdDisable(ctx, args) {
278
279
  else
279
280
  ctx.display.printError('Disable failed.');
280
281
  }
282
+ async function cmdStatus(ctx) {
283
+ const diag = await (0, cronManager_1.getDiagnostics)();
284
+ ctx.display.info(`Aiden cron — ${cronManager_1.AIDEN_CRON_BUILD}`);
285
+ ctx.display.write(` schema version : ${diag.schemaVersion}\n`);
286
+ ctx.display.write(` tick interval : ${diag.tickMs}ms\n`);
287
+ ctx.display.write(` fire timeout : ${diag.timeoutMs}ms\n`);
288
+ ctx.display.write(` heartbeat : ${diag.heartbeatActive ? 'active' : 'idle'}\n`);
289
+ ctx.display.write(` last heartbeat : ${diag.lastHeartbeatAt ?? 'never'}\n`);
290
+ ctx.display.write(` skipped ticks : ${diag.skippedTicks}\n`);
291
+ ctx.display.write(` fires (boot) : ${diag.firesStarted}\n`);
292
+ ctx.display.write(` lock : ${diag.lock.held ? 'held' : 'free'} (${diag.lock.path})\n`);
293
+ if (diag.recentFires.length > 0) {
294
+ ctx.display.write(` recent fires:\n`);
295
+ for (const r of diag.recentFires) {
296
+ const tag = r.status === 'ok' ? '✓'
297
+ : r.status === 'warn' ? '∼'
298
+ : r.status === 'timeout' ? 'T'
299
+ : '✗';
300
+ ctx.display.write(` ${tag} [${r.jobId.slice(0, 8)}] ${fmtTime(r.startedAt)} ${r.durationMs}ms` +
301
+ `${r.error ? ' ' + r.error.slice(0, 60) : ''}\n`);
302
+ }
303
+ }
304
+ }
281
305
  async function cmdRemove(ctx, args) {
282
306
  const job = resolveJob(args[0] ?? '');
283
307
  if (!job) {
@@ -335,6 +359,9 @@ exports.cron = {
335
359
  case 'info':
336
360
  cmdShow(ctx, rest);
337
361
  break;
362
+ case 'status':
363
+ await cmdStatus(ctx);
364
+ break;
338
365
  case 'logs':
339
366
  case 'log':
340
367
  await cmdLogs(ctx, rest);
@@ -361,6 +388,7 @@ exports.cron = {
361
388
  ctx.display.write(' /cron logs <id|name>\n');
362
389
  ctx.display.write(' /cron enable|disable <id|name>\n');
363
390
  ctx.display.write(' /cron remove <id|name>\n');
391
+ ctx.display.write(' /cron status (v4.1-cron diagnostics)\n');
364
392
  ctx.display.dim('Schedules: cron expr ("0 9 * * *"), interval ("every 2m"), one-shot ISO.');
365
393
  break;
366
394
  default:
@@ -0,0 +1,236 @@
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/fanout.ts — Phase v4.1-subagent
10
+ *
11
+ * `aiden fanout "<query>" --n=3 --merge=combine [--mode=ensemble]`
12
+ * one-shot CLI subcommand. Spins up N parallel children against the
13
+ * built-in agent runtime, prints the merged answer, and exits.
14
+ *
15
+ * Dry-run mode (`--dry-run` flag, OR env `AIDEN_FANOUT_DRY_RUN=1`)
16
+ * uses synthetic in-process stubs for both children and aggregator
17
+ * — no LLM calls. Used by the runtime smoke to verify the
18
+ * Promise.all + merge dispatch path against the BUILT artifact
19
+ * without depending on provider keys or network access.
20
+ *
21
+ * The slash-command counterpart (`/fanout` inside the REPL) lives
22
+ * in this same module — it shares the same arg parser. The REPL
23
+ * registers the slash command via `core/v4/commandRegistry`; the
24
+ * CLI subcommand is wired in `cli/v4/aidenCLI.ts`.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.parseFanoutArgs = parseFanoutArgs;
28
+ exports.runFanoutCli = runFanoutCli;
29
+ /* eslint-disable @typescript-eslint/no-explicit-any */
30
+ const fanout_1 = require("../../../core/v4/subagent/fanout");
31
+ function parseFanoutArgs(argv) {
32
+ const args = { positional: [] };
33
+ let i = 0;
34
+ while (i < argv.length) {
35
+ const a = argv[i];
36
+ if (a === '--dry-run') {
37
+ args.dryRun = true;
38
+ i += 1;
39
+ continue;
40
+ }
41
+ const eq = a.startsWith('--') ? a.indexOf('=') : -1;
42
+ let key = null;
43
+ let value = null;
44
+ if (eq > 0) {
45
+ key = a.slice(2, eq);
46
+ value = a.slice(eq + 1);
47
+ i += 1;
48
+ }
49
+ else if (a.startsWith('--')) {
50
+ key = a.slice(2);
51
+ value = argv[i + 1] ?? '';
52
+ i += 2;
53
+ }
54
+ else {
55
+ args.positional.push(a);
56
+ i += 1;
57
+ continue;
58
+ }
59
+ switch (key) {
60
+ case 'n':
61
+ args.n = Number.parseInt(value, 10);
62
+ break;
63
+ case 'merge':
64
+ if (value === 'all' || value === 'vote' || value === 'pick-best' || value === 'combine') {
65
+ args.merge = value;
66
+ }
67
+ break;
68
+ case 'mode':
69
+ if (value === 'partition' || value === 'ensemble') {
70
+ args.mode = value;
71
+ }
72
+ break;
73
+ case 'timeout-ms':
74
+ case 'timeoutMs':
75
+ args.timeoutMs = Number.parseInt(value, 10);
76
+ break;
77
+ default:
78
+ // Unknown flag — silently dropped, user gets feedback via missing-defaults.
79
+ break;
80
+ }
81
+ }
82
+ return {
83
+ query: args.positional.join(' '),
84
+ n: typeof args.n === 'number' && Number.isFinite(args.n) ? args.n : 3,
85
+ merge: args.merge ?? 'combine',
86
+ mode: args.mode ?? 'ensemble',
87
+ timeoutMs: args.timeoutMs,
88
+ dryRun: args.dryRun ?? false,
89
+ };
90
+ }
91
+ /** Run the CLI subcommand. Returns the process exit code. */
92
+ async function runFanoutCli(argv, opts = {}) {
93
+ const writeOut = opts.writeOut ?? ((t) => process.stdout.write(t));
94
+ const writeErr = opts.writeErr ?? ((t) => process.stderr.write(t));
95
+ const env = opts.env ?? process.env;
96
+ const args = parseFanoutArgs(argv);
97
+ if (!args.query) {
98
+ writeErr('Usage: aiden fanout "<query>" [--n=3] [--merge=combine] [--mode=ensemble] [--dry-run]\n');
99
+ return 1;
100
+ }
101
+ const dryRun = args.dryRun || env.AIDEN_FANOUT_DRY_RUN === '1' || env.AIDEN_FANOUT_DRY_RUN === 'true';
102
+ if (!dryRun) {
103
+ // Phase v4.1-subagent.1 — live mode boots a real agent runtime,
104
+ // pulls the wired subagent_fanout handler out of the registry,
105
+ // and dispatches one fanout call. The runtime build is the same
106
+ // one `aiden chat` / `aiden mcp` use; provider resolution, plugin
107
+ // discovery, etc. all run. On systems with no providers configured
108
+ // it'll surface the same friendly error the chat REPL does.
109
+ try {
110
+ // Lazy-import buildAgentRuntime to avoid pulling its (large)
111
+ // dependency graph into every CLI invocation. mcp / subagent /
112
+ // version don't need it.
113
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
114
+ const { buildAgentRuntime } = require('../aidenCLI');
115
+ const runtime = await buildAgentRuntime({}, {});
116
+ const registry = runtime.toolRegistry;
117
+ const handler = registry.get('subagent_fanout');
118
+ if (!handler) {
119
+ writeErr('aiden fanout: subagent_fanout not registered (build bug)\n');
120
+ return 1;
121
+ }
122
+ const toolArgs = {
123
+ mode: args.mode,
124
+ n: args.n,
125
+ merge: args.merge,
126
+ };
127
+ if (args.mode === 'ensemble') {
128
+ toolArgs.query = args.query;
129
+ }
130
+ else {
131
+ toolArgs.tasks = Array.from({ length: args.n }, (_, i) => ({
132
+ goal: `Task ${i + 1} from CLI: ${args.query}`,
133
+ }));
134
+ }
135
+ if (args.timeoutMs)
136
+ toolArgs.timeoutMs = args.timeoutMs;
137
+ const result = await handler.execute(toolArgs, {
138
+ cwd: process.cwd(),
139
+ paths: runtime.paths,
140
+ sessions: runtime.sessionManager,
141
+ memory: runtime.memoryManager,
142
+ skillLoader: runtime.skillLoader,
143
+ });
144
+ if (!result.success) {
145
+ writeErr(`aiden fanout: ${result.error ?? 'unknown error'}\n`);
146
+ return 1;
147
+ }
148
+ writeOut(`fanout (live)\n`);
149
+ writeOut(` mode: ${args.mode}\n`);
150
+ writeOut(` n: ${args.n}\n`);
151
+ writeOut(` merge: ${args.merge}\n`);
152
+ writeOut(` query: ${args.query}\n`);
153
+ if (result.diagnostics) {
154
+ writeOut(` succeeded: ${result.diagnostics.succeeded}/${args.n}\n`);
155
+ writeOut(` totalMs: ${result.diagnostics.totalMs}\n`);
156
+ writeOut(` providers: ${result.diagnostics.providerDistribution.join(', ')}\n`);
157
+ }
158
+ writeOut(`\n--- merged ---\n`);
159
+ if (args.merge === 'all' && result.results) {
160
+ for (const r of result.results) {
161
+ writeOut(`\n[${r.index}] ${r.providerId}:${r.modelId} (${r.elapsedMs}ms)\n`);
162
+ writeOut(`${r.error ? `[error: ${r.error}]` : r.output}\n`);
163
+ }
164
+ }
165
+ else {
166
+ writeOut(`${result.merged ?? '(no merged output)'}\n`);
167
+ }
168
+ return 0;
169
+ }
170
+ catch (err) {
171
+ writeErr(`aiden fanout failed: ${err.message}\n`);
172
+ return 1;
173
+ }
174
+ }
175
+ // ── Dry-run path: stub providers, stub child runner, stub aggregator
176
+ // ─ exercises the Promise.all + abort + merge plumbing against the
177
+ // built artifact without touching network or provider keys.
178
+ const providers = [
179
+ { providerId: 'stub-a', modelId: 'fake-model' },
180
+ { providerId: 'stub-b', modelId: 'fake-model' },
181
+ ];
182
+ const stubAdapter = {
183
+ apiMode: 'chat_completions',
184
+ async call() {
185
+ return {
186
+ content: '[dry-run aggregator]',
187
+ toolCalls: [],
188
+ finishReason: 'stop',
189
+ usage: { inputTokens: 0, outputTokens: 0 },
190
+ };
191
+ },
192
+ };
193
+ try {
194
+ const result = await (0, fanout_1.runFanout)({
195
+ mode: args.mode,
196
+ query: args.query,
197
+ tasks: args.mode === 'partition'
198
+ ? Array.from({ length: args.n }, (_, i) => ({
199
+ goal: `Task ${i + 1} from CLI: ${args.query}`,
200
+ }))
201
+ : undefined,
202
+ n: args.n,
203
+ merge: args.merge,
204
+ providers,
205
+ runChild: async ({ index, prompt, provider }) => {
206
+ return `[dry-run child ${index} via ${provider.providerId}] echo: ${prompt}`;
207
+ },
208
+ aggregatorAdapter: stubAdapter,
209
+ aggregatorModel: { providerId: 'stub-a', modelId: 'fake-model' },
210
+ timeoutMs: args.timeoutMs,
211
+ });
212
+ writeOut(`fanout dry-run\n`);
213
+ writeOut(` mode: ${args.mode}\n`);
214
+ writeOut(` n: ${args.n}\n`);
215
+ writeOut(` merge: ${args.merge}\n`);
216
+ writeOut(` query: ${args.query}\n`);
217
+ writeOut(` succeeded: ${result.diagnostics.succeeded}/${args.n}\n`);
218
+ writeOut(` totalMs: ${result.diagnostics.totalMs}\n`);
219
+ writeOut(` providers: ${result.diagnostics.providerDistribution.join(', ')}\n`);
220
+ writeOut(`\n--- merged ---\n`);
221
+ if (args.merge === 'all') {
222
+ for (const r of result.results) {
223
+ writeOut(`\n[${r.index}] ${r.providerId}:${r.modelId} (${r.elapsedMs}ms)\n`);
224
+ writeOut(`${r.output}\n`);
225
+ }
226
+ }
227
+ else {
228
+ writeOut(`${result.merged ?? '(no merged output)'}\n`);
229
+ }
230
+ return 0;
231
+ }
232
+ catch (err) {
233
+ writeErr(`fanout failed: ${err.message}\n`);
234
+ return 1;
235
+ }
236
+ }
@@ -46,6 +46,14 @@ exports.SUBSECTION_MAP = {
46
46
  quit: 'System',
47
47
  yolo: 'System',
48
48
  usage: 'System',
49
+ cron: 'System',
50
+ setup: 'System',
51
+ channel: 'System',
52
+ // Phase v4.1-tier3.1 + tier3-essentials commands.
53
+ voice: 'System',
54
+ status: 'System',
55
+ show: 'System',
56
+ history: 'System',
49
57
  // ── Authentication ──
50
58
  auth: 'Authentication',
51
59
  // ── Help ──
@@ -58,7 +66,7 @@ exports.help = {
58
66
  name: 'help',
59
67
  description: 'List available slash commands.',
60
68
  category: 'system',
61
- icon: '',
69
+ icon: '?',
62
70
  aliases: ['h', '?'],
63
71
  handler: async (ctx) => {
64
72
  const all = ctx.registry.list();
@@ -71,21 +79,24 @@ exports.help = {
71
79
  for (const c of system) {
72
80
  buckets.get(subsectionFor(c.name)).push(c);
73
81
  }
82
+ // Tier-3.1: gate icon column on AIDEN_UI_ICONS=1 (default OFF).
83
+ const showIcons = process.env.AIDEN_UI_ICONS === '1';
74
84
  for (const sec of exports.SUBSECTION_ORDER) {
75
85
  const cmds = buckets.get(sec);
76
86
  if (cmds.length === 0)
77
87
  continue;
78
88
  ctx.display.dim(`── ${sec} ──`);
79
89
  for (const c of cmds) {
80
- const icon = c.icon ?? ' ';
81
- ctx.display.write(` ${icon} /${c.name.padEnd(14)} ${c.description}\n`);
90
+ const prefix = showIcons ? `${c.icon ?? ' '} ` : '';
91
+ ctx.display.write(` ${prefix}/${c.name.padEnd(14)} ${c.description}\n`);
82
92
  }
83
93
  ctx.display.write('\n');
84
94
  }
85
95
  if (skill.length > 0) {
86
96
  ctx.display.dim('── Skills ──');
87
97
  for (const c of skill) {
88
- ctx.display.write(` /${c.name.padEnd(14)} ${c.description}\n`);
98
+ const prefix = showIcons ? '' : '';
99
+ ctx.display.write(` ${prefix}/${c.name.padEnd(14)} ${c.description}\n`);
89
100
  }
90
101
  }
91
102
  return {};
@@ -0,0 +1,84 @@
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/history.ts — Phase v4.1-tier3-essentials
10
+ *
11
+ * `/history [list|clear] [N]`
12
+ *
13
+ * /history — list last 50 entries (newest first)
14
+ * /history list 200 — list last 200 (clamped to HISTORY_MAX_ENTRIES)
15
+ * /history clear — wipe the on-disk history (with double-Enter
16
+ * confirmation)
17
+ *
18
+ * The on-disk file is `<aidenHome>/.aiden_history`, written by
19
+ * `historyStore.appendHistory`. This command is read-only except for
20
+ * `clear`, which removes the file entirely.
21
+ */
22
+ var __importDefault = (this && this.__importDefault) || function (mod) {
23
+ return (mod && mod.__esModule) ? mod : { "default": mod };
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.history = void 0;
27
+ const node_fs_1 = require("node:fs");
28
+ const node_path_1 = __importDefault(require("node:path"));
29
+ const historyStore_1 = require("../historyStore");
30
+ const paths_1 = require("../../../core/v4/paths");
31
+ const HISTORY_FILENAME = '.aiden_history';
32
+ exports.history = {
33
+ name: 'history',
34
+ description: 'List or clear the on-disk prompt history.',
35
+ category: 'system',
36
+ handler: async (ctx) => {
37
+ const sub = (ctx.args[0] ?? 'list').toLowerCase();
38
+ if (sub === 'list') {
39
+ const limit = (() => {
40
+ const arg = ctx.args[1];
41
+ const n = arg ? parseInt(arg, 10) : 50;
42
+ if (!Number.isFinite(n) || n <= 0)
43
+ return 50;
44
+ return Math.min(n, historyStore_1.HISTORY_MAX_ENTRIES);
45
+ })();
46
+ const entries = await (0, historyStore_1.loadRecent)(limit);
47
+ if (entries.length === 0) {
48
+ ctx.display.dim('(history is empty)');
49
+ return {};
50
+ }
51
+ ctx.display.info(`History (last ${entries.length}):`);
52
+ // Newest first; render as numbered list. Truncate each entry
53
+ // to keep the display tidy — full entry is one Enter away on
54
+ // arrow-up in the new prompt.
55
+ entries.forEach((entry, i) => {
56
+ const oneLine = entry.replace(/\s+/g, ' ').trim();
57
+ const display = oneLine.length > 100 ? oneLine.slice(0, 99) + '…' : oneLine;
58
+ ctx.display.write(` ${String(i + 1).padStart(3, ' ')}. ${display}\n`);
59
+ });
60
+ return {};
61
+ }
62
+ if (sub === 'clear') {
63
+ const confirm = ctx.args[1];
64
+ if (confirm !== '--yes') {
65
+ ctx.display.warn('History clear requires confirmation.');
66
+ ctx.display.dim('Run `/history clear --yes` to wipe the on-disk history file.');
67
+ return {};
68
+ }
69
+ const filePath = node_path_1.default.join((0, paths_1.resolveAidenPaths)().root, HISTORY_FILENAME);
70
+ try {
71
+ await node_fs_1.promises.rm(filePath, { force: true });
72
+ // Also drop any sibling .tmp left from an interrupted append.
73
+ await node_fs_1.promises.rm(`${filePath}.tmp`, { force: true });
74
+ ctx.display.success('History cleared.');
75
+ }
76
+ catch (err) {
77
+ ctx.display.printError(`Failed to clear history: ${err.message}`);
78
+ }
79
+ return {};
80
+ }
81
+ ctx.display.printError(`Unknown subcommand: ${sub}`, 'Try: /history list [N] | /history clear --yes');
82
+ return {};
83
+ },
84
+ };
@@ -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.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.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;
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");
@@ -65,6 +65,16 @@ const cron_1 = require("./cron");
65
65
  Object.defineProperty(exports, "cron", { enumerable: true, get: function () { return cron_1.cron; } });
66
66
  const setup_1 = require("./setup");
67
67
  Object.defineProperty(exports, "setup", { enumerable: true, get: function () { return setup_1.setup; } });
68
+ const channel_1 = require("./channel");
69
+ Object.defineProperty(exports, "channel", { enumerable: true, get: function () { return channel_1.channel; } });
70
+ const voice_1 = require("./voice");
71
+ Object.defineProperty(exports, "voice", { enumerable: true, get: function () { return voice_1.voice; } });
72
+ const status_1 = require("./status");
73
+ Object.defineProperty(exports, "status", { enumerable: true, get: function () { return status_1.status; } });
74
+ const show_1 = require("./show");
75
+ Object.defineProperty(exports, "show", { enumerable: true, get: function () { return show_1.show; } });
76
+ const history_1 = require("./history");
77
+ Object.defineProperty(exports, "history", { enumerable: true, get: function () { return history_1.history; } });
68
78
  /** All built-in system commands, in canonical order. */
69
79
  exports.allCommands = [
70
80
  help_1.help,
@@ -88,6 +98,11 @@ exports.allCommands = [
88
98
  doctor_1.doctor,
89
99
  cron_1.cron,
90
100
  setup_1.setup,
101
+ channel_1.channel,
102
+ voice_1.voice,
103
+ status_1.status,
104
+ show_1.show,
105
+ history_1.history,
91
106
  reloadMcp_1.reloadMcp,
92
107
  reasoning_1.reasoning,
93
108
  verbose_1.verbose,