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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/init.ts
4
- import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, readdirSync, statSync } from "fs";
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, chmodSync, 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";
@@ -43,6 +43,8 @@ function ensureDir(dir) {
43
43
  var PLIST_LABEL = "com.dstudio.rex";
44
44
  var INGEST_PLIST_LABEL = "com.dstudio.rex-ingest";
45
45
  var GATEWAY_PLIST_LABEL = "com.dstudio.rex-gateway";
46
+ var CALL_WATCH_PLIST_LABEL = "com.dstudio.rex-call-watch";
47
+ var DAEMON_PLIST_LABEL = "com.dstudio.rex-daemon";
46
48
  function installIngestAgent() {
47
49
  if (process.platform !== "darwin") {
48
50
  info("Auto-ingest only supported on macOS");
@@ -193,6 +195,74 @@ function installGatewayAgent() {
193
195
  }
194
196
  ok("Gateway LaunchAgent installed \u2014 Telegram bot always-on (auto-restart)");
195
197
  }
198
+ function installDaemonAgent() {
199
+ if (process.platform !== "darwin") {
200
+ info("Daemon LaunchAgent only supported on macOS");
201
+ return;
202
+ }
203
+ const launchAgentsDir = join(homedir(), "Library", "LaunchAgents");
204
+ ensureDir(launchAgentsDir);
205
+ const plistPath = join(launchAgentsDir, `${DAEMON_PLIST_LABEL}.plist`);
206
+ let rexBin = "";
207
+ try {
208
+ rexBin = execSync("which rex", { encoding: "utf-8" }).trim();
209
+ } catch {
210
+ }
211
+ if (!rexBin) {
212
+ info("rex binary not in PATH \u2014 skipping daemon LaunchAgent");
213
+ return;
214
+ }
215
+ if (existsSync(plistPath)) {
216
+ skip("Daemon LaunchAgent already installed (unified background daemon)");
217
+ return;
218
+ }
219
+ const settingsPath = join(homedir(), ".claude", "settings.json");
220
+ let botToken = "";
221
+ let chatIdVal = "";
222
+ try {
223
+ const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
224
+ botToken = settings.env?.REX_TELEGRAM_BOT_TOKEN || "";
225
+ chatIdVal = settings.env?.REX_TELEGRAM_CHAT_ID || "";
226
+ } catch {
227
+ }
228
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
229
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
230
+ <plist version="1.0">
231
+ <dict>
232
+ <key>Label</key>
233
+ <string>${DAEMON_PLIST_LABEL}</string>
234
+ <key>ProgramArguments</key>
235
+ <array>
236
+ <string>${rexBin}</string>
237
+ <string>daemon</string>
238
+ </array>
239
+ <key>RunAtLoad</key>
240
+ <true/>
241
+ <key>KeepAlive</key>
242
+ <true/>
243
+ <key>StandardOutPath</key>
244
+ <string>${join(homedir(), ".claude", "rex", "daemon.log")}</string>
245
+ <key>StandardErrorPath</key>
246
+ <string>${join(homedir(), ".claude", "rex", "daemon.log")}</string>
247
+ <key>EnvironmentVariables</key>
248
+ <dict>
249
+ <key>PATH</key>
250
+ <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:${dirname(rexBin)}</string>
251
+ <key>REX_TELEGRAM_BOT_TOKEN</key>
252
+ <string>${botToken}</string>
253
+ <key>REX_TELEGRAM_CHAT_ID</key>
254
+ <string>${chatIdVal}</string>
255
+ </dict>
256
+ </dict>
257
+ </plist>
258
+ `;
259
+ writeFileSync(plistPath, plist);
260
+ try {
261
+ execSync(`launchctl load ${plistPath}`, { stdio: "ignore" });
262
+ } catch {
263
+ }
264
+ ok("Daemon LaunchAgent installed \u2014 unified background daemon (KeepAlive)");
265
+ }
196
266
  function uninstallGatewayAgent() {
197
267
  if (process.platform !== "darwin") return;
198
268
  const plistPath = join(homedir(), "Library", "LaunchAgents", `${GATEWAY_PLIST_LABEL}.plist`);
@@ -210,30 +280,173 @@ function uninstallGatewayAgent() {
210
280
  }
211
281
  ok("Gateway LaunchAgent removed");
212
282
  }
283
+ function installHammerspoonCallWatcher() {
284
+ if (process.platform !== "darwin") {
285
+ info("Hammerspoon call watcher only supported on macOS");
286
+ return;
287
+ }
288
+ let hsBin = "";
289
+ try {
290
+ hsBin = execSync("which hs", { encoding: "utf-8" }).trim();
291
+ } catch {
292
+ }
293
+ if (!hsBin) {
294
+ info("Hammerspoon CLI (hs) not found \u2014 skipping call watcher install");
295
+ return;
296
+ }
297
+ const hsDir = join(homedir(), ".hammerspoon");
298
+ ensureDir(hsDir);
299
+ const thisDir = dirname(fileURLToPath(import.meta.url));
300
+ const sourceWatcher = join(thisDir, "hammerspoon", "rex-call-watcher.lua");
301
+ const targetWatcher = join(hsDir, "rex-call-watcher.lua");
302
+ if (!existsSync(sourceWatcher)) {
303
+ info("Bundled call watcher not found in rex-cli package");
304
+ return;
305
+ }
306
+ try {
307
+ copyFileSync(sourceWatcher, targetWatcher);
308
+ chmodSync(targetWatcher, 420);
309
+ ok("Hammerspoon call watcher installed");
310
+ } catch {
311
+ info("Could not install Hammerspoon call watcher");
312
+ return;
313
+ }
314
+ const initLuaPath = join(hsDir, "init.lua");
315
+ const loaderBlock = `
316
+ -- REX Call Watcher (installed by rex init)
317
+ do
318
+ local ok_rex_call, rex_call = pcall(dofile, os.getenv("HOME") .. "/.hammerspoon/rex-call-watcher.lua")
319
+ if ok_rex_call and rex_call and rex_call.start then
320
+ rex_call.start()
321
+ end
322
+ end
323
+ `;
324
+ try {
325
+ const current = existsSync(initLuaPath) ? readFileSync(initLuaPath, "utf-8") : "";
326
+ if (current.includes("rex-call-watcher.lua")) {
327
+ skip("Hammerspoon init.lua already loads REX call watcher");
328
+ } else {
329
+ const next = current.endsWith("\n") || current.length === 0 ? current + loaderBlock : `${current}
330
+ ${loaderBlock}`;
331
+ writeFileSync(initLuaPath, next);
332
+ ok("Hammerspoon init.lua patched to start call watcher");
333
+ }
334
+ } catch {
335
+ info("Could not patch ~/.hammerspoon/init.lua automatically");
336
+ }
337
+ try {
338
+ execSync(`${hsBin} -c "hs.reload()"`, { stdio: "ignore" });
339
+ ok("Hammerspoon config reloaded");
340
+ } catch {
341
+ skip("Could not auto-reload Hammerspoon config (reload manually in Hammerspoon)");
342
+ }
343
+ }
344
+ function installCallWatchAgent() {
345
+ if (process.platform !== "darwin") {
346
+ info("Call watch LaunchAgent only supported on macOS");
347
+ return;
348
+ }
349
+ const watcherPath = join(homedir(), ".hammerspoon", "rex-call-watcher.lua");
350
+ if (!existsSync(watcherPath)) {
351
+ info("Hammerspoon call watcher not installed \u2014 skipping call watch LaunchAgent");
352
+ return;
353
+ }
354
+ const launchAgentsDir = join(homedir(), "Library", "LaunchAgents");
355
+ ensureDir(launchAgentsDir);
356
+ const plistPath = join(launchAgentsDir, `${CALL_WATCH_PLIST_LABEL}.plist`);
357
+ let rexBin = "";
358
+ try {
359
+ rexBin = execSync("which rex", { encoding: "utf-8" }).trim();
360
+ } catch {
361
+ }
362
+ if (!rexBin) {
363
+ info("rex binary not in PATH \u2014 skipping call watch LaunchAgent");
364
+ return;
365
+ }
366
+ if (existsSync(plistPath)) {
367
+ skip("Call watch LaunchAgent already installed (auto audio logger)");
368
+ return;
369
+ }
370
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
371
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
372
+ <plist version="1.0">
373
+ <dict>
374
+ <key>Label</key>
375
+ <string>${CALL_WATCH_PLIST_LABEL}</string>
376
+ <key>ProgramArguments</key>
377
+ <array>
378
+ <string>${rexBin}</string>
379
+ <string>call</string>
380
+ <string>watch</string>
381
+ </array>
382
+ <key>RunAtLoad</key>
383
+ <true/>
384
+ <key>KeepAlive</key>
385
+ <true/>
386
+ <key>StandardOutPath</key>
387
+ <string>${join(homedir(), ".claude", "rex-call-watch.log")}</string>
388
+ <key>StandardErrorPath</key>
389
+ <string>${join(homedir(), ".claude", "rex-call-watch.log")}</string>
390
+ <key>EnvironmentVariables</key>
391
+ <dict>
392
+ <key>PATH</key>
393
+ <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:${dirname(rexBin)}</string>
394
+ </dict>
395
+ </dict>
396
+ </plist>
397
+ `;
398
+ writeFileSync(plistPath, plist);
399
+ try {
400
+ execSync(`launchctl load ${plistPath}`, { stdio: "ignore" });
401
+ } catch {
402
+ }
403
+ ok("Call watch LaunchAgent installed \u2014 auto audio logger on call start/end");
404
+ }
405
+ function uninstallCallWatchAgent() {
406
+ if (process.platform !== "darwin") return;
407
+ const plistPath = join(homedir(), "Library", "LaunchAgents", `${CALL_WATCH_PLIST_LABEL}.plist`);
408
+ if (!existsSync(plistPath)) return;
409
+ try {
410
+ execSync(`launchctl unload ${plistPath}`, { stdio: "ignore" });
411
+ } catch {
412
+ }
413
+ try {
414
+ unlinkSync(plistPath);
415
+ } catch {
416
+ }
417
+ ok("Call watch LaunchAgent removed");
418
+ }
213
419
  function installApp() {
214
420
  if (process.platform !== "darwin") return;
215
421
  const thisDir = new URL(".", import.meta.url).pathname;
216
- const buildApp = join(thisDir, "..", "..", "flutter_app", "build", "macos", "Build", "Products", "Release", "rex_app.app");
217
- const installedApp = "/Applications/REX.app";
422
+ const releaseBuild = join(thisDir, "..", "..", "flutter_app", "build", "macos", "Build", "Products", "Release", "rex_app.app");
423
+ const debugBuild = join(thisDir, "..", "..", "flutter_app", "build", "macos", "Build", "Products", "Debug", "rex_app.app");
424
+ const buildApp = existsSync(releaseBuild) ? releaseBuild : debugBuild;
425
+ const installedApp = "/Applications/rex_app.app";
426
+ const legacyApp = "/Applications/REX.app";
218
427
  if (existsSync(installedApp)) {
219
- skip("REX.app already in /Applications");
428
+ skip("rex_app.app already in /Applications");
220
429
  } else if (existsSync(buildApp)) {
221
430
  try {
222
- execSync(`cp -R "${buildApp}" "${installedApp}"`, { stdio: "ignore" });
223
- ok("REX.app installed to /Applications");
431
+ execSync(`ditto "${buildApp}" "${installedApp}"`, { stdio: "ignore" });
432
+ ok("rex_app.app installed to /Applications");
433
+ try {
434
+ execSync(`ln -sfn "${installedApp}" "${legacyApp}"`, { stdio: "ignore" });
435
+ } catch {
436
+ }
224
437
  } catch {
225
- info("Could not copy REX.app to /Applications (try manually)");
438
+ info("Could not copy rex_app.app to /Applications (try manually)");
226
439
  return;
227
440
  }
228
441
  } else {
229
- info("REX.app not built \u2014 run `pnpm tauri build` in packages/app first");
442
+ info("App not built \u2014 run `rex app update` (or `flutter build macos --debug`) in packages/flutter_app first");
230
443
  return;
231
444
  }
232
445
  try {
233
446
  execSync(`osascript -e 'tell application "System Events" to make login item at end with properties {path:"${installedApp}", hidden:false}'`, { stdio: "ignore" });
234
- ok("REX.app added to Login Items (auto-start on login)");
447
+ ok("rex_app.app added to Login Items (auto-start on login)");
235
448
  } catch {
236
- skip("REX.app already in Login Items");
449
+ skip("rex_app.app already in Login Items");
237
450
  }
238
451
  }
239
452
  function installStartup() {
@@ -310,6 +523,7 @@ function uninstallStartup() {
310
523
  }
311
524
  uninstallIngestAgent();
312
525
  uninstallGatewayAgent();
526
+ uninstallCallWatchAgent();
313
527
  }
314
528
  async function init() {
315
529
  const claudeDir = join(homedir(), ".claude");
@@ -351,6 +565,30 @@ ${line}`);
351
565
  } else {
352
566
  info("Memory package not found \u2014 install @rex/memory or run from monorepo");
353
567
  }
568
+ const registryPath = join(homedir(), ".rex-memory", "mcp-registry.json");
569
+ if (existsSync(registryPath)) {
570
+ try {
571
+ const registry = readJson(registryPath);
572
+ const registryServers = registry?.servers;
573
+ if (registryServers && typeof registryServers === "object") {
574
+ let added = 0;
575
+ for (const [name, cfg] of Object.entries(registryServers)) {
576
+ if (!settings.mcpServers[name]) {
577
+ settings.mcpServers[name] = cfg;
578
+ added++;
579
+ }
580
+ }
581
+ if (added > 0) {
582
+ writeJson(settingsPath, settings);
583
+ ok(`MCP registry synced (${added} server${added > 1 ? "s" : ""} added)`);
584
+ } else {
585
+ skip("MCP registry already synced");
586
+ }
587
+ }
588
+ } catch {
589
+ info("MCP registry exists but could not be parsed");
590
+ }
591
+ }
354
592
  if (!settings.hooks) settings.hooks = {};
355
593
  const hasIngestHook = settings.hooks.SessionEnd?.some?.(
356
594
  (h) => h.hooks?.some?.((hh) => hh.command?.includes("rex") && hh.command?.includes("ingest"))
@@ -371,12 +609,8 @@ ${line}`);
371
609
  const hasContextHook = settings.hooks.SessionStart?.some?.(
372
610
  (h) => h.hooks?.some?.((hh) => hh.command?.includes("rex-context"))
373
611
  );
374
- if (hasContextHook) {
375
- skip("Context injection hook (SessionStart) already configured");
376
- } else {
377
- const contextScript = join(claudeDir, "rex-context.sh");
378
- if (!existsSync(contextScript)) {
379
- writeFileSync(contextScript, `#!/bin/bash
612
+ const contextScript = join(claudeDir, "rex-context.sh");
613
+ writeFileSync(contextScript, `#!/bin/bash
380
614
  # REX Context Injection \u2014 runs at session start
381
615
  # Outputs relevant memory context to CLAUDE_ENV_FILE
382
616
 
@@ -384,20 +618,31 @@ if [ -z "$CLAUDE_ENV_FILE" ]; then
384
618
  exit 0
385
619
  fi
386
620
 
621
+ PROJECT_PATH="\${CLAUDE_PROJECT_DIR:-$PWD}"
622
+
387
623
  # Quick check if rex-memory MCP is available
388
624
  if command -v npx &>/dev/null; then
389
625
  # Context will be loaded via MCP rex_context tool
390
626
  # This hook just ensures the env is ready
391
627
  echo "REX_MEMORY_AVAILABLE=true" >> "$CLAUDE_ENV_FILE"
392
628
  fi
629
+
630
+ # Auto tool/skill recommendations (LLM-assisted)
631
+ if command -v rex &>/dev/null; then
632
+ rex agents recommend "$PROJECT_PATH" --quiet --env-file "$CLAUDE_ENV_FILE" >/dev/null 2>&1 || true
633
+ elif command -v npx &>/dev/null; then
634
+ npx rex-cli agents recommend "$PROJECT_PATH" --quiet --env-file "$CLAUDE_ENV_FILE" >/dev/null 2>&1 || true
635
+ fi
393
636
  `, { mode: 493 });
394
- }
637
+ if (hasContextHook) {
638
+ skip("Context injection hook (SessionStart) already configured");
639
+ } else {
395
640
  if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
396
641
  settings.hooks.SessionStart.push({
397
642
  hooks: [{
398
643
  type: "command",
399
644
  command: `bash ${contextScript}`,
400
- timeout: 5
645
+ timeout: 20
401
646
  }]
402
647
  });
403
648
  ok("Context injection hook configured (SessionStart)");
@@ -491,6 +736,9 @@ fi
491
736
  installStartup();
492
737
  installIngestAgent();
493
738
  installGatewayAgent();
739
+ installDaemonAgent();
740
+ installHammerspoonCallWatcher();
741
+ installCallWatchAgent();
494
742
  installApp();
495
743
  let ollamaOk = false;
496
744
  try {
@@ -551,12 +799,58 @@ fi
551
799
  }
552
800
  const memoryMonorepoDir = join(thisDir, "..", "..", "memory");
553
801
  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" });
802
+ ensureDir(memoryTargetDir);
803
+ const runtimePkgPath = join(memoryTargetDir, "package.json");
804
+ const runtimePkg = {
805
+ name: "rex-memory-runtime",
806
+ private: true,
807
+ type: "module",
808
+ dependencies: {
809
+ "better-sqlite3": "^11.8.1",
810
+ "sqlite-vec": "^0.1.6"
811
+ }
812
+ };
813
+ try {
814
+ let shouldInstall = false;
815
+ if (!existsSync(runtimePkgPath)) {
816
+ writeJson(runtimePkgPath, runtimePkg);
817
+ shouldInstall = true;
818
+ } else {
819
+ const existing = readJson(runtimePkgPath) || {};
820
+ const deps = existing.dependencies || {};
821
+ if (!deps["better-sqlite3"] || !deps["sqlite-vec"]) {
822
+ writeJson(runtimePkgPath, {
823
+ ...existing,
824
+ private: true,
825
+ type: existing.type || "module",
826
+ dependencies: {
827
+ ...deps,
828
+ "better-sqlite3": deps["better-sqlite3"] || "^11.8.1",
829
+ "sqlite-vec": deps["sqlite-vec"] || "^0.1.6"
830
+ }
831
+ });
832
+ shouldInstall = true;
833
+ }
834
+ }
835
+ if (!existsSync(join(memoryTargetDir, "node_modules", "better-sqlite3"))) {
836
+ shouldInstall = true;
837
+ }
838
+ if (shouldInstall) {
557
839
  execSync("npm install --production 2>/dev/null", { cwd: memoryTargetDir, stdio: "ignore" });
558
- ok("@rex/memory synced to ~/.rex-memory/");
559
- } catch {
840
+ ok("Memory runtime dependencies installed in ~/.rex-memory/");
841
+ } else {
842
+ skip("Memory runtime dependencies already present");
843
+ }
844
+ } catch {
845
+ if (existsSync(join(memoryMonorepoDir, "package.json")) && !existsSync(join(memoryTargetDir, "src"))) {
846
+ try {
847
+ execSync(`cp -R "${memoryMonorepoDir}/." "${memoryTargetDir}/"`, { stdio: "ignore" });
848
+ execSync("npm install --production 2>/dev/null", { cwd: memoryTargetDir, stdio: "ignore" });
849
+ ok("@rex/memory synced to ~/.rex-memory/");
850
+ } catch {
851
+ info("Could not sync @rex/memory \u2014 install manually");
852
+ }
853
+ } else {
560
854
  info("Could not sync @rex/memory \u2014 install manually");
561
855
  }
562
856
  }
@@ -577,13 +871,18 @@ ${COLORS.bold} REX initialized!${COLORS.reset}`);
577
871
  console.log(` \u2022 Run ${COLORS.cyan}rex doctor${COLORS.reset} to verify setup`);
578
872
  console.log();
579
873
  }
874
+
580
875
  export {
581
- init,
582
- installApp,
583
- installGatewayAgent,
584
876
  installIngestAgent,
585
- installStartup,
586
- uninstallGatewayAgent,
587
877
  uninstallIngestAgent,
588
- uninstallStartup
878
+ installGatewayAgent,
879
+ installDaemonAgent,
880
+ uninstallGatewayAgent,
881
+ installHammerspoonCallWatcher,
882
+ installCallWatchAgent,
883
+ uninstallCallWatchAgent,
884
+ installApp,
885
+ installStartup,
886
+ uninstallStartup,
887
+ init
589
888
  };
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ loadConfig
4
+ } from "./chunk-PPGYFMU5.js";
5
+ import {
6
+ createLogger
7
+ } from "./chunk-5ND7JYY3.js";
8
+ import {
9
+ PROJECTS_DIR,
10
+ ensureRexDirs
11
+ } from "./chunk-6SRV2I2H.js";
12
+
13
+ // src/projects.ts
14
+ import { readdirSync, readFileSync, existsSync, statSync, writeFileSync } from "fs";
15
+ import { join } from "path";
16
+ import { execSync } from "child_process";
17
+ var log = createLogger("projects");
18
+ function detectStack(projectPath) {
19
+ const stack = [];
20
+ const pkgPath = join(projectPath, "package.json");
21
+ if (existsSync(pkgPath)) {
22
+ try {
23
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
24
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
25
+ if (allDeps["next"]) stack.push("next.js");
26
+ else if (allDeps["react"]) stack.push("react");
27
+ if (allDeps["vue"]) stack.push("vue");
28
+ if (allDeps["@angular/core"]) stack.push("angular");
29
+ if (allDeps["@ionic/angular"] || allDeps["@ionic/react"]) stack.push("ionic");
30
+ if (allDeps["typescript"]) stack.push("typescript");
31
+ if (allDeps["tailwindcss"]) stack.push("tailwind");
32
+ if (allDeps["drizzle-orm"]) stack.push("drizzle");
33
+ if (allDeps["hono"]) stack.push("hono");
34
+ if (allDeps["wrangler"] || allDeps["@cloudflare/workers-types"]) stack.push("cloudflare-workers");
35
+ if (allDeps["better-sqlite3"] || allDeps["sqlite3"]) stack.push("sqlite");
36
+ if (allDeps["express"]) stack.push("express");
37
+ if (stack.length === 0) stack.push("node");
38
+ } catch {
39
+ }
40
+ }
41
+ if (existsSync(join(projectPath, "pubspec.yaml"))) {
42
+ stack.push("flutter");
43
+ try {
44
+ const pubspec = readFileSync(join(projectPath, "pubspec.yaml"), "utf-8");
45
+ if (pubspec.includes("macos_ui")) stack.push("macos");
46
+ } catch {
47
+ }
48
+ }
49
+ if (existsSync(join(projectPath, "composer.json"))) {
50
+ stack.push("php");
51
+ try {
52
+ const composer = JSON.parse(readFileSync(join(projectPath, "composer.json"), "utf-8"));
53
+ if (composer.require?.["cakephp/cakephp"]) stack.push("cakephp");
54
+ if (composer.require?.["laravel/framework"]) stack.push("laravel");
55
+ } catch {
56
+ }
57
+ }
58
+ if (existsSync(join(projectPath, "Cargo.toml"))) stack.push("rust");
59
+ if (existsSync(join(projectPath, "go.mod"))) stack.push("go");
60
+ return stack;
61
+ }
62
+ function getLastModified(projectPath) {
63
+ try {
64
+ const date = execSync("git log -1 --format=%ci 2>/dev/null", { cwd: projectPath, encoding: "utf-8" }).trim();
65
+ if (date) return date.split(" ")[0];
66
+ } catch {
67
+ }
68
+ try {
69
+ return statSync(projectPath).mtime.toISOString().split("T")[0];
70
+ } catch {
71
+ return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
72
+ }
73
+ }
74
+ function getGitRemote(projectPath) {
75
+ try {
76
+ return execSync("git remote get-url origin 2>/dev/null", { cwd: projectPath, encoding: "utf-8" }).trim() || void 0;
77
+ } catch {
78
+ return void 0;
79
+ }
80
+ }
81
+ var MANIFEST_FILES = ["package.json", "pubspec.yaml", "composer.json", "Cargo.toml", "go.mod"];
82
+ function scanProjects() {
83
+ const config = loadConfig();
84
+ const HOME = process.env.HOME || "~";
85
+ const projects = [];
86
+ for (const scanPath of config.ingest.scanPaths) {
87
+ const resolved = scanPath.replace("~", HOME);
88
+ if (!existsSync(resolved)) continue;
89
+ const entries = readdirSync(resolved, { withFileTypes: true });
90
+ for (const entry of entries) {
91
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
92
+ if (config.ingest.excludePaths.includes(entry.name)) continue;
93
+ const fullPath = join(resolved, entry.name);
94
+ const hasManifest = MANIFEST_FILES.some((f) => existsSync(join(fullPath, f)));
95
+ if (hasManifest) {
96
+ const status = entry.name.startsWith("_") ? entry.name === "_templates" ? "template" : "archived" : "active";
97
+ projects.push({
98
+ name: entry.name,
99
+ path: fullPath,
100
+ stack: detectStack(fullPath),
101
+ lastActive: getLastModified(fullPath),
102
+ status,
103
+ repo: getGitRemote(fullPath)
104
+ });
105
+ } else {
106
+ try {
107
+ const subEntries = readdirSync(fullPath, { withFileTypes: true });
108
+ for (const sub of subEntries) {
109
+ if (!sub.isDirectory() || sub.name.startsWith(".")) continue;
110
+ if (config.ingest.excludePaths.includes(sub.name)) continue;
111
+ const subPath = join(fullPath, sub.name);
112
+ if (MANIFEST_FILES.some((f) => existsSync(join(subPath, f)))) {
113
+ projects.push({
114
+ name: sub.name,
115
+ path: subPath,
116
+ stack: detectStack(subPath),
117
+ lastActive: getLastModified(subPath),
118
+ status: "active",
119
+ repo: getGitRemote(subPath)
120
+ });
121
+ }
122
+ }
123
+ } catch {
124
+ }
125
+ }
126
+ }
127
+ }
128
+ log.info(`Scanned ${projects.length} projects across ${config.ingest.scanPaths.length} paths`);
129
+ return projects.sort((a, b) => b.lastActive.localeCompare(a.lastActive));
130
+ }
131
+ function saveProjectIndex(projects) {
132
+ ensureRexDirs();
133
+ writeFileSync(join(PROJECTS_DIR, "index.json"), JSON.stringify(projects, null, 2));
134
+ }
135
+ function loadProjectIndex() {
136
+ const indexPath = join(PROJECTS_DIR, "index.json");
137
+ if (!existsSync(indexPath)) return [];
138
+ try {
139
+ return JSON.parse(readFileSync(indexPath, "utf-8"));
140
+ } catch {
141
+ return [];
142
+ }
143
+ }
144
+ function findProject(cwd) {
145
+ const projects = loadProjectIndex();
146
+ return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
147
+ }
148
+
149
+ export {
150
+ scanProjects,
151
+ saveProjectIndex,
152
+ loadProjectIndex,
153
+ findProject
154
+ };
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ export {
10
+ __require
11
+ };
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CONFIG_PATH
4
+ } from "./chunk-6SRV2I2H.js";
5
+
6
+ // src/config.ts
7
+ import { readFileSync, writeFileSync, copyFileSync, existsSync } from "fs";
8
+ var DEFAULTS = {
9
+ llm: {
10
+ embedModel: "nomic-embed-text",
11
+ classifyModel: "auto",
12
+ routing: "ollama-first",
13
+ claudeFallback: "haiku"
14
+ },
15
+ ingest: {
16
+ scanPaths: ["~/Documents/Developer/"],
17
+ excludePaths: ["node_modules", ".git", "_archive", "dist", "build"],
18
+ autoIngestInterval: 1800
19
+ },
20
+ selfImprovement: {
21
+ enabled: true,
22
+ ruleThreshold: 3,
23
+ reviewInterval: 86400
24
+ },
25
+ daemon: {
26
+ healthCheckInterval: 300,
27
+ ingestInterval: 1800,
28
+ maintenanceInterval: 3600,
29
+ selfReviewInterval: 86400
30
+ },
31
+ notifications: {
32
+ silent: ["ollama-restart", "pending-flush", "categorize-batch"],
33
+ warn: ["db-corrupt", "disk-low", "config-corrupt"],
34
+ daily: true,
35
+ weekly: true
36
+ }
37
+ };
38
+ function loadConfig() {
39
+ if (!existsSync(CONFIG_PATH)) return DEFAULTS;
40
+ try {
41
+ const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
42
+ return {
43
+ ...DEFAULTS,
44
+ ...raw,
45
+ llm: { ...DEFAULTS.llm, ...raw.llm },
46
+ ingest: { ...DEFAULTS.ingest, ...raw.ingest },
47
+ selfImprovement: { ...DEFAULTS.selfImprovement, ...raw.selfImprovement },
48
+ daemon: { ...DEFAULTS.daemon, ...raw.daemon },
49
+ notifications: { ...DEFAULTS.notifications, ...raw.notifications }
50
+ };
51
+ } catch {
52
+ const bakPath = CONFIG_PATH + ".bak";
53
+ if (existsSync(bakPath)) {
54
+ try {
55
+ const bak = JSON.parse(readFileSync(bakPath, "utf-8"));
56
+ writeFileSync(CONFIG_PATH, JSON.stringify(bak, null, 2));
57
+ return { ...DEFAULTS, ...bak };
58
+ } catch {
59
+ }
60
+ }
61
+ return DEFAULTS;
62
+ }
63
+ }
64
+
65
+ export {
66
+ loadConfig
67
+ };