rex-claude 2.2.0 → 3.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 (33) hide show
  1. package/dist/chunk-7AGI43F5.js +42 -0
  2. package/dist/context-FN5O5YBI.js +114 -0
  3. package/dist/gateway-EOVQXRON.js +198 -0
  4. package/dist/guards/completion-guard.sh +15 -2
  5. package/dist/guards/dangerous-cmd-guard.sh +2 -2
  6. package/dist/guards/error-pattern-guard.sh +45 -0
  7. package/dist/guards/notify-telegram.sh +34 -0
  8. package/dist/guards/test-protect-guard.sh +2 -2
  9. package/dist/guards/ui-checklist-guard.sh +1 -1
  10. package/dist/index.js +52 -13
  11. package/dist/{init-NXU37FCV.js → init-W3XGDQ6D.js} +159 -1
  12. package/dist/llm-YRORUH7E.js +9 -0
  13. package/dist/optimize-UKMAGQQE.js +148 -0
  14. package/dist/setup-AO3MW46W.js +252 -0
  15. package/dist/skills/build-validate/SKILL.md +23 -0
  16. package/dist/skills/code-review/SKILL.md +25 -0
  17. package/dist/skills/context-loader/SKILL.md +25 -0
  18. package/dist/skills/debug-assist/SKILL.md +26 -0
  19. package/dist/skills/deploy-checklist/SKILL.md +61 -0
  20. package/dist/skills/dstudio-design-system/SKILL.md +120 -0
  21. package/dist/skills/figma-workflow/SKILL.md +23 -0
  22. package/dist/skills/fix-issue/SKILL.md +43 -0
  23. package/dist/skills/new-rule/SKILL.md +19 -0
  24. package/dist/skills/notify/SKILL.md +26 -0
  25. package/dist/skills/one-shot/SKILL.md +18 -0
  26. package/dist/skills/pr-review-loop/SKILL.md +48 -0
  27. package/dist/skills/project-init/SKILL.md +45 -0
  28. package/dist/skills/research/SKILL.md +17 -0
  29. package/dist/skills/rex-boot/SKILL.md +64 -0
  30. package/dist/skills/spec-interview/SKILL.md +20 -0
  31. package/dist/skills/token-guard/SKILL.md +26 -0
  32. package/package.json +4 -4
  33. package/dist/optimize-NE47FMOP.js +0 -111
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/init.ts
4
- import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "fs";
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, readdirSync, statSync } from "fs";
5
5
  import { join, dirname } from "path";
6
6
  import { homedir } from "os";
7
7
  import { execSync } from "child_process";
@@ -42,6 +42,7 @@ function ensureDir(dir) {
42
42
  }
43
43
  var PLIST_LABEL = "com.dstudio.rex";
44
44
  var INGEST_PLIST_LABEL = "com.dstudio.rex-ingest";
