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.
Files changed (38) hide show
  1. package/dist/agents-JIZXXASP.js +853 -0
  2. package/dist/app-3VWDSH5F.js +248 -0
  3. package/dist/audio-US2J627E.js +196 -0
  4. package/dist/audit-ZVTGE4L4.js +8 -0
  5. package/dist/call-AQZ3Z5SE.js +143 -0
  6. package/dist/chunk-5ND7JYY3.js +62 -0
  7. package/dist/chunk-6SRV2I2H.js +56 -0
  8. package/dist/{setup-AO3MW46W.js → chunk-A7ZLQUOX.js} +93 -16
  9. package/dist/chunk-E5UYN3W7.js +105 -0
  10. package/dist/chunk-HAHJD3QH.js +147 -0
  11. package/dist/{init-DLFEGD6O.js → chunk-KR7ISYZH.js} +328 -29
  12. package/dist/chunk-LTOM55UV.js +154 -0
  13. package/dist/chunk-PDX44BCA.js +11 -0
  14. package/dist/chunk-PPGYFMU5.js +67 -0
  15. package/dist/{chunk-7AGI43F5.js → chunk-WBMVBMWB.js} +4 -2
  16. package/dist/{context-FN5O5YBI.js → context-XNCG2M5Q.js} +2 -1
  17. package/dist/daemon-5KNSNFTD.js +208 -0
  18. package/dist/gateway-YLP66MCQ.js +2273 -0
  19. package/dist/hammerspoon/rex-call-watcher.lua +186 -0
  20. package/dist/index.js +309 -15
  21. package/dist/init-RDZFIBLA.js +30 -0
  22. package/dist/install-63JBDPRU.js +41 -0
  23. package/dist/{llm-YRORUH7E.js → llm-RALIPIMI.js} +2 -1
  24. package/dist/mcp_registry-DX4GGSP6.js +514 -0
  25. package/dist/migrate-GDO37TI5.js +87 -0
  26. package/dist/{optimize-UKMAGQQE.js → optimize-5TE5RKZV.js} +2 -1
  27. package/dist/paths-4SECM6E6.js +38 -0
  28. package/dist/preload-I3MYBVNU.js +78 -0
  29. package/dist/projects-V6TSLO7E.js +17 -0
  30. package/dist/{prune-2PPIVDXK.js → prune-B7F5B5OF.js} +2 -1
  31. package/dist/recategorize-YXYIMQLZ.js +155 -0
  32. package/dist/router-2JD34COX.js +12 -0
  33. package/dist/self-improve-YK7RCYF4.js +197 -0
  34. package/dist/setup-KNDTVFO6.js +8 -0
  35. package/dist/skills-AIWFY5NH.js +374 -0
  36. package/dist/voice-RITC3EVC.js +248 -0
  37. package/package.json +12 -3
  38. package/dist/gateway-EKMU5D7J.js +0 -784
