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,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
|
-
"description": "
|
|
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",
|