rex-claude 4.0.0 → 6.0.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/dist/agents-JIZXXASP.js +853 -0
- package/dist/app-3VWDSH5F.js +248 -0
- package/dist/audio-US2J627E.js +196 -0
- package/dist/audit-ZVTGE4L4.js +8 -0
- package/dist/call-AQZ3Z5SE.js +143 -0
- package/dist/chunk-5ND7JYY3.js +62 -0
- package/dist/chunk-6SRV2I2H.js +56 -0
- package/dist/{setup-AO3MW46W.js → chunk-A7ZLQUOX.js} +93 -16
- package/dist/chunk-E5UYN3W7.js +105 -0
- package/dist/chunk-HAHJD3QH.js +147 -0
- package/dist/{init-DLFEGD6O.js → chunk-KR7ISYZH.js} +328 -29
- package/dist/chunk-LTOM55UV.js +154 -0
- package/dist/chunk-PDX44BCA.js +11 -0
- package/dist/chunk-PPGYFMU5.js +67 -0
- package/dist/{chunk-7AGI43F5.js → chunk-WBMVBMWB.js} +4 -2
- package/dist/{context-FN5O5YBI.js → context-XNCG2M5Q.js} +2 -1
- package/dist/daemon-5KNSNFTD.js +208 -0
- package/dist/gateway-YLP66MCQ.js +2273 -0
- package/dist/hammerspoon/rex-call-watcher.lua +186 -0
- package/dist/index.js +309 -15
- package/dist/init-RDZFIBLA.js +30 -0
- package/dist/install-63JBDPRU.js +41 -0
- package/dist/{llm-YRORUH7E.js → llm-RALIPIMI.js} +2 -1
- package/dist/mcp_registry-DX4GGSP6.js +514 -0
- package/dist/migrate-GDO37TI5.js +87 -0
- package/dist/{optimize-UKMAGQQE.js → optimize-5TE5RKZV.js} +2 -1
- package/dist/paths-4SECM6E6.js +38 -0
- package/dist/preload-I3MYBVNU.js +78 -0
- package/dist/projects-V6TSLO7E.js +17 -0
- package/dist/{prune-2PPIVDXK.js → prune-B7F5B5OF.js} +2 -1
- package/dist/recategorize-YXYIMQLZ.js +155 -0
- package/dist/router-2JD34COX.js +12 -0
- package/dist/self-improve-YK7RCYF4.js +197 -0
- package/dist/setup-KNDTVFO6.js +8 -0
- package/dist/skills-AIWFY5NH.js +374 -0
- package/dist/voice-RITC3EVC.js +248 -0
- package/package.json +12 -3
- package/dist/gateway-EKMU5D7J.js +0 -784
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/paths.ts
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { existsSync, mkdirSync } from "fs";
|
|
6
|
+
var HOME = process.env.HOME || "~";
|
|
7
|
+
var REX_DIR = join(HOME, ".claude", "rex");
|
|
8
|
+
var MEMORY_DIR = join(REX_DIR, "memory");
|
|
9
|
+
var MEMORY_DB_PATH = join(MEMORY_DIR, "rex.sqlite");
|
|
10
|
+
var PENDING_DIR = join(MEMORY_DIR, "pending");
|
|
11
|
+
var BACKUPS_DIR = join(MEMORY_DIR, "backups");
|
|
12
|
+
var PROJECTS_DIR = join(REX_DIR, "projects");
|
|
13
|
+
var SUMMARIES_DIR = join(PROJECTS_DIR, "summaries");
|
|
14
|
+
var SELF_IMPROVEMENT_DIR = join(REX_DIR, "self-improvement");
|
|
15
|
+
var CONFIG_PATH = join(REX_DIR, "config.json");
|
|
16
|
+
var VAULT_PATH = join(REX_DIR, "vault.md");
|
|
17
|
+
var DAEMON_LOG_PATH = join(REX_DIR, "daemon.log");
|
|
18
|
+
var REFERENCES_DIR = join(REX_DIR, "references");
|
|
19
|
+
var INSPIRATIONS_DIR = join(REX_DIR, "inspirations");
|
|
20
|
+
var LEGACY_MEMORY_DIR = join(HOME, ".rex-memory");
|
|
21
|
+
var LEGACY_DB_PATH = join(LEGACY_MEMORY_DIR, "db", "rex.sqlite");
|
|
22
|
+
function ensureRexDirs() {
|
|
23
|
+
const dirs = [
|
|
24
|
+
REX_DIR,
|
|
25
|
+
MEMORY_DIR,
|
|
26
|
+
PENDING_DIR,
|
|
27
|
+
BACKUPS_DIR,
|
|
28
|
+
PROJECTS_DIR,
|
|
29
|
+
SUMMARIES_DIR,
|
|
30
|
+
SELF_IMPROVEMENT_DIR,
|
|
31
|
+
REFERENCES_DIR,
|
|
32
|
+
INSPIRATIONS_DIR
|
|
33
|
+
];
|
|
34
|
+
for (const dir of dirs) {
|
|
35
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
REX_DIR,
|
|
41
|
+
MEMORY_DIR,
|
|
42
|
+
MEMORY_DB_PATH,
|
|
43
|
+
PENDING_DIR,
|
|
44
|
+
BACKUPS_DIR,
|
|
45
|
+
PROJECTS_DIR,
|
|
46
|
+
SUMMARIES_DIR,
|
|
47
|
+
SELF_IMPROVEMENT_DIR,
|
|
48
|
+
CONFIG_PATH,
|
|
49
|
+
VAULT_PATH,
|
|
50
|
+
DAEMON_LOG_PATH,
|
|
51
|
+
REFERENCES_DIR,
|
|
52
|
+
INSPIRATIONS_DIR,
|
|
53
|
+
LEGACY_MEMORY_DIR,
|
|
54
|
+
LEGACY_DB_PATH,
|
|
55
|
+
ensureRexDirs
|
|
56
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/setup.ts
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import { platform, totalmem, homedir } from "os";
|
|
6
|
-
import { readFileSync, writeFileSync } from "fs";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
7
7
|
import { join } from "path";
|
|
8
8
|
import { createInterface } from "readline";
|
|
9
9
|
var COLORS = {
|
|
@@ -25,14 +25,30 @@ function fail(msg) {
|
|
|
25
25
|
console.log(` ${COLORS.red}\u2717${COLORS.reset} ${msg}`);
|
|
26
26
|
}
|
|
27
27
|
var OLLAMA_URL = process.env.OLLAMA_URL || "http://localhost:11434";
|
|
28
|
-
|
|
28
|
+
function commandExists(cmd) {
|
|
29
|
+
try {
|
|
30
|
+
execSync(`which ${cmd}`, { stdio: "ignore" });
|
|
31
|
+
return true;
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function hasBrew() {
|
|
37
|
+
return commandExists("brew");
|
|
38
|
+
}
|
|
39
|
+
function tryBrewInstall(formula, isCask = false) {
|
|
40
|
+
if (!hasBrew()) return false;
|
|
29
41
|
try {
|
|
30
|
-
|
|
42
|
+
const cmd = isCask ? `brew install --cask ${formula}` : `brew install ${formula}`;
|
|
43
|
+
execSync(cmd, { stdio: "inherit" });
|
|
31
44
|
return true;
|
|
32
45
|
} catch {
|
|
33
46
|
return false;
|
|
34
47
|
}
|
|
35
48
|
}
|
|
49
|
+
async function isOllamaInstalled() {
|
|
50
|
+
return commandExists("ollama");
|
|
51
|
+
}
|
|
36
52
|
async function isOllamaRunning() {
|
|
37
53
|
try {
|
|
38
54
|
const res = await fetch(`${OLLAMA_URL}/api/tags`);
|
|
@@ -92,10 +108,44 @@ function prompt(question) {
|
|
|
92
108
|
});
|
|
93
109
|
});
|
|
94
110
|
}
|
|
95
|
-
async function
|
|
111
|
+
async function ensurePlatformDeps(os, autoInstallDeps) {
|
|
112
|
+
console.log(`
|
|
113
|
+
${COLORS.bold}System Dependencies${COLORS.reset}`);
|
|
114
|
+
const deps = [
|
|
115
|
+
{ cmd: "ffmpeg", formula: "ffmpeg", label: "ffmpeg (audio logger)" },
|
|
116
|
+
{ cmd: "whisper-cli", formula: "whisper-cpp", label: "whisper-cli (transcription)" },
|
|
117
|
+
{ cmd: "hs", formula: "hammerspoon", cask: true, label: "Hammerspoon CLI (call watcher)" }
|
|
118
|
+
];
|
|
119
|
+
for (const dep of deps) {
|
|
120
|
+
if (commandExists(dep.cmd)) {
|
|
121
|
+
ok(`${dep.label} available`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (!autoInstallDeps) {
|
|
125
|
+
info(`${dep.label} missing`);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (os !== "darwin") {
|
|
129
|
+
info(`${dep.label} missing (auto-install currently targets macOS)`);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (!hasBrew()) {
|
|
133
|
+
info(`${dep.label} missing and Homebrew not found`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
info(`Installing ${dep.label}...`);
|
|
137
|
+
const installed = tryBrewInstall(dep.formula, dep.cask === true);
|
|
138
|
+
if (installed && commandExists(dep.cmd)) ok(`${dep.label} installed`);
|
|
139
|
+
else fail(`Could not install ${dep.label}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function setupTelegram(options = {}) {
|
|
96
143
|
console.log(`
|
|
97
144
|
${COLORS.bold}Telegram Gateway${COLORS.reset}`);
|
|
98
|
-
const
|
|
145
|
+
const nonInteractive = options.nonInteractive === true;
|
|
146
|
+
const settingsDir = join(homedir(), ".claude");
|
|
147
|
+
const settingsPath = join(settingsDir, "settings.json");
|
|
148
|
+
if (!existsSync(settingsDir)) mkdirSync(settingsDir, { recursive: true });
|
|
99
149
|
let settings = {};
|
|
100
150
|
try {
|
|
101
151
|
settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
@@ -119,6 +169,23 @@ async function setupTelegram() {
|
|
|
119
169
|
}
|
|
120
170
|
info("Existing Telegram config found but not working \u2014 reconfiguring");
|
|
121
171
|
}
|
|
172
|
+
if (options.skipTelegram) {
|
|
173
|
+
info("Skipping Telegram setup (flag enabled)");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (nonInteractive) {
|
|
177
|
+
const envToken = process.env.REX_TELEGRAM_BOT_TOKEN;
|
|
178
|
+
const envChat = process.env.REX_TELEGRAM_CHAT_ID;
|
|
179
|
+
if (envToken && envChat) {
|
|
180
|
+
settings.env.REX_TELEGRAM_BOT_TOKEN = envToken;
|
|
181
|
+
settings.env.REX_TELEGRAM_CHAT_ID = envChat;
|
|
182
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
183
|
+
ok("Telegram credentials loaded from environment");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
info("Telegram not configured (set REX_TELEGRAM_BOT_TOKEN + REX_TELEGRAM_CHAT_ID to enable)");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
122
189
|
const botToken = await prompt("Telegram Bot Token (from @BotFather):");
|
|
123
190
|
if (!botToken) {
|
|
124
191
|
info("Skipped Telegram setup");
|
|
@@ -172,7 +239,9 @@ async function setupTelegram() {
|
|
|
172
239
|
fail("Could not send test message");
|
|
173
240
|
}
|
|
174
241
|
}
|
|
175
|
-
async function setup() {
|
|
242
|
+
async function setup(options = {}) {
|
|
243
|
+
const nonInteractive = options.nonInteractive === true;
|
|
244
|
+
const autoInstallDeps = options.autoInstallDeps ?? nonInteractive;
|
|
176
245
|
const line = "\u2550".repeat(45);
|
|
177
246
|
console.log(`
|
|
178
247
|
${line}`);
|
|
@@ -182,24 +251,31 @@ ${line}`);
|
|
|
182
251
|
const ramGB = Math.round(totalmem() / 1024 ** 3);
|
|
183
252
|
const os = platform();
|
|
184
253
|
info(`System: ${os}, ${ramGB}GB RAM`);
|
|
254
|
+
await ensurePlatformDeps(os, autoInstallDeps);
|
|
185
255
|
if (!await isOllamaInstalled()) {
|
|
186
|
-
|
|
187
|
-
|
|
256
|
+
if (autoInstallDeps && os === "darwin" && hasBrew()) {
|
|
257
|
+
info("Installing Ollama via Homebrew...");
|
|
258
|
+
tryBrewInstall("ollama");
|
|
259
|
+
}
|
|
260
|
+
if (!await isOllamaInstalled()) {
|
|
261
|
+
fail("Ollama not installed");
|
|
262
|
+
console.log(`
|
|
188
263
|
Install: ${COLORS.cyan}https://ollama.com/download${COLORS.reset}`);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
264
|
+
if (os === "darwin" && !nonInteractive) {
|
|
265
|
+
info("Opening download page...");
|
|
266
|
+
try {
|
|
267
|
+
execSync("open https://ollama.com/download", { stdio: "ignore" });
|
|
268
|
+
} catch {
|
|
269
|
+
}
|
|
194
270
|
}
|
|
271
|
+
return;
|
|
195
272
|
}
|
|
196
|
-
return;
|
|
197
273
|
}
|
|
198
274
|
ok("Ollama installed");
|
|
199
275
|
if (!await isOllamaRunning()) {
|
|
200
276
|
info("Starting Ollama...");
|
|
201
277
|
try {
|
|
202
|
-
execSync("ollama serve &", { stdio: "ignore" });
|
|
278
|
+
execSync("nohup ollama serve > ~/.claude/rex-ollama.log 2>&1 &", { stdio: "ignore" });
|
|
203
279
|
await new Promise((r) => setTimeout(r, 3e3));
|
|
204
280
|
if (await isOllamaRunning()) {
|
|
205
281
|
ok("Ollama started");
|
|
@@ -235,7 +311,7 @@ ${line}`);
|
|
|
235
311
|
else fail("Embedding test failed");
|
|
236
312
|
if (genOk) ok("Generation test passed");
|
|
237
313
|
else fail("Generation test failed");
|
|
238
|
-
await setupTelegram();
|
|
314
|
+
await setupTelegram(options);
|
|
239
315
|
console.log(`
|
|
240
316
|
${COLORS.dim}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${COLORS.reset}`);
|
|
241
317
|
if (embedOk && genOk) {
|
|
@@ -247,6 +323,7 @@ ${COLORS.dim}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\
|
|
|
247
323
|
}
|
|
248
324
|
console.log();
|
|
249
325
|
}
|
|
326
|
+
|
|
250
327
|
export {
|
|
251
328
|
setup
|
|
252
329
|
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/router.ts
|
|
4
|
+
var OLLAMA_URL = process.env.OLLAMA_URL || "http://localhost:11434";
|
|
5
|
+
var TASK_PREFERENCES = {
|
|
6
|
+
background: ["qwen2.5:1.5b", "qwen3.5:latest", "qwen3.5:9b"],
|
|
7
|
+
consolidate: ["qwen2.5:1.5b", "qwen3.5:latest", "qwen3.5:9b"],
|
|
8
|
+
categorize: ["qwen3.5:9b", "qwen3.5:latest", "qwen2.5:1.5b", "deepseek-r1:8b"],
|
|
9
|
+
gateway: ["qwen3.5:9b", "qwen3.5:latest", "deepseek-r1:8b", "qwen2.5:1.5b"],
|
|
10
|
+
optimize: ["qwen3.5:9b", "qwen3.5:latest", "deepseek-r1:8b"],
|
|
11
|
+
reason: ["deepseek-r1:8b", "qwen3.5:9b", "qwen3.5:latest"],
|
|
12
|
+
code: ["qwen3-coder:30b", "qwen2.5-coder:32b-instruct-q4_K_M", "qwen3.5:9b"]
|
|
13
|
+
};
|
|
14
|
+
var _cachedModels = null;
|
|
15
|
+
var _cacheTime = 0;
|
|
16
|
+
var CACHE_TTL = 6e4;
|
|
17
|
+
async function getAvailableModels() {
|
|
18
|
+
const now = Date.now();
|
|
19
|
+
if (_cachedModels && now - _cacheTime < CACHE_TTL) return _cachedModels;
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch(`${OLLAMA_URL}/api/tags`, {
|
|
22
|
+
signal: AbortSignal.timeout(3e3)
|
|
23
|
+
});
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
_cachedModels = data.models.map((m) => m.name);
|
|
26
|
+
_cacheTime = now;
|
|
27
|
+
return _cachedModels;
|
|
28
|
+
} catch {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function pickModel(task) {
|
|
33
|
+
if (process.env.REX_LLM_MODEL) return process.env.REX_LLM_MODEL;
|
|
34
|
+
const available = await getAvailableModels();
|
|
35
|
+
const prefs = TASK_PREFERENCES[task];
|
|
36
|
+
for (const pref of prefs) {
|
|
37
|
+
if (available.includes(pref)) return pref;
|
|
38
|
+
const base = pref.split(":")[0];
|
|
39
|
+
const match = available.find((a) => (a === base || a.startsWith(base + ":")) && !a.includes("embed"));
|
|
40
|
+
if (match) return match;
|
|
41
|
+
}
|
|
42
|
+
const fallback = available.find((a) => !a.includes("embed") && !a.includes("nomic"));
|
|
43
|
+
return fallback ?? "qwen3.5:latest";
|
|
44
|
+
}
|
|
45
|
+
async function showModelRouter() {
|
|
46
|
+
const available = await getAvailableModels();
|
|
47
|
+
const embed = available.filter((m) => m.includes("embed") || m.includes("nomic"));
|
|
48
|
+
const gen = available.filter((m) => !m.includes("embed") && !m.includes("nomic"));
|
|
49
|
+
const line = "\u2500".repeat(52);
|
|
50
|
+
const tasks = ["background", "categorize", "consolidate", "gateway", "optimize", "reason", "code"];
|
|
51
|
+
console.log(`
|
|
52
|
+
\x1B[1mREX Model Router\x1B[0m`);
|
|
53
|
+
console.log(line);
|
|
54
|
+
console.log(`\x1B[2mInstalled: ${gen.length} generation, ${embed.length} embedding\x1B[0m
|
|
55
|
+
`);
|
|
56
|
+
for (const task of tasks) {
|
|
57
|
+
const chosen = await pickModel(task);
|
|
58
|
+
const prefs = TASK_PREFERENCES[task];
|
|
59
|
+
const isOptimal = prefs[0] === chosen || available.includes(prefs[0]);
|
|
60
|
+
const dot = isOptimal ? "\x1B[32m\u25CF\x1B[0m" : "\x1B[33m\u25CF\x1B[0m";
|
|
61
|
+
console.log(` ${dot} \x1B[1m${task.padEnd(12)}\x1B[0m \u2192 ${chosen}`);
|
|
62
|
+
}
|
|
63
|
+
console.log(`
|
|
64
|
+
\x1B[2membeddings \u2192 ${embed[0] ?? "nomic-embed-text (not found)"}\x1B[0m`);
|
|
65
|
+
console.log(line);
|
|
66
|
+
if (gen.length === 0) {
|
|
67
|
+
console.log(`
|
|
68
|
+
\x1B[33m!\x1B[0m No Ollama models found \u2014 run: ollama pull qwen3.5:latest`);
|
|
69
|
+
} else {
|
|
70
|
+
const missingOptimal = tasks.filter((t) => {
|
|
71
|
+
const pref = TASK_PREFERENCES[t][0];
|
|
72
|
+
const base = pref.split(":")[0];
|
|
73
|
+
return !available.some((a) => a.startsWith(base));
|
|
74
|
+
});
|
|
75
|
+
if (missingOptimal.length > 0) {
|
|
76
|
+
console.log(`
|
|
77
|
+
\x1B[33m!\x1B[0m Optimal models missing for: ${missingOptimal.join(", ")}`);
|
|
78
|
+
console.log(` \x1B[2mRecommended to pull:\x1B[0m`);
|
|
79
|
+
const toRecommend = /* @__PURE__ */ new Set();
|
|
80
|
+
for (const t of missingOptimal) {
|
|
81
|
+
const pref = TASK_PREFERENCES[t][0];
|
|
82
|
+
const base = pref.split(":")[0];
|
|
83
|
+
if (!available.some((a) => a.startsWith(base))) toRecommend.add(pref);
|
|
84
|
+
}
|
|
85
|
+
for (const m of toRecommend) {
|
|
86
|
+
console.log(` ollama pull ${m}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
console.log();
|
|
91
|
+
}
|
|
92
|
+
async function getRouterSnapshot() {
|
|
93
|
+
const tasks = ["background", "categorize", "consolidate", "gateway", "optimize", "reason", "code"];
|
|
94
|
+
const snap = {};
|
|
95
|
+
for (const t of tasks) {
|
|
96
|
+
snap[t] = await pickModel(t);
|
|
97
|
+
}
|
|
98
|
+
return snap;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
pickModel,
|
|
103
|
+
showModelRouter,
|
|
104
|
+
getRouterSnapshot
|
|
105
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/audit.ts
|
|
4
|
+
import { spawnSync } from "child_process";
|
|
5
|
+
var COLORS = {
|
|
6
|
+
reset: "\x1B[0m",
|
|
7
|
+
green: "\x1B[32m",
|
|
8
|
+
yellow: "\x1B[33m",
|
|
9
|
+
red: "\x1B[31m",
|
|
10
|
+
bold: "\x1B[1m",
|
|
11
|
+
dim: "\x1B[2m"
|
|
12
|
+
};
|
|
13
|
+
function runSelf(args, timeout = 3e4) {
|
|
14
|
+
const script = process.argv[1];
|
|
15
|
+
return spawnSync(process.execPath, [script, ...args], {
|
|
16
|
+
encoding: "utf-8",
|
|
17
|
+
timeout
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function parseJson(text) {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(text);
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function add(checks, name, status, message) {
|
|
28
|
+
checks.push({ name, status, message });
|
|
29
|
+
}
|
|
30
|
+
function printChecks(checks) {
|
|
31
|
+
console.log(`
|
|
32
|
+
${COLORS.bold}REX AUDIT${COLORS.reset}`);
|
|
33
|
+
for (const c of checks) {
|
|
34
|
+
const icon = c.status === "pass" ? `${COLORS.green}\u2713${COLORS.reset}` : c.status === "warn" ? `${COLORS.yellow}!${COLORS.reset}` : `${COLORS.red}\u2717${COLORS.reset}`;
|
|
35
|
+
console.log(` ${icon} ${c.name} ${COLORS.dim}\u2014 ${c.message}${COLORS.reset}`);
|
|
36
|
+
}
|
|
37
|
+
const pass = checks.filter((c) => c.status === "pass").length;
|
|
38
|
+
const warn = checks.filter((c) => c.status === "warn").length;
|
|
39
|
+
const fail = checks.filter((c) => c.status === "fail").length;
|
|
40
|
+
console.log(`
|
|
41
|
+
Summary: ${pass} pass, ${warn} warn, ${fail} fail`);
|
|
42
|
+
}
|
|
43
|
+
async function audit(options = {}) {
|
|
44
|
+
const checks = [];
|
|
45
|
+
{
|
|
46
|
+
const res = runSelf(["--version"]);
|
|
47
|
+
if (res.status === 0 && (res.stdout || "").includes("rex-claude")) {
|
|
48
|
+
add(checks, "CLI version", "pass", (res.stdout || "").trim());
|
|
49
|
+
} else {
|
|
50
|
+
add(checks, "CLI version", "fail", (res.stderr || res.stdout || "version command failed").trim());
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
{
|
|
54
|
+
const res = runSelf(["doctor"], 6e4);
|
|
55
|
+
const out = `${res.stdout || ""}${res.stderr || ""}`;
|
|
56
|
+
if ((res.status === 0 || res.status === 1) && out.includes("REX DOCTOR")) {
|
|
57
|
+
add(checks, "Doctor command", "pass", "doctor output rendered");
|
|
58
|
+
} else {
|
|
59
|
+
add(checks, "Doctor command", "fail", (out || "doctor command failed").trim().slice(0, 200));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
{
|
|
63
|
+
const res = runSelf(["status"]);
|
|
64
|
+
const out = (res.stdout || "").trim();
|
|
65
|
+
if (res.status === 0 && out.startsWith("REX")) {
|
|
66
|
+
add(checks, "Status command", "pass", out);
|
|
67
|
+
} else {
|
|
68
|
+
add(checks, "Status command", "fail", (res.stderr || out || "status command failed").slice(0, 200));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
{
|
|
72
|
+
const res = runSelf(["call", "status", "--json"]);
|
|
73
|
+
const parsed = parseJson((res.stdout || "").trim());
|
|
74
|
+
if (res.status === 0 && parsed && typeof parsed.active === "boolean") {
|
|
75
|
+
add(checks, "Call watcher status", "pass", `active=${String(parsed.active)}`);
|
|
76
|
+
} else {
|
|
77
|
+
add(checks, "Call watcher status", "warn", "call state file not ready or watcher not running yet");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
{
|
|
81
|
+
const res = runSelf(["audio", "status", "--json"]);
|
|
82
|
+
const parsed = parseJson((res.stdout || "").trim());
|
|
83
|
+
if (res.status === 0 && parsed && typeof parsed.capturing === "boolean") {
|
|
84
|
+
add(checks, "Audio logger status", "pass", `capturing=${String(parsed.capturing)}, recordings=${String(parsed.recordingsCount ?? 0)}`);
|
|
85
|
+
} else {
|
|
86
|
+
add(checks, "Audio logger status", "fail", (res.stderr || res.stdout || "audio status failed").trim().slice(0, 200));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
{
|
|
90
|
+
const res = runSelf(["voice", "status", "--json"]);
|
|
91
|
+
const parsed = parseJson((res.stdout || "").trim());
|
|
92
|
+
if (res.status === 0 && parsed && typeof parsed.optimizeEnabled === "boolean") {
|
|
93
|
+
const whisper = parsed.whisperCliAvailable === true ? "ready" : "missing";
|
|
94
|
+
add(checks, "Voice pipeline status", parsed.whisperCliAvailable ? "pass" : "warn", `whisper=${whisper}, optimize=${String(parsed.optimizeEnabled)}`);
|
|
95
|
+
} else {
|
|
96
|
+
add(checks, "Voice pipeline status", "fail", (res.stderr || res.stdout || "voice status failed").trim().slice(0, 200));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
{
|
|
100
|
+
const res = runSelf(["prune", "--stats"]);
|
|
101
|
+
if (res.status === 0) {
|
|
102
|
+
add(checks, "Memory stats", "pass", "prune --stats ok");
|
|
103
|
+
} else {
|
|
104
|
+
add(checks, "Memory stats", "warn", (res.stderr || res.stdout || "memory stats failed").trim().slice(0, 200));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
{
|
|
108
|
+
const res = runSelf(["context"]);
|
|
109
|
+
if (res.status === 0) {
|
|
110
|
+
add(checks, "Context analysis", "pass", "context command ok");
|
|
111
|
+
} else {
|
|
112
|
+
add(checks, "Context analysis", "warn", (res.stderr || res.stdout || "context command failed").trim().slice(0, 200));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
{
|
|
116
|
+
const res = runSelf(["agents", "profiles", "--json"]);
|
|
117
|
+
const parsed = parseJson((res.stdout || "").trim());
|
|
118
|
+
if (res.status === 0 && parsed && Array.isArray(parsed.profiles) && parsed.profiles.length > 0) {
|
|
119
|
+
add(checks, "Agents profiles", "pass", `${parsed.profiles.length} profiles available`);
|
|
120
|
+
} else {
|
|
121
|
+
add(checks, "Agents profiles", "fail", (res.stderr || res.stdout || "agents profiles failed").trim().slice(0, 200));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
{
|
|
125
|
+
const res = runSelf(["mcp", "list", "--json"]);
|
|
126
|
+
const parsed = parseJson((res.stdout || "").trim());
|
|
127
|
+
if (res.status === 0 && parsed && Array.isArray(parsed.servers)) {
|
|
128
|
+
add(checks, "MCP registry", "pass", `servers=${parsed.servers.length}`);
|
|
129
|
+
} else {
|
|
130
|
+
add(checks, "MCP registry", "fail", (res.stderr || res.stdout || "mcp list failed").trim().slice(0, 200));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const failCount = checks.filter((c) => c.status === "fail").length;
|
|
134
|
+
const warnCount = checks.filter((c) => c.status === "warn").length;
|
|
135
|
+
if (options.json) {
|
|
136
|
+
console.log(JSON.stringify({ checks, failCount, warnCount }, null, 2));
|
|
137
|
+
} else {
|
|
138
|
+
printChecks(checks);
|
|
139
|
+
}
|
|
140
|
+
if (failCount > 0 || options.strict && warnCount > 0) {
|
|
141
|
+
process.exitCode = 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
audit
|
|
147
|
+
};
|