@@ -0,0 +1,374 @@
1
+ #!/usr/bin/env node
2
+ import "./chunk-PDX44BCA.js";
3
+
4
+ // src/skills.ts
5
+ import { homedir } from "os";
6
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from "fs";
7
+ import { join } from "path";
8
+ var HOME = homedir();
9
+ var SKILLS_DIR = join(HOME, ".claude", "rex", "skills");
10
+ var COLORS = {
11
+ reset: "\x1B[0m",
12
+ bold: "\x1B[1m",
13
+ dim: "\x1B[2m",
14
+ green: "\x1B[32m",
15
+ yellow: "\x1B[33m",
16
+ red: "\x1B[31m",
17
+ cyan: "\x1B[36m",
18
+ magenta: "\x1B[35m"
19
+ };
20
+ var DEFAULT_SKILLS = {
21
+ "code-review": {
22
+ description: "Review code for bugs, security issues, and performance problems",
23
+ requiredTools: ["Read", "Grep", "Glob", "Bash"],
24
+ requiredMcp: [],
25
+ body: `# Code Review
26
+
27
+ ## Objective
28
+
29
+ Review code for bugs, security issues, performance. Report findings with file:line references.
30
+
31
+ ## Process
32
+
33
+ 1. **Understand scope** \u2014 Identify the files and modules under review.
34
+ 2. **Read thoroughly** \u2014 Use Read and Grep to examine implementation details.
35
+ 3. **Check for bugs** \u2014 Logic errors, off-by-one, null/undefined access, race conditions.
36
+ 4. **Check security** \u2014 SQL injection, XSS, secrets in code, missing auth checks (OWASP top 10).
37
+ 5. **Check performance** \u2014 Unbounded loops, missing pagination, N+1 queries, large payloads.
38
+ 6. **Report** \u2014 List each finding with severity (critical/high/medium/low), file:line, and suggested fix.
39
+
40
+ ## Rules
41
+
42
+ - Never modify files during a review \u2014 read-only.
43
+ - Always provide file paths and line numbers for every finding.
44
+ - Group findings by severity, most critical first.
45
+ - If no issues found, say so explicitly \u2014 don't invent problems.`
46
+ },
47
+ "bug-fix": {
48
+ description: "Identify root cause, implement fix, verify with tests",
49
+ requiredTools: ["Read", "Edit", "Write", "Grep", "Glob", "Bash"],
50
+ requiredMcp: [],
51
+ body: `# Bug Fix
52
+
53
+ ## Objective
54
+
55
+ Identify root cause, implement fix, verify with tests. Never delete tests to make them pass.
56
+
57
+ ## Process
58
+
59
+ 1. **Reproduce** \u2014 Understand the bug description. Locate relevant code with Grep/Glob.
60
+ 2. **Root cause** \u2014 Trace the issue through the codebase. Read related files to understand context.
61
+ 3. **Plan fix** \u2014 Identify the minimal change that fixes the root cause (not symptoms).
62
+ 4. **Implement** \u2014 Edit the affected files. Keep changes focused and minimal.
63
+ 5. **Verify** \u2014 Run the build. Run tests if they exist. Confirm the fix resolves the issue.
64
+ 6. **Side effects** \u2014 Grep for other consumers of changed functions/state. Ensure nothing breaks.
65
+
66
+ ## Rules
67
+
68
+ - Fix the root cause, not the symptoms.
69
+ - Never delete or skip tests to make them pass \u2014 fix the code instead.
70
+ - If stuck after 2 attempts at the same approach, stop and report what you tried.
71
+ - Always verify the build passes after your fix.`
72
+ },
73
+ "refactor": {
74
+ description: "Improve code quality without changing behavior",
75
+ requiredTools: ["Read", "Edit", "Write", "Grep", "Glob", "Bash"],
76
+ requiredMcp: [],
77
+ body: `# Refactor
78
+
79
+ ## Objective
80
+
81
+ Improve code quality without changing behavior. Verify builds and tests pass before and after.
82
+
83
+ ## Process
84
+
85
+ 1. **Baseline** \u2014 Run the build and tests BEFORE any changes. Record the results.
86
+ 2. **Analyze** \u2014 Read the code to understand current structure, patterns, and pain points.
87
+ 3. **Plan** \u2014 Identify specific improvements: extract functions, reduce duplication, improve naming, simplify logic.
88
+ 4. **Implement** \u2014 Apply changes incrementally. One logical change at a time.
89
+ 5. **Verify** \u2014 Run build and tests AFTER changes. Compare with baseline \u2014 behavior must be identical.
90
+ 6. **Review** \u2014 Read back the changed files. Ensure consistency with existing codebase patterns.
91
+
92
+ ## Rules
93
+
94
+ - Behavior must not change \u2014 refactoring is about structure, not features.
95
+ - Run build before AND after. Both must pass with zero errors.
96
+ - Follow existing codebase conventions (naming, formatting, patterns).
97
+ - Do not over-engineer. Only improve what is clearly problematic.
98
+ - If tests exist, they must all pass after refactoring without modification.`
99
+ },
100
+ "deploy-check": {
101
+ description: "Pre-deploy checklist: build, secrets, env vars, migrations",
102
+ requiredTools: ["Read", "Grep", "Glob", "Bash"],
103
+ requiredMcp: [],
104
+ body: `# Deploy Check
105
+
106
+ ## Objective
107
+
108
+ Pre-deploy checklist: build passes, no secrets in code, env vars configured, migrations ready.
109
+
110
+ ## Checklist
111
+
112
+ ### 1. Build
113
+ - [ ] \`npm run build\` (or equivalent) passes with zero errors
114
+ - [ ] No TypeScript/linter warnings in changed files
115
+
116
+ ### 2. Secrets & Security
117
+ - [ ] No API keys, tokens, or passwords hardcoded in source
118
+ - [ ] \`.env\` files are in \`.gitignore\`
119
+ - [ ] No \`console.log\` with sensitive data
120
+ - [ ] SQL queries use parameterized statements
121
+
122
+ ### 3. Environment Variables
123
+ - [ ] All required env vars are documented
124
+ - [ ] \`.env.example\` is up to date (if it exists)
125
+ - [ ] No references to localhost/dev URLs in production code
126
+
127
+ ### 4. Database
128
+ - [ ] Migrations are ready and tested
129
+ - [ ] New queries have appropriate indexes
130
+ - [ ] No destructive migrations without rollback plan
131
+
132
+ ### 5. Dependencies
133
+ - [ ] No vulnerable dependencies (\`npm audit\`)
134
+ - [ ] Lock file is committed
135
+ - [ ] No unnecessary new dependencies added
136
+
137
+ ## Rules
138
+
139
+ - This is a read-only check \u2014 do not modify files.
140
+ - Report each item as PASS / FAIL / N/A with details.
141
+ - Any FAIL is a deploy blocker \u2014 state this clearly.`
142
+ }
143
+ };
144
+ function ensureDirs() {
145
+ if (!existsSync(SKILLS_DIR)) mkdirSync(SKILLS_DIR, { recursive: true });
146
+ }
147
+ function installDefaults() {
148
+ ensureDirs();
149
+ for (const [name, skill] of Object.entries(DEFAULT_SKILLS)) {
150
+ const filePath = join(SKILLS_DIR, `${name}.md`);
151
+ if (existsSync(filePath)) continue;
152
+ const content = `---
153
+ name: ${name}
154
+ description: ${skill.description}
155
+ requiredTools: [${skill.requiredTools.join(", ")}]
156
+ requiredMcp: [${skill.requiredMcp.join(", ")}]
157
+ ---
158
+
159
+ ${skill.body}
160
+ `;
161
+ writeFileSync(filePath, content);
162
+ }
163
+ }
164
+ function parseFrontmatter(content) {
165
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
166
+ if (!fmMatch) {
167
+ return {
168
+ meta: { name: "unknown", description: "", requiredTools: [], requiredMcp: [] },
169
+ body: content
170
+ };
171
+ }
172
+ const raw = fmMatch[1];
173
+ const body = fmMatch[2].trim();
174
+ const getString = (key) => {
175
+ const m = raw.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
176
+ return m ? m[1].trim() : "";
177
+ };
178
+ const getArray = (key) => {
179
+ const m = raw.match(new RegExp(`^${key}:\\s*\\[([^\\]]*)\\]`, "m"));
180
+ if (!m) return [];
181
+ return m[1].split(",").map((s) => s.trim()).filter(Boolean);
182
+ };
183
+ return {
184
+ meta: {
185
+ name: getString("name"),
186
+ description: getString("description"),
187
+ requiredTools: getArray("requiredTools"),
188
+ requiredMcp: getArray("requiredMcp")
189
+ },
190
+ body
191
+ };
192
+ }
193
+ function readSkill(name) {
194
+ const filePath = join(SKILLS_DIR, `${name}.md`);
195
+ if (!existsSync(filePath)) return null;
196
+ const content = readFileSync(filePath, "utf-8");
197
+ const { meta, body } = parseFrontmatter(content);
198
+ return {
199
+ info: { ...meta, file: `${name}.md`, path: filePath },
200
+ body
201
+ };
202
+ }
203
+ function getAllSkills() {
204
+ ensureDirs();
205
+ installDefaults();
206
+ const files = readdirSync(SKILLS_DIR).filter((f) => f.endsWith(".md")).sort();
207
+ const skills2 = [];
208
+ for (const file of files) {
209
+ const filePath = join(SKILLS_DIR, file);
210
+ try {
211
+ const content = readFileSync(filePath, "utf-8");
212
+ const { meta } = parseFrontmatter(content);
213
+ skills2.push({ ...meta, file, path: filePath });
214
+ } catch {
215
+ }
216
+ }
217
+ return skills2;
218
+ }
219
+ function loadSkill(name) {
220
+ ensureDirs();
221
+ installDefaults();
222
+ const skill = readSkill(name);
223
+ if (!skill) return null;
224
+ return skill.body;
225
+ }
226
+ function listSkills() {
227
+ return getAllSkills();
228
+ }
229
+ function cmdList(jsonMode) {
230
+ const skills2 = getAllSkills();
231
+ if (jsonMode) {
232
+ console.log(JSON.stringify({ skills: skills2 }, null, 2));
233
+ return;
234
+ }
235
+ if (skills2.length === 0) {
236
+ console.log("No skills found. They will be created on first use.");
237
+ return;
238
+ }
239
+ const line = "\u2500".repeat(60);
240
+ console.log(`
241
+ ${COLORS.bold} REX Skills${COLORS.reset} ${COLORS.dim}(${SKILLS_DIR})${COLORS.reset}
242
+ ${line}`);
243
+ for (const s of skills2) {
244
+ const tools = s.requiredTools.length > 0 ? s.requiredTools.join(", ") : "none";
245
+ const mcp = s.requiredMcp.length > 0 ? s.requiredMcp.join(", ") : "none";
246
+ console.log(` ${COLORS.cyan}${s.name}${COLORS.reset}`);
247
+ console.log(` ${COLORS.dim}${s.description}${COLORS.reset}`);
248
+ console.log(` ${COLORS.dim}tools: ${tools} | mcp: ${mcp}${COLORS.reset}`);
249
+ console.log();
250
+ }
251
+ console.log(`${line}`);
252
+ console.log(`${COLORS.dim} ${skills2.length} skill(s) in ${SKILLS_DIR}${COLORS.reset}
253
+ `);
254
+ }
255
+ function cmdShow(args) {
256
+ const name = args[0];
257
+ if (!name) {
258
+ console.log("Usage: rex skills show <name>");
259
+ return;
260
+ }
261
+ ensureDirs();
262
+ installDefaults();
263
+ const skill = readSkill(name);
264
+ if (!skill) {
265
+ console.log(`${COLORS.red}Skill not found:${COLORS.reset} ${name}`);
266
+ console.log(`${COLORS.dim}Available: ${getAllSkills().map((s2) => s2.name).join(", ")}${COLORS.reset}`);
267
+ return;
268
+ }
269
+ const s = skill.info;
270
+ const line = "\u2500".repeat(60);
271
+ console.log(`
272
+ ${COLORS.bold} ${s.name}${COLORS.reset}
273
+ ${line}`);
274
+ console.log(` ${COLORS.dim}Description:${COLORS.reset} ${s.description}`);
275
+ console.log(` ${COLORS.dim}Tools:${COLORS.reset} ${s.requiredTools.join(", ") || "none"}`);
276
+ console.log(` ${COLORS.dim}MCP:${COLORS.reset} ${s.requiredMcp.join(", ") || "none"}`);
277
+ console.log(` ${COLORS.dim}Path:${COLORS.reset} ${s.path}`);
278
+ console.log(`${line}
279
+ `);
280
+ console.log(skill.body);
281
+ console.log();
282
+ }
283
+ function cmdAdd(args) {
284
+ const name = args[0];
285
+ if (!name) {
286
+ console.log("Usage: rex skills add <name>");
287
+ console.log("Creates a new skill template in the skills directory.");
288
+ return;
289
+ }
290
+ ensureDirs();
291
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
292
+ const filePath = join(SKILLS_DIR, `${slug}.md`);
293
+ if (existsSync(filePath)) {
294
+ console.log(`${COLORS.yellow}Skill already exists:${COLORS.reset} ${slug}`);
295
+ console.log(`${COLORS.dim}Edit it at: ${filePath}${COLORS.reset}`);
296
+ return;
297
+ }
298
+ const content = `---
299
+ name: ${slug}
300
+ description: TODO \u2014 describe this skill
301
+ requiredTools: [Read, Grep]
302
+ requiredMcp: []
303
+ ---
304
+
305
+ # ${slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ")}
306
+
307
+ ## Objective
308
+
309
+ TODO \u2014 what this skill does.
310
+
311
+ ## Process
312
+
313
+ 1. TODO \u2014 step 1
314
+ 2. TODO \u2014 step 2
315
+ 3. TODO \u2014 step 3
316
+
317
+ ## Rules
318
+
319
+ - TODO \u2014 constraints and guardrails
320
+ `;
321
+ writeFileSync(filePath, content);
322
+ console.log(`${COLORS.green}Skill created:${COLORS.reset} ${slug}`);
323
+ console.log(`${COLORS.dim}Edit it at: ${filePath}${COLORS.reset}`);
324
+ }
325
+ function cmdDelete(args) {
326
+ const name = args[0];
327
+ if (!name) {
328
+ console.log("Usage: rex skills delete <name>");
329
+ return;
330
+ }
331
+ ensureDirs();
332
+ const filePath = join(SKILLS_DIR, `${name}.md`);
333
+ if (!existsSync(filePath)) {
334
+ console.log(`${COLORS.red}Skill not found:${COLORS.reset} ${name}`);
335
+ return;
336
+ }
337
+ unlinkSync(filePath);
338
+ console.log(`${COLORS.green}Deleted:${COLORS.reset} ${name}`);
339
+ }
340
+ async function skills(args) {
341
+ ensureDirs();
342
+ installDefaults();
343
+ const sub = args[0] || "list";
344
+ const rest = args.slice(1);
345
+ const jsonMode = args.includes("--json");
346
+ switch (sub) {
347
+ case "list":
348
+ cmdList(jsonMode);
349
+ return;
350
+ case "show":
351
+ cmdShow(rest);
352
+ return;
353
+ case "add":
354
+ cmdAdd(rest);
355
+ return;
356
+ case "delete":
357
+ cmdDelete(rest);
358
+ return;
359
+ default:
360
+ console.log("Usage: rex skills <list|show|add|delete>");
361
+ console.log("");
362
+ console.log(" rex skills list List all available skills");
363
+ console.log(" rex skills show <name> Show skill content");
364
+ console.log(" rex skills add <name> Create a new skill template");
365
+ console.log(" rex skills delete <name> Delete a skill");
366
+ console.log("");
367
+ console.log(`Skills directory: ${SKILLS_DIR}`);
368
+ }
369
+ }
370
+ export {
371
+ listSkills,
372
+ loadSkill,
373
+ skills
374
+ };
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env node
2
+ import "./chunk-PDX44BCA.js";
3
+
4
+ // src/voice.ts
5
+ import { homedir } from "os";
6
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
7
+ import { join } from "path";
8
+ import { spawnSync } from "child_process";
9
+ var HOME = homedir();
10
+ var RUNTIME_DIR = join(HOME, ".rex-memory", "runtime");
11
+ var RECORDINGS_DIR = join(HOME, ".rex-memory", "recordings");
12
+ var SETTINGS_FILE = join(RUNTIME_DIR, "voice-settings.json");
13
+ function ensureDirs() {
14
+ if (!existsSync(RUNTIME_DIR)) mkdirSync(RUNTIME_DIR, { recursive: true });
15
+ }
16
+ function readSettings() {
17
+ try {
18
+ if (existsSync(SETTINGS_FILE)) {
19
+ const parsed = JSON.parse(readFileSync(SETTINGS_FILE, "utf-8"));
20
+ return {
21
+ optimizeEnabled: parsed.optimizeEnabled === true,
22
+ optimizeModel: typeof parsed.optimizeModel === "string" && parsed.optimizeModel.trim().length > 0 ? parsed.optimizeModel : process.env.REX_OPTIMIZE_MODEL || "qwen3.5:4b"
23
+ };
24
+ }
25
+ } catch {
26
+ }
27
+ return {
28
+ optimizeEnabled: false,
29
+ optimizeModel: process.env.REX_OPTIMIZE_MODEL || "qwen3.5:4b"
30
+ };
31
+ }
32
+ function writeSettings(settings) {
33
+ ensureDirs();
34
+ writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2));
35
+ }
36
+ function whisperExists() {
37
+ const check = spawnSync("whisper-cli", ["--help"], { stdio: "ignore" });
38
+ return check.status === 0;
39
+ }
40
+ function defaultWhisperModelPath() {
41
+ const envPath = process.env.REX_WHISPER_MODEL;
42
+ if (envPath) return envPath;
43
+ const candidates = [
44
+ join(HOME, ".rex-memory", "models", "ggml-base.en.bin"),
45
+ join(HOME, ".rex-memory", "models", "ggml-small.en.bin"),
46
+ join(HOME, ".rex-memory", "models", "ggml-tiny.en.bin"),
47
+ join(HOME, ".whisper", "models", "ggml-base.en.bin"),
48
+ join(HOME, ".whisper", "models", "ggml-small.en.bin"),
49
+ join(HOME, ".whisper", "models", "ggml-tiny.en.bin")
50
+ ];
51
+ const found = candidates.find((c) => existsSync(c));
52
+ return found || candidates[0];
53
+ }
54
+ function latestRecording() {
55
+ if (!existsSync(RECORDINGS_DIR)) return null;
56
+ const files = readdirSync(RECORDINGS_DIR).filter((f) => f.endsWith(".wav")).map((f) => join(RECORDINGS_DIR, f));
57
+ if (files.length === 0) return null;
58
+ files.sort((a, b) => {
59
+ try {
60
+ return statSync(b).mtimeMs - statSync(a).mtimeMs;
61
+ } catch {
62
+ return 0;
63
+ }
64
+ });
65
+ return files[0] || null;
66
+ }
67
+ function parseWhisperOutput(raw) {
68
+ const lines = raw.split("\n").map((l) => l.trim()).filter(Boolean);
69
+ const cleaned = [];
70
+ for (const line of lines) {
71
+ if (line.startsWith("whisper_") || line.startsWith("main:") || line.startsWith("system_info:")) {
72
+ continue;
73
+ }
74
+ const ts = line.match(/^\[[0-9:.\-\s>]+\]\s*(.*)$/);
75
+ if (ts) {
76
+ if (ts[1] && ts[1].trim().length > 0) cleaned.push(ts[1].trim());
77
+ continue;
78
+ }
79
+ cleaned.push(line);
80
+ }
81
+ const text = cleaned.join(" ").replace(/\s+/g, " ").trim();
82
+ return text;
83
+ }
84
+ async function optimizePrompt(text, model) {
85
+ const ollama = process.env.OLLAMA_URL || "http://localhost:11434";
86
+ const prompt = `You are a transcription optimizer for coding assistants. Rewrite the transcript into a clean, structured prompt with technical terms corrected and no filler. Return only optimized text.
87
+
88
+ Transcript:
89
+ ${text}`;
90
+ const res = await fetch(`${ollama}/api/generate`, {
91
+ method: "POST",
92
+ headers: { "Content-Type": "application/json" },
93
+ body: JSON.stringify({
94
+ model,
95
+ stream: false,
96
+ prompt
97
+ })
98
+ });
99
+ if (!res.ok) {
100
+ throw new Error(`LLM optimize failed (${res.status})`);
101
+ }
102
+ const data = await res.json();
103
+ const out = (data.response || "").trim();
104
+ if (!out) throw new Error("LLM optimize returned empty output");
105
+ return out;
106
+ }
107
+ async function transcribe(args) {
108
+ const jsonMode = args.includes("--json");
109
+ const optimizeFlag = args.includes("--optimize");
110
+ const fileArg = args.find((a) => a.endsWith(".wav")) || null;
111
+ const source = fileArg || latestRecording();
112
+ if (!source) {
113
+ const msg = "No recording found. Provide a WAV path or start audio logger first.";
114
+ if (jsonMode) {
115
+ console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
116
+ return;
117
+ }
118
+ console.error(msg);
119
+ process.exit(1);
120
+ }
121
+ if (!whisperExists()) {
122
+ const msg = "whisper-cli not found. Install whisper.cpp CLI and ensure whisper-cli is in PATH.";
123
+ if (jsonMode) {
124
+ console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
125
+ return;
126
+ }
127
+ console.error(msg);
128
+ process.exit(1);
129
+ }
130
+ const modelPath = defaultWhisperModelPath();
131
+ if (!existsSync(modelPath)) {
132
+ const msg = `Whisper model not found: ${modelPath}. Set REX_WHISPER_MODEL or place ggml model in ~/.rex-memory/models.`;
133
+ if (jsonMode) {
134
+ console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
135
+ return;
136
+ }
137
+ console.error(msg);
138
+ process.exit(1);
139
+ }
140
+ const run = spawnSync("whisper-cli", ["-m", modelPath, "-f", source, "-nt"], {
141
+ encoding: "utf-8",
142
+ maxBuffer: 1024 * 1024 * 10
143
+ });
144
+ if (run.status !== 0) {
145
+ const err = (run.stderr || run.stdout || "").trim() || "whisper-cli failed";
146
+ if (jsonMode) {
147
+ console.log(JSON.stringify({ ok: false, error: err }, null, 2));
148
+ return;
149
+ }
150
+ console.error(err);
151
+ process.exit(1);
152
+ }
153
+ const transcript = parseWhisperOutput((run.stdout || "").toString());
154
+ const settings = readSettings();
155
+ const optimize = optimizeFlag || settings.optimizeEnabled;
156
+ let optimized = null;
157
+ let optimizeError = null;
158
+ if (optimize && transcript.length > 0) {
159
+ try {
160
+ optimized = await optimizePrompt(transcript, settings.optimizeModel);
161
+ } catch (e) {
162
+ optimizeError = e instanceof Error ? e.message : String(e);
163
+ }
164
+ }
165
+ if (jsonMode) {
166
+ console.log(
167
+ JSON.stringify(
168
+ {
169
+ ok: true,
170
+ source,
171
+ modelPath,
172
+ transcript,
173
+ optimizeEnabled: optimize,
174
+ optimizeModel: settings.optimizeModel,
175
+ optimized,
176
+ optimizeError,
177
+ output: optimized || transcript
178
+ },
179
+ null,
180
+ 2
181
+ )
182
+ );
183
+ return;
184
+ }
185
+ if (optimized) {
186
+ console.log(optimized);
187
+ } else {
188
+ console.log(transcript);
189
+ if (optimizeError) {
190
+ console.error(`Optimize skipped: ${optimizeError}`);
191
+ }
192
+ }
193
+ }
194
+ function showStatus(jsonMode) {
195
+ const settings = readSettings();
196
+ const payload = {
197
+ optimizeEnabled: settings.optimizeEnabled,
198
+ optimizeModel: settings.optimizeModel,
199
+ whisperCliAvailable: whisperExists(),
200
+ whisperModelPath: defaultWhisperModelPath(),
201
+ whisperModelExists: existsSync(defaultWhisperModelPath()),
202
+ recordingsDir: RECORDINGS_DIR
203
+ };
204
+ if (jsonMode) {
205
+ console.log(JSON.stringify(payload, null, 2));
206
+ return;
207
+ }
208
+ console.log(`Optimize: ${payload.optimizeEnabled ? "on" : "off"} (${payload.optimizeModel})`);
209
+ console.log(`whisper-cli: ${payload.whisperCliAvailable ? "available" : "missing"}`);
210
+ console.log(`Model: ${payload.whisperModelPath}`);
211
+ console.log(`Recordings: ${payload.recordingsDir}`);
212
+ }
213
+ function setOptimize(args) {
214
+ const value = (args[0] || "").toLowerCase();
215
+ const model = args[1];
216
+ if (!["on", "off"].includes(value)) {
217
+ console.log("Usage: rex voice set-optimize <on|off> [model]");
218
+ process.exit(1);
219
+ }
220
+ const current = readSettings();
221
+ const next = {
222
+ optimizeEnabled: value === "on",
223
+ optimizeModel: model || current.optimizeModel
224
+ };
225
+ writeSettings(next);
226
+ console.log(JSON.stringify({ ok: true, ...next }, null, 2));
227
+ }
228
+ async function voice(args) {
229
+ const sub = args[0] || "status";
230
+ const rest = args.slice(1);
231
+ switch (sub) {
232
+ case "status":
233
+ case "settings":
234
+ showStatus(args.includes("--json"));
235
+ return;
236
+ case "set-optimize":
237
+ setOptimize(rest);
238
+ return;
239
+ case "transcribe":
240
+ await transcribe(rest);
241
+ return;
242
+ default:
243
+ console.log("Usage: rex voice <status|settings|set-optimize|transcribe>");
244
+ }
245
+ }
246
+ export {
247
+ voice
248
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rex-claude",
3
- "version": "4.0.0",
4
- "description": "Claude Code sous steroides guards, health checks, memory RAG",
3
+ "version": "6.0.0",
4
+ "description": "REX dev assistant with Telegram gateway, memory RAG, guards, model switching, and macOS native app",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "rex": "./dist/index.js"
@@ -22,7 +22,12 @@
22
22
  "productivity",
23
23
  "ai-assistant",
24
24
  "guards",
25
- "hooks"
25
+ "hooks",
26
+ "telegram",
27
+ "memory",
28
+ "rag",
29
+ "ollama",
30
+ "qwen"
26
31
  ],
27
32
  "author": "Kevin <kevin@dstudio.company>",
28
33
  "license": "MIT",
@@ -34,6 +39,10 @@
34
39
  "engines": {
35
40
  "node": ">=20"
36
41
  },
42
+ "dependencies": {
43
+ "better-sqlite3": "^11.8.1",
44
+ "sqlite-vec": "^0.1.6"
45
+ },
37
46
  "devDependencies": {
38
47
  "@rex/core": "workspace:*",
39
48
  "@types/node": "^25.3.3",