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.
- package/README.md +19 -11
- package/config/hardware.json +2 -2
- package/dist/api/server.js +50 -52
- package/dist/cli/v4/aidenCLI.js +424 -7
- package/dist/cli/v4/aidenPrompt.js +317 -0
- package/dist/cli/v4/box.js +105 -39
- package/dist/cli/v4/callbacks.js +39 -6
- package/dist/cli/v4/chatSession.js +256 -55
- package/dist/cli/v4/citationFooter.js +97 -0
- package/dist/cli/v4/commands/channel.js +656 -0
- package/dist/cli/v4/commands/clear.js +1 -1
- package/dist/cli/v4/commands/compress.js +1 -1
- package/dist/cli/v4/commands/cron.js +44 -16
- package/dist/cli/v4/commands/fanout.js +236 -0
- package/dist/cli/v4/commands/help.js +15 -4
- package/dist/cli/v4/commands/history.js +84 -0
- package/dist/cli/v4/commands/index.js +16 -1
- package/dist/cli/v4/commands/mcp.js +358 -0
- package/dist/cli/v4/commands/show.js +43 -0
- package/dist/cli/v4/commands/skills.js +169 -4
- package/dist/cli/v4/commands/status.js +84 -0
- package/dist/cli/v4/commands/subagent.js +78 -0
- package/dist/cli/v4/commands/verbose.js +1 -1
- package/dist/cli/v4/commands/voice.js +218 -0
- package/dist/cli/v4/cronCli.js +103 -0
- package/dist/cli/v4/display.js +297 -13
- package/dist/cli/v4/doctor.js +102 -1
- package/dist/cli/v4/doctorLiveness.js +329 -0
- package/dist/cli/v4/envSources.js +105 -0
- package/dist/cli/v4/ghostMatch.js +74 -0
- package/dist/cli/v4/historyStore.js +163 -0
- package/dist/cli/v4/pasteCompression.js +124 -0
- package/dist/cli/v4/pasteIntercept.js +203 -0
- package/dist/cli/v4/replyRenderer.js +209 -0
- package/dist/cli/v4/resizeGuard.js +92 -0
- package/dist/cli/v4/shellInterpolation.js +139 -0
- package/dist/cli/v4/skinEngine.js +21 -1
- package/dist/cli/v4/streamingPrefix.js +121 -0
- package/dist/cli/v4/syntaxHighlight.js +345 -0
- package/dist/cli/v4/table.js +216 -0
- package/dist/cli/v4/themeDetect.js +81 -0
- package/dist/cli/v4/uiBuild.js +74 -0
- package/dist/cli/v4/voiceCli.js +113 -0
- package/dist/cli/v4/voicePromptApi.js +196 -0
- package/dist/core/channels/discord.js +16 -10
- package/dist/core/channels/email.js +13 -9
- package/dist/core/channels/imessage.js +13 -9
- package/dist/core/channels/manager.js +25 -7
- package/dist/core/channels/pdf-extract.js +180 -0
- package/dist/core/channels/photo-vision.js +157 -0
- package/dist/core/channels/signal.js +11 -7
- package/dist/core/channels/slack.js +13 -10
- package/dist/core/channels/telegram-commands.js +154 -0
- package/dist/core/channels/telegram-groups.js +198 -0
- package/dist/core/channels/telegram-rate-limit.js +124 -0
- package/dist/core/channels/telegram.js +1980 -0
- package/dist/core/channels/twilio.js +11 -7
- package/dist/core/channels/webhook.js +9 -5
- package/dist/core/channels/whatsapp.js +15 -11
- package/dist/core/channels/whisper-transcribe.js +163 -0
- package/dist/core/cronManager.js +33 -294
- package/dist/core/gateway.js +29 -8
- package/dist/core/playwrightBridge.js +90 -0
- package/dist/core/v4/aidenAgent.js +35 -0
- package/dist/core/v4/auxiliaryClient.js +2 -2
- package/dist/core/v4/cron/atomicWrite.js +18 -4
- package/dist/core/v4/cron/cronExecute.js +300 -0
- package/dist/core/v4/cron/cronManager.js +502 -0
- package/dist/core/v4/cron/cronState.js +314 -0
- package/dist/core/v4/cron/cronTick.js +90 -0
- package/dist/core/v4/cron/diagnostics.js +104 -0
- package/dist/core/v4/cron/graceWindow.js +79 -0
- package/dist/core/v4/logger/factory.js +110 -0
- package/dist/core/v4/logger/index.js +22 -0
- package/dist/core/v4/logger/logger.js +101 -0
- package/dist/core/v4/logger/sinks/fileSink.js +110 -0
- package/dist/core/v4/logger/sinks/multiSink.js +43 -0
- package/dist/core/v4/logger/sinks/nullSink.js +53 -0
- package/dist/core/v4/logger/sinks/stdSink.js +81 -0
- package/dist/core/v4/mcp/server/diagnostics.js +40 -0
- package/dist/core/v4/mcp/server/skillBridge.js +94 -0
- package/dist/core/v4/mcp/server/stdioServer.js +119 -0
- package/dist/core/v4/mcp/server/toolBridge.js +168 -0
- package/dist/core/v4/platformPaths.js +105 -0
- package/dist/core/v4/providerFallback.js +25 -0
- package/dist/core/v4/skillLoader.js +21 -5
- package/dist/core/v4/skillMining/candidateStore.js +164 -0
- package/dist/core/v4/skillMining/extractorPrompt.js +118 -0
- package/dist/core/v4/skillMining/proposalBuilder.js +140 -0
- package/dist/core/v4/skillMining/skillMiner.js +191 -0
- package/dist/core/v4/skillMining/traceFingerprint.js +51 -0
- package/dist/core/v4/subagent/budget.js +76 -0
- package/dist/core/v4/subagent/diagnostics.js +22 -0
- package/dist/core/v4/subagent/fanout.js +216 -0
- package/dist/core/v4/subagent/merger.js +148 -0
- package/dist/core/v4/subagent/providerRotation.js +54 -0
- package/dist/core/v4/voice/audioStream.js +373 -0
- package/dist/core/v4/voice/cliVoice.js +393 -0
- package/dist/core/v4/voice/diagnostics.js +66 -0
- package/dist/core/v4/voice/ttsStream.js +193 -0
- package/dist/core/version.js +1 -1
- package/dist/core/visionAnalyze.js +291 -90
- package/dist/core/voice/audio.js +61 -5
- package/dist/core/voice/audioBackend.js +134 -0
- package/dist/core/voice/stt.js +61 -6
- package/dist/core/voice/tts.js +19 -3
- package/dist/moat/dangerousPatterns.js +1 -1
- package/dist/providers/v4/codexResponsesAdapter.js +7 -2
- package/dist/providers/v4/errors.js +51 -1
- package/dist/providers/v4/ollamaPromptToolsAdapter.js +9 -2
- package/dist/tools/v4/index.js +32 -1
- package/dist/tools/v4/subagent/subagentFanout.js +190 -0
- package/package.json +11 -2
|
@@ -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/status.ts — Tier-3.1 (v4.1-tier3.1)
|
|
10
|
+
*
|
|
11
|
+
* `/status` — full environment + capability table that the v4.0 boot
|
|
12
|
+
* card used to print inline. Slim boot now ships a 3-4 line summary;
|
|
13
|
+
* users who want the full picture (provider table, skill counts, MCP
|
|
14
|
+
* details, channel adapter list) call `/status` on demand.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.status = void 0;
|
|
18
|
+
const chatSession_1 = require("../chatSession");
|
|
19
|
+
const display_1 = require("../display");
|
|
20
|
+
exports.status = {
|
|
21
|
+
name: 'status',
|
|
22
|
+
description: 'Detailed runtime status (env, providers, skills, channels).',
|
|
23
|
+
category: 'system',
|
|
24
|
+
icon: 'i',
|
|
25
|
+
handler: async (ctx) => {
|
|
26
|
+
const display = ctx.display;
|
|
27
|
+
// Provider / model — drawn from the live session when available.
|
|
28
|
+
const provider = ctx.session?.getCurrentProvider() ?? '(unset)';
|
|
29
|
+
const model = ctx.session?.getCurrentModel() ?? '(unset)';
|
|
30
|
+
// Tools / skills counts.
|
|
31
|
+
const toolsCount = ctx.toolRegistry?.list().length ?? 0;
|
|
32
|
+
let skillsLoaded = 0;
|
|
33
|
+
try {
|
|
34
|
+
if (ctx.skillLoader)
|
|
35
|
+
skillsLoaded = (await ctx.skillLoader.list()).length;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
skillsLoaded = 0;
|
|
39
|
+
}
|
|
40
|
+
// Channels — uses the same summariser the boot card used.
|
|
41
|
+
let channelSummary;
|
|
42
|
+
const cm = ctx.channelManager;
|
|
43
|
+
if (cm) {
|
|
44
|
+
const adapterStatuses = cm.getStatus().map((s) => {
|
|
45
|
+
const adapter = cm.get(s.name);
|
|
46
|
+
const botHandle = typeof adapter?.getBotUsername === 'function' ? adapter.getBotUsername() : null;
|
|
47
|
+
const state = typeof adapter?.getState === 'function' ? adapter.getState() : undefined;
|
|
48
|
+
return { id: s.name, healthy: s.healthy, botHandle, state };
|
|
49
|
+
});
|
|
50
|
+
channelSummary = (0, display_1.summarizeChannelState)({ adapters: adapterStatuses });
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
channelSummary = (0, display_1.summarizeChannelState)(null);
|
|
54
|
+
}
|
|
55
|
+
display.write('\n');
|
|
56
|
+
display.write(display.twoColumnBlock({
|
|
57
|
+
title: 'Environment',
|
|
58
|
+
rows: [
|
|
59
|
+
{ key: 'OS', value: (0, chatSession_1.detectOS)() },
|
|
60
|
+
{ key: 'shell', value: (0, chatSession_1.detectShell)() },
|
|
61
|
+
{ key: 'runtime', value: 'local-first' },
|
|
62
|
+
{ key: 'provider', value: provider },
|
|
63
|
+
{ key: 'model', value: model },
|
|
64
|
+
{ key: 'tools', value: `${toolsCount} loaded` },
|
|
65
|
+
{ key: 'skills', value: `${skillsLoaded} loaded` },
|
|
66
|
+
{ key: 'channels', value: channelSummary },
|
|
67
|
+
],
|
|
68
|
+
}, {
|
|
69
|
+
title: 'Capabilities',
|
|
70
|
+
rows: [
|
|
71
|
+
{ key: 'web', value: 'research, extract' },
|
|
72
|
+
{ key: 'browser', value: 'navigate, automate' },
|
|
73
|
+
{ key: 'files', value: 'read, patch, organize' },
|
|
74
|
+
{ key: 'execution', value: 'shell, code, workflows' },
|
|
75
|
+
{ key: 'memory', value: 'persistent recall' },
|
|
76
|
+
],
|
|
77
|
+
}) + '\n');
|
|
78
|
+
display.write('\n');
|
|
79
|
+
display.write(` ${display.rule()}\n`);
|
|
80
|
+
display.write(display.bottomPromptHint() + '\n');
|
|
81
|
+
display.write('\n');
|
|
82
|
+
return {};
|
|
83
|
+
},
|
|
84
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
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/subagent.ts — Phase v4.1-subagent
|
|
10
|
+
*
|
|
11
|
+
* `aiden subagent <action>` subcommand. Two actions:
|
|
12
|
+
*
|
|
13
|
+
* status — print build fingerprint + env config + provider count.
|
|
14
|
+
* tools — list the subagent_fanout schema (debug).
|
|
15
|
+
*
|
|
16
|
+
* Lightweight diagnostics surface — no provider resolution, no
|
|
17
|
+
* agent runtime build. The point is "what would my fanout look like
|
|
18
|
+
* before I run it?" — useful for triaging "no providers configured"
|
|
19
|
+
* errors and verifying env-var allowlists before pointing a client
|
|
20
|
+
* at the binary.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.AIDEN_SUBAGENT_BUILD = void 0;
|
|
24
|
+
exports.runSubagentSubcommand = runSubagentSubcommand;
|
|
25
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
26
|
+
const toolRegistry_1 = require("../../../core/v4/toolRegistry");
|
|
27
|
+
const index_1 = require("../../../tools/v4/index");
|
|
28
|
+
const diagnostics_1 = require("../../../core/v4/subagent/diagnostics");
|
|
29
|
+
Object.defineProperty(exports, "AIDEN_SUBAGENT_BUILD", { enumerable: true, get: function () { return diagnostics_1.AIDEN_SUBAGENT_BUILD; } });
|
|
30
|
+
const budget_1 = require("../../../core/v4/subagent/budget");
|
|
31
|
+
const merger_1 = require("../../../core/v4/subagent/merger");
|
|
32
|
+
async function runSubagentSubcommand(action, opts = {}) {
|
|
33
|
+
const writeOut = opts.writeOut ?? ((t) => process.stdout.write(t));
|
|
34
|
+
const writeErr = opts.writeErr ?? ((t) => process.stderr.write(t));
|
|
35
|
+
switch (action) {
|
|
36
|
+
case 'status': {
|
|
37
|
+
const budget = (0, budget_1.resolveBudget)();
|
|
38
|
+
const allowDestructive = process.env.AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE === '1' ||
|
|
39
|
+
process.env.AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE === 'true';
|
|
40
|
+
const aggOverride = (0, merger_1.resolveAggregatorOverride)();
|
|
41
|
+
writeOut(`Aiden subagent fanout\n`);
|
|
42
|
+
writeOut(` build: ${diagnostics_1.AIDEN_SUBAGENT_BUILD}\n`);
|
|
43
|
+
writeOut(` default n: ${budget_1.DEFAULT_FANOUT_N}\n`);
|
|
44
|
+
writeOut(` hard cap n: ${budget_1.MAX_FANOUT_N}\n`);
|
|
45
|
+
writeOut(` per-subagent timeout ms: ${budget.perSubagentTimeoutMs}\n`);
|
|
46
|
+
writeOut(` wall-clock cap ms: ${budget.wallClockCapMs}\n`);
|
|
47
|
+
writeOut(` max iterations: ${budget.maxIterations}\n`);
|
|
48
|
+
writeOut(` allowDestructive: ${allowDestructive ? 'yes' : 'no'}\n`);
|
|
49
|
+
writeOut(` aggregator override: ${aggOverride
|
|
50
|
+
? `${aggOverride.providerId}:${aggOverride.modelId}`
|
|
51
|
+
: '(unset — use parent active model)'}\n`);
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
case 'tools': {
|
|
55
|
+
const registry = new toolRegistry_1.ToolRegistry();
|
|
56
|
+
(0, index_1.registerAllTools)(registry);
|
|
57
|
+
const handler = registry.get('subagent_fanout');
|
|
58
|
+
if (!handler) {
|
|
59
|
+
writeErr('subagent_fanout not registered (this is a build bug)\n');
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
writeOut(`Aiden subagent — tool schema\n`);
|
|
63
|
+
writeOut(` name: ${handler.schema.name}\n`);
|
|
64
|
+
writeOut(` category: ${handler.category}\n`);
|
|
65
|
+
writeOut(` mutates: ${handler.mutates}\n`);
|
|
66
|
+
writeOut(` toolset: ${handler.toolset}\n`);
|
|
67
|
+
writeOut(` description: ${handler.schema.description}\n`);
|
|
68
|
+
writeOut(`\n inputSchema (JSON):\n`);
|
|
69
|
+
writeOut(`${JSON.stringify(handler.schema.inputSchema, null, 2)}\n`);
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
default: {
|
|
73
|
+
writeErr(`Unknown 'aiden subagent' action: ${action}\n`);
|
|
74
|
+
writeErr(`Actions: status | tools\n`);
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
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/voice.ts — Phase v4.1-voice-cli
|
|
10
|
+
*
|
|
11
|
+
* `/voice` slash command. Subcommands:
|
|
12
|
+
*
|
|
13
|
+
* /voice on — enable voice mode for this session
|
|
14
|
+
* /voice off — disable
|
|
15
|
+
* /voice toggle — flip on/off
|
|
16
|
+
* /voice status — show fingerprint + backend + provider availability
|
|
17
|
+
* /voice mode push — switch to push-to-talk (default)
|
|
18
|
+
* /voice mode continuous — switch to continuous (VAD-driven)
|
|
19
|
+
* /voice provider <name> — pin TTS provider (edge | sapi | elevenlabs | voxcpm)
|
|
20
|
+
* /voice voice <id> — pin TTS voice (default: en-US-AriaNeural)
|
|
21
|
+
*
|
|
22
|
+
* Persistence: writes to the user's `.aiden/.env` via `upsertEnvVar`
|
|
23
|
+
* so settings survive REPL restarts. Mirrors the channel slash
|
|
24
|
+
* command's atomic .env mutation pattern.
|
|
25
|
+
*
|
|
26
|
+
* State surfaces via `process.env` keys:
|
|
27
|
+
* AIDEN_VOICE_ENABLED — "1" / "0"
|
|
28
|
+
* AIDEN_VOICE_MODE — "push-to-talk" / "continuous"
|
|
29
|
+
* AIDEN_VOICE_TTS_VOICE — voice id
|
|
30
|
+
* AIDEN_VOICE_PROVIDER — provider override
|
|
31
|
+
*/
|
|
32
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
33
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
34
|
+
};
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.voice = void 0;
|
|
37
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
38
|
+
const node_fs_1 = require("node:fs");
|
|
39
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
40
|
+
const diagnostics_1 = require("../../../core/v4/voice/diagnostics");
|
|
41
|
+
// .env upsert (atomic) — mirrors the channel slash command's
|
|
42
|
+
// pattern. Local to this file so the import surface stays tight.
|
|
43
|
+
async function upsertEnvVar(envFile, key, value) {
|
|
44
|
+
const k = key.toUpperCase();
|
|
45
|
+
let body = '';
|
|
46
|
+
try {
|
|
47
|
+
body = await node_fs_1.promises.readFile(envFile, 'utf8');
|
|
48
|
+
}
|
|
49
|
+
catch { /* fresh file */ }
|
|
50
|
+
const lines = body.split(/\r?\n/);
|
|
51
|
+
let replaced = false;
|
|
52
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
53
|
+
if (lines[i].startsWith(`${k}=`)) {
|
|
54
|
+
lines[i] = `${k}=${value}`;
|
|
55
|
+
replaced = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!replaced)
|
|
59
|
+
lines.push(`${k}=${value}`);
|
|
60
|
+
while (lines.length > 0 && lines[lines.length - 1] === '')
|
|
61
|
+
lines.pop();
|
|
62
|
+
await node_fs_1.promises.mkdir(node_path_1.default.dirname(envFile), { recursive: true });
|
|
63
|
+
const tmp = `${envFile}.${process.pid}.tmp`;
|
|
64
|
+
await node_fs_1.promises.writeFile(tmp, `${lines.join('\n')}\n`, 'utf8');
|
|
65
|
+
await node_fs_1.promises.rename(tmp, envFile);
|
|
66
|
+
}
|
|
67
|
+
async function deleteEnvKey(envFile, key) {
|
|
68
|
+
const k = key.toUpperCase();
|
|
69
|
+
let body = '';
|
|
70
|
+
try {
|
|
71
|
+
body = await node_fs_1.promises.readFile(envFile, 'utf8');
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const lines = body.split(/\r?\n/);
|
|
77
|
+
const filtered = lines.filter((l) => !l.startsWith(`${k}=`));
|
|
78
|
+
if (filtered.length === lines.length)
|
|
79
|
+
return false;
|
|
80
|
+
while (filtered.length > 0 && filtered[filtered.length - 1] === '')
|
|
81
|
+
filtered.pop();
|
|
82
|
+
const tmp = `${envFile}.${process.pid}.tmp`;
|
|
83
|
+
await node_fs_1.promises.writeFile(tmp, `${filtered.join('\n')}\n`, 'utf8');
|
|
84
|
+
await node_fs_1.promises.rename(tmp, envFile);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
const USAGE = [
|
|
88
|
+
'Usage:',
|
|
89
|
+
' /voice on | off | toggle',
|
|
90
|
+
' /voice status',
|
|
91
|
+
' /voice mode push | continuous',
|
|
92
|
+
' /voice provider edge | sapi | elevenlabs | voxcpm',
|
|
93
|
+
' /voice voice <id> (e.g. en-US-AriaNeural)',
|
|
94
|
+
].join('\n');
|
|
95
|
+
const handler = async (ctx) => {
|
|
96
|
+
const display = ctx.display;
|
|
97
|
+
const action = (ctx.args[0] ?? 'status').toLowerCase();
|
|
98
|
+
const paths = ctx.paths;
|
|
99
|
+
switch (action) {
|
|
100
|
+
case 'on':
|
|
101
|
+
case 'off':
|
|
102
|
+
case 'toggle': {
|
|
103
|
+
const current = process.env.AIDEN_VOICE_ENABLED === '1';
|
|
104
|
+
const next = action === 'on'
|
|
105
|
+
? true
|
|
106
|
+
: action === 'off'
|
|
107
|
+
? false
|
|
108
|
+
: !current;
|
|
109
|
+
process.env.AIDEN_VOICE_ENABLED = next ? '1' : '0';
|
|
110
|
+
if (paths) {
|
|
111
|
+
try {
|
|
112
|
+
if (next)
|
|
113
|
+
await upsertEnvVar(paths.envFile, 'AIDEN_VOICE_ENABLED', '1');
|
|
114
|
+
else
|
|
115
|
+
await deleteEnvKey(paths.envFile, 'AIDEN_VOICE_ENABLED');
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
display.warn(`Could not persist /voice ${action} to .env: ${err.message}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const tty = !!process.stdin.isTTY && !!process.stdout.isTTY;
|
|
122
|
+
if (next && !tty) {
|
|
123
|
+
display.warn('Voice mode requested, but stdin is not a TTY. The next REPL session must run in an interactive terminal.');
|
|
124
|
+
}
|
|
125
|
+
display.info(`voice mode ${next ? 'on' : 'off'}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
case 'status': {
|
|
129
|
+
const diag = await (0, diagnostics_1.collectVoiceDiagnostics)();
|
|
130
|
+
display.info(`Aiden voice — ${diagnostics_1.AIDEN_VOICE_CLI_BUILD}`);
|
|
131
|
+
display.info(` enabled: ${process.env.AIDEN_VOICE_ENABLED === '1' ? 'yes' : 'no'}`);
|
|
132
|
+
display.info(` tty: ${diag.isTty ? 'yes' : 'no (voice refused — non-TTY stdin)'}`);
|
|
133
|
+
display.info(` mic backend: ${diag.audio.backend}`);
|
|
134
|
+
display.info(` mic active: ${diag.audio.active ? 'yes' : 'no'}`);
|
|
135
|
+
display.info(` sox on PATH: ${diag.audio.soxOnPath ? 'yes' : 'no'}`);
|
|
136
|
+
display.info(` mode: ${diag.config.mode}`);
|
|
137
|
+
display.info(` tts voice: ${diag.config.ttsVoice}`);
|
|
138
|
+
display.info(` beeps: ${diag.config.beepsEnabled ? 'on' : 'off'}`);
|
|
139
|
+
display.info(` tts providers:`);
|
|
140
|
+
for (const p of diag.ttsProviders) {
|
|
141
|
+
const tag = p.available ? '✓' : '✗';
|
|
142
|
+
display.info(` ${tag} ${p.name.padEnd(12)} ${p.note ?? ''}`);
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
case 'mode': {
|
|
147
|
+
const sub = (ctx.args[1] ?? '').toLowerCase();
|
|
148
|
+
if (sub !== 'push' && sub !== 'push-to-talk' && sub !== 'continuous') {
|
|
149
|
+
display.warn('Mode must be "push" or "continuous"');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const value = sub === 'continuous' ? 'continuous' : 'push-to-talk';
|
|
153
|
+
process.env.AIDEN_VOICE_MODE = value;
|
|
154
|
+
if (paths) {
|
|
155
|
+
try {
|
|
156
|
+
await upsertEnvVar(paths.envFile, 'AIDEN_VOICE_MODE', value);
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
display.warn(`Could not persist mode: ${err.message}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
display.info(`voice mode = ${value}`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
case 'provider': {
|
|
166
|
+
const provider = (ctx.args[1] ?? '').toLowerCase();
|
|
167
|
+
const valid = ['edge', 'sapi', 'elevenlabs', 'voxcpm'];
|
|
168
|
+
if (!valid.includes(provider)) {
|
|
169
|
+
display.warn(`Provider must be one of: ${valid.join(', ')}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
process.env.AIDEN_VOICE_PROVIDER = provider;
|
|
173
|
+
if (paths) {
|
|
174
|
+
try {
|
|
175
|
+
await upsertEnvVar(paths.envFile, 'AIDEN_VOICE_PROVIDER', provider);
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
display.warn(`Could not persist provider: ${err.message}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
display.info(`tts provider = ${provider}`);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
case 'voice': {
|
|
185
|
+
const voiceId = (ctx.args[1] ?? '').trim();
|
|
186
|
+
if (!voiceId) {
|
|
187
|
+
display.warn('Voice id required (e.g. en-US-AriaNeural)');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
process.env.AIDEN_VOICE_TTS_VOICE = voiceId;
|
|
191
|
+
if (paths) {
|
|
192
|
+
try {
|
|
193
|
+
await upsertEnvVar(paths.envFile, 'AIDEN_VOICE_TTS_VOICE', voiceId);
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
display.warn(`Could not persist voice: ${err.message}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
display.info(`tts voice = ${voiceId}`);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
case 'help':
|
|
203
|
+
case '--help':
|
|
204
|
+
case '-h':
|
|
205
|
+
display.info(USAGE);
|
|
206
|
+
return;
|
|
207
|
+
default:
|
|
208
|
+
display.warn(`Unknown action: ${action}`);
|
|
209
|
+
display.info(USAGE);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
exports.voice = {
|
|
214
|
+
name: 'voice',
|
|
215
|
+
description: 'Voice mode (PTT + TTS). Subcommands: on/off/toggle/status/mode/provider/voice',
|
|
216
|
+
category: 'system',
|
|
217
|
+
handler,
|
|
218
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
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/cronCli.ts — Phase v4.1-cron
|
|
10
|
+
*
|
|
11
|
+
* `aiden cron <action>` top-level CLI subcommand. Three actions:
|
|
12
|
+
*
|
|
13
|
+
* status — print build fingerprint, tick + lock state,
|
|
14
|
+
* last 5 fires (boot-local). No mutation.
|
|
15
|
+
* list — table of jobs (id, schedule, enabled, next run).
|
|
16
|
+
* run <id> — fire a job immediately. Useful for scripted
|
|
17
|
+
* "trigger this scheduled task now" flows from
|
|
18
|
+
* a shell.
|
|
19
|
+
*
|
|
20
|
+
* Distinct from the `/cron` slash command (which mutates session
|
|
21
|
+
* state from inside the REPL). This subcommand is for scripting +
|
|
22
|
+
* sanity-checking from a non-interactive shell.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.AIDEN_CRON_BUILD = void 0;
|
|
26
|
+
exports.runCronSubcommand = runCronSubcommand;
|
|
27
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
28
|
+
const cronManager_1 = require("../../core/cronManager");
|
|
29
|
+
Object.defineProperty(exports, "AIDEN_CRON_BUILD", { enumerable: true, get: function () { return cronManager_1.AIDEN_CRON_BUILD; } });
|
|
30
|
+
async function runCronSubcommand(action, args, opts = {}) {
|
|
31
|
+
const writeOut = opts.writeOut ?? ((t) => process.stdout.write(t));
|
|
32
|
+
const writeErr = opts.writeErr ?? ((t) => process.stderr.write(t));
|
|
33
|
+
switch (action) {
|
|
34
|
+
case 'status': {
|
|
35
|
+
// Booting may armed timers we don't need for status — but it
|
|
36
|
+
// also populates the cache. Skip arming side-effect by NOT
|
|
37
|
+
// calling loadJobs; getDiagnostics works without the cache.
|
|
38
|
+
const diag = await (0, cronManager_1.getDiagnostics)();
|
|
39
|
+
writeOut(`Aiden cron — ${cronManager_1.AIDEN_CRON_BUILD}\n`);
|
|
40
|
+
writeOut(` schema version : ${diag.schemaVersion}\n`);
|
|
41
|
+
writeOut(` tick interval : ${diag.tickMs}ms\n`);
|
|
42
|
+
writeOut(` fire timeout : ${diag.timeoutMs}ms\n`);
|
|
43
|
+
writeOut(` heartbeat : ${diag.heartbeatActive ? 'active' : 'idle'}\n`);
|
|
44
|
+
writeOut(` last heartbeat : ${diag.lastHeartbeatAt ?? 'never'}\n`);
|
|
45
|
+
writeOut(` skipped ticks : ${diag.skippedTicks}\n`);
|
|
46
|
+
writeOut(` fires (boot) : ${diag.firesStarted}\n`);
|
|
47
|
+
writeOut(` lock : ${diag.lock.held ? 'held' : 'free'}\n`);
|
|
48
|
+
writeOut(` lock path : ${diag.lock.path}\n`);
|
|
49
|
+
if (diag.recentFires.length > 0) {
|
|
50
|
+
writeOut(` recent fires:\n`);
|
|
51
|
+
for (const r of diag.recentFires) {
|
|
52
|
+
const tag = r.status === 'ok' ? '✓'
|
|
53
|
+
: r.status === 'warn' ? '∼'
|
|
54
|
+
: r.status === 'timeout' ? 'T'
|
|
55
|
+
: '✗';
|
|
56
|
+
writeOut(` ${tag} [${r.jobId.slice(0, 8)}] ${r.startedAt} ${r.durationMs}ms` +
|
|
57
|
+
`${r.error ? ' ' + r.error.slice(0, 60) : ''}\n`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
case 'list': {
|
|
63
|
+
const jobs = await (0, cronManager_1.listJobsAsync)();
|
|
64
|
+
if (jobs.length === 0) {
|
|
65
|
+
writeOut(`No cron jobs configured.\n`);
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
writeOut(`Aiden cron — ${jobs.length} job(s)\n`);
|
|
69
|
+
writeOut(` ID NAME SCHEDULE STATE NEXT RUN\n`);
|
|
70
|
+
for (const j of jobs) {
|
|
71
|
+
const id = j.id.padEnd(5).slice(0, 5);
|
|
72
|
+
const name = (j.description || '(unnamed)').padEnd(31).slice(0, 31);
|
|
73
|
+
const sched = (j.schedule || '?').padEnd(33).slice(0, 33);
|
|
74
|
+
const stateLabel = j.state.padEnd(11).slice(0, 11);
|
|
75
|
+
const next = j.nextRun ?? 'never';
|
|
76
|
+
writeOut(` ${id} ${name} ${sched} ${stateLabel} ${next}\n`);
|
|
77
|
+
}
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
case 'run': {
|
|
81
|
+
const id = args[0];
|
|
82
|
+
if (!id) {
|
|
83
|
+
writeErr(`Usage: aiden cron run <id>\n`);
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
// loadJobs populates the cache + arms timers. The trigger is
|
|
87
|
+
// what we actually want; arming is side-effect.
|
|
88
|
+
await (0, cronManager_1.loadJobs)();
|
|
89
|
+
const ok = await (0, cronManager_1.triggerJob)(id);
|
|
90
|
+
if (!ok) {
|
|
91
|
+
writeErr(`Job not found: ${id}\n`);
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
writeOut(`Triggered job ${id}.\n`);
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
default: {
|
|
98
|
+
writeErr(`Unknown 'aiden cron' action: ${action}\n`);
|
|
99
|
+
writeErr(`Actions: status | list | run <id>\n`);
|
|
100
|
+
return 1;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|