45
+ var GATEWAY_PLIST_LABEL = "com.dstudio.rex-gateway";
45
46
  function installIngestAgent() {
46
47
  if (process.platform !== "darwin") {
47
48
  info("Auto-ingest only supported on macOS");
@@ -114,6 +115,101 @@ function uninstallIngestAgent() {
114
115
  }
115
116
  ok("Ingest LaunchAgent removed");
116
117
  }
118
+ function installGatewayAgent() {
119
+ if (process.platform !== "darwin") {
120
+ info("Gateway LaunchAgent only supported on macOS");
121
+ return;
122
+ }
123
+ const settingsPath = join(homedir(), ".claude", "settings.json");
124
+ let hasTelegram = false;
125
+ try {
126
+ const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
127
+ hasTelegram = !!(settings.env?.REX_TELEGRAM_BOT_TOKEN && settings.env?.REX_TELEGRAM_CHAT_ID);
128
+ } catch {
129
+ }
130
+ if (!hasTelegram) {
131
+ info("Telegram not configured \u2014 skipping gateway LaunchAgent (run rex setup first)");
132
+ return;
133
+ }
134
+ const launchAgentsDir = join(homedir(), "Library", "LaunchAgents");
135
+ ensureDir(launchAgentsDir);
136
+ const plistPath = join(launchAgentsDir, `${GATEWAY_PLIST_LABEL}.plist`);
137
+ let rexBin = "";
138
+ try {
139
+ rexBin = execSync("which rex", { encoding: "utf-8" }).trim();
140
+ } catch {
141
+ }
142
+ if (!rexBin) {
143
+ info("rex binary not in PATH \u2014 skipping gateway LaunchAgent");
144
+ return;
145
+ }
146
+ if (existsSync(plistPath)) {
147
+ skip("Gateway LaunchAgent already installed (Telegram bot always-on)");
148
+ return;
149
+ }
150
+ let botToken = "";
151
+ let chatIdVal = "";
152
+ try {
153
+ const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
154
+ botToken = settings.env?.REX_TELEGRAM_BOT_TOKEN || "";
155
+ chatIdVal = settings.env?.REX_TELEGRAM_CHAT_ID || "";
156
+ } catch {
157
+ }
158
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
159
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
160
+ <plist version="1.0">
161
+ <dict>
162
+ <key>Label</key>
163
+ <string>${GATEWAY_PLIST_LABEL}</string>
164
+ <key>ProgramArguments</key>
165
+ <array>
166
+ <string>${rexBin}</string>
167
+ <string>gateway</string>
168
+ </array>
169
+ <key>RunAtLoad</key>
170
+ <true/>
171
+ <key>KeepAlive</key>
172
+ <true/>
173
+ <key>StandardOutPath</key>
174
+ <string>${join(homedir(), ".claude", "rex-gateway.log")}</string>
175
+ <key>StandardErrorPath</key>
176
+ <string>${join(homedir(), ".claude", "rex-gateway.log")}</string>
177
+ <key>EnvironmentVariables</key>
178
+ <dict>
179
+ <key>PATH</key>
180
+ <string>/usr/local/bin:/usr/bin:/bin:${dirname(rexBin)}</string>
181
+ <key>REX_TELEGRAM_BOT_TOKEN</key>
182
+ <string>${botToken}</string>
183
+ <key>REX_TELEGRAM_CHAT_ID</key>
184
+ <string>${chatIdVal}</string>
185
+ </dict>
186
+ </dict>
187
+ </plist>
188
+ `;
189
+ writeFileSync(plistPath, plist);
190
+ try {
191
+ execSync(`launchctl load ${plistPath}`, { stdio: "ignore" });
192
+ } catch {
193
+ }
194
+ ok("Gateway LaunchAgent installed \u2014 Telegram bot always-on (auto-restart)");
195
+ }
196
+ function uninstallGatewayAgent() {
197
+ if (process.platform !== "darwin") return;
198
+ const plistPath = join(homedir(), "Library", "LaunchAgents", `${GATEWAY_PLIST_LABEL}.plist`);
199
+ if (!existsSync(plistPath)) {
200
+ info("Gateway LaunchAgent not installed");
201
+ return;
202
+ }
203
+ try {
204
+ execSync(`launchctl unload ${plistPath}`, { stdio: "ignore" });
205
+ } catch {
206
+ }
207
+ try {
208
+ unlinkSync(plistPath);
209
+ } catch {
210
+ }
211
+ ok("Gateway LaunchAgent removed");
212
+ }
117
213
  function installApp() {
118
214
  if (process.platform !== "darwin") return;
119
215
  const thisDir = new URL(".", import.meta.url).pathname;
@@ -213,6 +309,7 @@ function uninstallStartup() {
213
309
  ok("LaunchAgent removed");
214
310
  }
215
311
  uninstallIngestAgent();
312
+ uninstallGatewayAgent();
216
313
  }
217
314
  async function init() {
218
315
  const claudeDir = join(homedir(), ".claude");
@@ -345,6 +442,18 @@ fi
345
442
  event: "PostToolUse",
346
443
  desc: "Scope creep detector",
347
444
  matcher: "Edit|Write"
445
+ },
446
+ {
447
+ file: "error-pattern-guard.sh",
448
+ event: "PostToolUse",
449
+ desc: "Recurring error pattern detector",
450
+ matcher: "Bash"
451
+ },
452
+ {
453
+ file: "notify-telegram.sh",
454
+ event: "Stop",
455
+ desc: "Telegram notification on task completion",
456
+ matcher: void 0
348
457
  }
349
458
  ];
350
459
  let guardsInstalled = 0;
@@ -381,6 +490,7 @@ fi
381
490
  }
382
491
  installStartup();
383
492
  installIngestAgent();
493
+ installGatewayAgent();
384
494
  installApp();
385
495
  let ollamaOk = false;
386
496
  try {
@@ -404,6 +514,52 @@ fi
404
514
  } else {
405
515
  info("Ollama not running \u2014 needed for memory/RAG. Install: https://ollama.ai");
406
516
  }
517
+ const bundledSkillsDir = join(thisDir, "..", "skills");
518
+ if (existsSync(bundledSkillsDir)) {
519
+ const skillsTargetDir = join(claudeDir, "skills");
520
+ ensureDir(skillsTargetDir);
521
+ let skillsSynced = 0;
522
+ try {
523
+ const skillDirs = readdirSync(bundledSkillsDir).filter((d) => {
524
+ try {
525
+ return statSync(join(bundledSkillsDir, d)).isDirectory();
526
+ } catch {
527
+ return false;
528
+ }
529
+ });
530
+ for (const skill of skillDirs) {
531
+ const srcSkill = join(bundledSkillsDir, skill, "SKILL.md");
532
+ const destSkillDir = join(skillsTargetDir, skill);
533
+ const destSkill = join(destSkillDir, "SKILL.md");
534
+ if (existsSync(srcSkill)) {
535
+ const srcContent = readFileSync(srcSkill, "utf-8");
536
+ const destContent = existsSync(destSkill) ? readFileSync(destSkill, "utf-8") : "";
537
+ if (srcContent !== destContent) {
538
+ ensureDir(destSkillDir);
539
+ writeFileSync(destSkill, srcContent);
540
+ skillsSynced++;
541
+ }
542
+ }
543
+ }
544
+ } catch {
545
+ }
546
+ if (skillsSynced > 0) {
547
+ ok(`${skillsSynced} skills synced from rex-claude`);
548
+ } else {
549
+ skip("All skills up to date");
550
+ }
551
+ }
552
+ const memoryMonorepoDir = join(thisDir, "..", "..", "memory");
553
+ const memoryTargetDir = join(homedir(), ".rex-memory");
554
+ if (existsSync(join(memoryMonorepoDir, "package.json")) && !existsSync(join(memoryTargetDir, "package.json"))) {
555
+ try {
556
+ execSync(`cp -R "${memoryMonorepoDir}" "${memoryTargetDir}"`, { stdio: "ignore" });
557
+ execSync("npm install --production 2>/dev/null", { cwd: memoryTargetDir, stdio: "ignore" });
558
+ ok("@rex/memory synced to ~/.rex-memory/");
559
+ } catch {
560
+ info("Could not sync @rex/memory \u2014 install manually");
561
+ }
562
+ }
407
563
  writeJson(settingsPath, settings);
408
564
  console.log(`
409
565
  ${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}`);
@@ -424,8 +580,10 @@ ${COLORS.bold} REX initialized!${COLORS.reset}`);
424
580
  export {
425
581
  init,
426
582
  installApp,
583
+ installGatewayAgent,
427
584
  installIngestAgent,
428
585
  installStartup,
586
+ uninstallGatewayAgent,
429
587
  uninstallIngestAgent,
430
588
  uninstallStartup
431
589
  };
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ detectModel,
4
+ llm
5
+ } from "./chunk-7AGI43F5.js";
6
+ export {
7
+ detectModel,
8
+ llm
9
+ };
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ detectModel,
4
+ llm
5
+ } from "./chunk-7AGI43F5.js";
6
+
7
+ // src/optimize.ts
8
+ import { readFileSync, writeFileSync, existsSync, readdirSync } from "fs";
9
+ import { join } from "path";
10
+ import { homedir } from "os";
11
+ var COLORS = {
12
+ reset: "\x1B[0m",
13
+ green: "\x1B[32m",
14
+ yellow: "\x1B[33m",
15
+ red: "\x1B[31m",
16
+ bold: "\x1B[1m",
17
+ dim: "\x1B[2m",
18
+ cyan: "\x1B[36m"
19
+ };
20
+ var OLLAMA_URL = process.env.OLLAMA_URL || "http://localhost:11434";
21
+ function collectImports(content, baseDir) {
22
+ const imports = [];
23
+ const importRegex = /@import\s+["']([^"']+)["']/g;
24
+ let match;
25
+ while ((match = importRegex.exec(content)) !== null) {
26
+ const importPath = join(baseDir, match[1]);
27
+ if (existsSync(importPath)) {
28
+ imports.push(`
29
+ --- ${match[1]} ---
30
+ ${readFileSync(importPath, "utf-8")}`);
31
+ }
32
+ }
33
+ const rulesDir = join(baseDir, "rules");
34
+ if (existsSync(rulesDir)) {
35
+ try {
36
+ const rules = readdirSync(rulesDir).filter((f) => f.endsWith(".md"));
37
+ for (const rule of rules) {
38
+ imports.push(`
39
+ --- rules/${rule} ---
40
+ ${readFileSync(join(rulesDir, rule), "utf-8")}`);
41
+ }
42
+ } catch {
43
+ }
44
+ }
45
+ return imports.join("\n");
46
+ }
47
+ async function optimize(apply = false) {
48
+ const line = "\u2550".repeat(45);
49
+ console.log(`
50
+ ${line}`);
51
+ console.log(`${COLORS.bold} REX OPTIMIZE${apply ? " --apply" : ""}${COLORS.reset}`);
52
+ console.log(`${line}
53
+ `);
54
+ try {
55
+ const res = await fetch(`${OLLAMA_URL}/api/tags`);
56
+ if (!res.ok) throw new Error();
57
+ } catch {
58
+ console.error(`${COLORS.red}Ollama not running.${COLORS.reset} Start it: ollama serve`);
59
+ process.exit(1);
60
+ }
61
+ const cwd = process.cwd();
62
+ const projectClaudeMd = join(cwd, "CLAUDE.md");
63
+ const globalClaudeMd = join(homedir(), ".claude", "CLAUDE.md");
64
+ const target = existsSync(projectClaudeMd) ? projectClaudeMd : globalClaudeMd;
65
+ if (!existsSync(target)) {
66
+ console.error(`${COLORS.red}No CLAUDE.md found.${COLORS.reset}`);
67
+ process.exit(1);
68
+ }
69
+ const content = readFileSync(target, "utf-8");
70
+ const baseDir = join(target, "..");
71
+ const importedContent = collectImports(content, baseDir);
72
+ const fullContent = content + importedContent;
73
+ const lines = content.split("\n").length;
74
+ const chars = fullContent.length;
75
+ const tokens = Math.ceil(chars / 4);
76
+ console.log(` ${COLORS.cyan}Target:${COLORS.reset} ${target}`);
77
+ console.log(` ${COLORS.cyan}Size:${COLORS.reset} ${lines} lines, ~${tokens} tokens (with imports)`);
78
+ console.log();
79
+ const model = await detectModel();
80
+ console.log(` ${COLORS.dim}Analyzing with ${model}...${COLORS.reset}`);
81
+ if (!apply) {
82
+ const analysis = await llm(
83
+ `Analyze this CLAUDE.md file and all its imported rules. Provide specific suggestions to reduce token count while keeping all important instructions. Focus on:
84
+ 1. Redundant or duplicate instructions (across main file AND imported rules)
85
+ 2. Overly verbose sections that could be shortened
86
+ 3. Content that could be moved to separate @import files
87
+ 4. Dead or outdated references
88
+ 5. Contradictions between files
89
+
90
+ CLAUDE.md + imports:
91
+ ---
92
+ ${fullContent.slice(0, 8e3)}
93
+ ---
94
+
95
+ Provide a concise analysis with specific, actionable suggestions. Format each suggestion as:
96
+ - [SECTION] What to change and why (estimated savings: N tokens)`,
97
+ "You are a technical editor that optimizes AI instruction files. Be direct and specific. Output only the analysis, no preamble.",
98
+ model
99
+ );
100
+ console.log(`
101
+ ${COLORS.bold} Analysis:${COLORS.reset}
102
+ `);
103
+ for (const l of analysis.split("\n")) {
104
+ console.log(` ${l}`);
105
+ }
106
+ console.log(`
107
+ ${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}`);
108
+ console.log(`
109
+ ${COLORS.dim}Run ${COLORS.cyan}rex optimize --apply${COLORS.reset}${COLORS.dim} to auto-apply suggestions${COLORS.reset}`);
110
+ } else {
111
+ const backupPath = target + ".bak";
112
+ writeFileSync(backupPath, content);
113
+ console.log(` ${COLORS.green}\u2713${COLORS.reset} Backup saved to ${backupPath}`);
114
+ const optimized = await llm(
115
+ `Rewrite this CLAUDE.md to be more concise while keeping ALL important instructions. Rules:
116
+ - Remove redundancy and duplication
117
+ - Shorten verbose explanations to bullet points
118
+ - Keep all actionable rules, security requirements, and workflow steps
119
+ - Preserve @import references
120
+ - Use tables instead of verbose lists where possible
121
+ - Remove filler words and unnecessary context
122
+ - Output ONLY the rewritten CLAUDE.md content, nothing else
123
+
124
+ Original:
125
+ ---
126
+ ${content.slice(0, 8e3)}
127
+ ---`,
128
+ "You rewrite AI instruction files to be maximally concise. Output only the rewritten file, no commentary.",
129
+ model
130
+ );
131
+ writeFileSync(target, optimized);
132
+ const oldTokens = Math.ceil(content.length / 4);
133
+ const newTokens = Math.ceil(optimized.length / 4);
134
+ const saved = oldTokens - newTokens;
135
+ const pct = Math.round(saved / oldTokens * 100);
136
+ console.log(` ${COLORS.green}\u2713${COLORS.reset} CLAUDE.md rewritten`);
137
+ console.log(`
138
+ ${COLORS.bold}Before:${COLORS.reset} ~${oldTokens} tokens`);
139
+ console.log(` ${COLORS.bold}After:${COLORS.reset} ~${newTokens} tokens`);
140
+ console.log(` ${COLORS.bold}Saved:${COLORS.reset} ~${saved} tokens (${pct}%)`);
141
+ console.log(`
142
+ ${COLORS.dim}Review changes: diff ${backupPath} ${target}${COLORS.reset}`);
143
+ }
144
+ console.log();
145
+ }
146
+ export {
147
+ optimize
148
+ };
@@ -0,0 +1,252 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/setup.ts
4
+ import { execSync } from "child_process";
5
+ import { platform, totalmem, homedir } from "os";
6
+ import { readFileSync, writeFileSync } from "fs";
7
+ import { join } from "path";
8
+ import { createInterface } from "readline";
9
+ var COLORS = {
10
+ reset: "\x1B[0m",
11
+ green: "\x1B[32m",
12
+ yellow: "\x1B[33m",
13
+ red: "\x1B[31m",
14
+ bold: "\x1B[1m",
15
+ dim: "\x1B[2m",
16
+ cyan: "\x1B[36m"
17
+ };
18
+ function ok(msg) {
19
+ console.log(` ${COLORS.green}\u2713${COLORS.reset} ${msg}`);
20
+ }
21
+ function info(msg) {
22
+ console.log(` ${COLORS.cyan}\u2139${COLORS.reset} ${msg}`);
23
+ }
24
+ function fail(msg) {
25
+ console.log(` ${COLORS.red}\u2717${COLORS.reset} ${msg}`);
26
+ }
27
+ var OLLAMA_URL = process.env.OLLAMA_URL || "http://localhost:11434";
28
+ async function isOllamaInstalled() {
29
+ try {
30
+ execSync("which ollama", { stdio: "ignore" });
31
+ return true;
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+ async function isOllamaRunning() {
37
+ try {
38
+ const res = await fetch(`${OLLAMA_URL}/api/tags`);
39
+ return res.ok;
40
+ } catch {
41
+ return false;
42
+ }
43
+ }
44
+ async function getInstalledModels() {
45
+ try {
46
+ const res = await fetch(`${OLLAMA_URL}/api/tags`);
47
+ const data = await res.json();
48
+ return data.models.map((m) => m.name);
49
+ } catch {
50
+ return [];
51
+ }
52
+ }
53
+ function pullModel(model) {
54
+ console.log(` ${COLORS.dim}Pulling ${model}...${COLORS.reset}`);
55
+ try {
56
+ execSync(`ollama pull ${model}`, { stdio: "inherit" });
57
+ ok(`${model} installed`);
58
+ } catch {
59
+ fail(`Failed to pull ${model}`);
60
+ }
61
+ }
62
+ async function testEmbed() {
63
+ try {
64
+ const res = await fetch(`${OLLAMA_URL}/api/embed`, {
65
+ method: "POST",
66
+ headers: { "Content-Type": "application/json" },
67
+ body: JSON.stringify({ model: "nomic-embed-text", input: "test embedding" })
68
+ });
69
+ return res.ok;
70
+ } catch {
71
+ return false;
72
+ }
73
+ }
74
+ async function testGenerate(model) {
75
+ try {
76
+ const res = await fetch(`${OLLAMA_URL}/api/generate`, {
77
+ method: "POST",
78
+ headers: { "Content-Type": "application/json" },
79
+ body: JSON.stringify({ model, prompt: 'Say "ok" in one word.', stream: false })
80
+ });
81
+ return res.ok;
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+ function prompt(question) {
87
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
88
+ return new Promise((resolve) => {
89
+ rl.question(` ${COLORS.cyan}?${COLORS.reset} ${question} `, (answer) => {
90
+ rl.close();
91
+ resolve(answer.trim());
92
+ });
93
+ });
94
+ }
95
+ async function setupTelegram() {
96
+ console.log(`
97
+ ${COLORS.bold}Telegram Gateway${COLORS.reset}`);
98
+ const settingsPath = join(homedir(), ".claude", "settings.json");
99
+ let settings = {};
100
+ try {
101
+ settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
102
+ } catch {
103
+ }
104
+ if (!settings.env) settings.env = {};
105
+ const existingToken = settings.env.REX_TELEGRAM_BOT_TOKEN;
106
+ const existingChat = settings.env.REX_TELEGRAM_CHAT_ID;
107
+ if (existingToken && existingChat) {
108
+ try {
109
+ const res = await fetch(`https://api.telegram.org/bot${existingToken}/sendMessage`, {
110
+ method: "POST",
111
+ headers: { "Content-Type": "application/json" },
112
+ body: JSON.stringify({ chat_id: existingChat, text: "\u{1F514} REX Setup \u2014 Telegram gateway verified", parse_mode: "Markdown" })
113
+ });
114
+ if (res.ok) {
115
+ ok("Telegram gateway already configured and working");
116
+ return;
117
+ }
118
+ } catch {
119
+ }
120
+ info("Existing Telegram config found but not working \u2014 reconfiguring");
121
+ }
122
+ const botToken = await prompt("Telegram Bot Token (from @BotFather):");
123
+ if (!botToken) {
124
+ info("Skipped Telegram setup");
125
+ return;
126
+ }
127
+ try {
128
+ const res = await fetch(`https://api.telegram.org/bot${botToken}/getMe`);
129
+ const data = await res.json();
130
+ if (!data.ok) {
131
+ fail("Invalid bot token");
132
+ return;
133
+ }
134
+ ok(`Bot: @${data.result?.username}`);
135
+ } catch {
136
+ fail("Could not validate bot token");
137
+ return;
138
+ }
139
+ console.log(`
140
+ ${COLORS.dim}Send /start to your bot on Telegram, then press Enter...${COLORS.reset}`);
141
+ await prompt("Press Enter when done");
142
+ let chatId = "";
143
+ try {
144
+ const res = await fetch(`https://api.telegram.org/bot${botToken}/getUpdates`);
145
+ const data = await res.json();
146
+ const msg = data.result?.find((u) => u.message);
147
+ if (msg?.message) {
148
+ chatId = String(msg.message.chat.id);
149
+ ok(`Chat ID: ${chatId} (from @${msg.message.from?.username ?? "?"})`);
150
+ }
151
+ } catch {
152
+ }
153
+ if (!chatId) {
154
+ chatId = await prompt("Chat ID (could not auto-detect):");
155
+ }
156
+ if (!chatId) {
157
+ fail("No chat ID \u2014 Telegram setup aborted");
158
+ return;
159
+ }
160
+ settings.env.REX_TELEGRAM_BOT_TOKEN = botToken;
161
+ settings.env.REX_TELEGRAM_CHAT_ID = chatId;
162
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
163
+ ok("Telegram credentials saved to settings.json");
164
+ try {
165
+ await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
166
+ method: "POST",
167
+ headers: { "Content-Type": "application/json" },
168
+ body: JSON.stringify({ chat_id: chatId, text: "\u2705 *REX Setup Complete*\nTelegram gateway is active.", parse_mode: "Markdown" })
169
+ });
170
+ ok("Test message sent \u2014 check Telegram!");
171
+ } catch {
172
+ fail("Could not send test message");
173
+ }
174
+ }
175
+ async function setup() {
176
+ const line = "\u2550".repeat(45);
177
+ console.log(`
178
+ ${line}`);
179
+ console.log(`${COLORS.bold} REX SETUP \u2014 Full Configuration${COLORS.reset}`);
180
+ console.log(`${line}
181
+ `);
182
+ const ramGB = Math.round(totalmem() / 1024 ** 3);
183
+ const os = platform();
184
+ info(`System: ${os}, ${ramGB}GB RAM`);
185
+ if (!await isOllamaInstalled()) {
186
+ fail("Ollama not installed");
187
+ console.log(`
188
+ Install: ${COLORS.cyan}https://ollama.com/download${COLORS.reset}`);
189
+ if (os === "darwin") {
190
+ info("Opening download page...");
191
+ try {
192
+ execSync("open https://ollama.com/download", { stdio: "ignore" });
193
+ } catch {
194
+ }
195
+ }
196
+ return;
197
+ }
198
+ ok("Ollama installed");
199
+ if (!await isOllamaRunning()) {
200
+ info("Starting Ollama...");
201
+ try {
202
+ execSync("ollama serve &", { stdio: "ignore" });
203
+ await new Promise((r) => setTimeout(r, 3e3));
204
+ if (await isOllamaRunning()) {
205
+ ok("Ollama started");
206
+ } else {
207
+ fail("Could not start Ollama \u2014 start manually: ollama serve");
208
+ return;
209
+ }
210
+ } catch {
211
+ fail("Could not start Ollama \u2014 start manually: ollama serve");
212
+ return;
213
+ }
214
+ } else {
215
+ ok("Ollama running");
216
+ }
217
+ const models = await getInstalledModels();
218
+ if (models.some((m) => m.includes("nomic-embed-text"))) {
219
+ ok("nomic-embed-text already installed");
220
+ } else {
221
+ pullModel("nomic-embed-text");
222
+ }
223
+ const reasoningModel = ramGB >= 16 ? "qwen3.5:9b" : "qwen3.5:4b";
224
+ info(`Selected reasoning model: ${reasoningModel} (${ramGB}GB RAM)`);
225
+ if (models.some((m) => m.includes(reasoningModel.split(":")[0]))) {
226
+ ok(`${reasoningModel} already installed`);
227
+ } else {
228
+ pullModel(reasoningModel);
229
+ }
230
+ console.log(`
231
+ ${COLORS.dim}Testing...${COLORS.reset}`);
232
+ const embedOk = await testEmbed();
233
+ const genOk = await testGenerate(reasoningModel);
234
+ if (embedOk) ok("Embedding test passed");
235
+ else fail("Embedding test failed");
236
+ if (genOk) ok("Generation test passed");
237
+ else fail("Generation test failed");
238
+ await setupTelegram();
239
+ console.log(`
240
+ ${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
+ if (embedOk && genOk) {
242
+ console.log(`
243
+ ${COLORS.green}${COLORS.bold}Setup complete!${COLORS.reset} REX is fully configured.`);
244
+ } else {
245
+ console.log(`
246
+ ${COLORS.yellow}Setup partial.${COLORS.reset} Some tests failed \u2014 check Ollama logs.`);
247
+ }
248
+ console.log();
249
+ }
250
+ export {
251
+ setup
252
+ };
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: build-validate
3
+ description: Verify code changes compile, pass tests, and work correctly. Runs build, lint, tests, dev server checks. Reports without modifying code.
4
+ ---
5
+
6
+ # Build Validation
7
+
8
+ Verify the current project state:
9
+
10
+ 1. Run build command (`npm run build` or equivalent)
11
+ 2. Run linter if configured
12
+ 3. Run test suite if exists
13
+ 4. Start dev server, verify it loads (curl for 200)
14
+ 5. For UI changes, take a screenshot
15
+
16
+ Report each step as PASS/FAIL with details. NEVER modify code — only report.
17
+
18
+ ## Auto-Learn
19
+
20
+ If any step FAILS, call `rex_learn` MCP tool:
21
+ - category: `"lesson"`
22
+ - fact: the error message + root cause + fix applied (e.g. "Next.js build fails with X when Y — fix: Z")
23
+ - This builds a knowledge base of project-specific build issues
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: code-review
3
+ description: Thorough code review of staged/changed files. Checks logic errors, security, performance, missing states, TypeScript strictness.
4
+ ---
5
+
6
+ # Code Review
7
+
8
+ Review all changed files (`git diff`):
9
+
10
+ 1. Logic errors and edge cases
11
+ 2. Security vulnerabilities (OWASP top 10)
12
+ 3. Performance (N+1 queries, unbounded loops, missing indexes)
13
+ 4. Missing error handling, loading/empty/error states
14
+ 5. TypeScript strictness (no `any`, no `@ts-ignore` without justification)
15
+ 6. Consistency with existing codebase patterns
16
+
17
+ Rate findings: **critical** / **warning** / **suggestion**. Provide fix snippets.
18
+ Never suggest modifying tests to pass. Focus bugs > security > style.
19
+
20
+ ## Auto-Learn
21
+
22
+ After completing the review, call `rex_learn` MCP tool for each notable finding:
23
+ - category: `"pattern"` for codebase patterns, `"lesson"` for bugs/anti-patterns
24
+ - fact: concise description of the pattern found and why it matters
25
+ - Only learn findings rated **critical** or recurring patterns — skip trivial suggestions