cmdr-agent 2.5.4 → 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 (100) hide show
  1. package/README.md +122 -205
  2. package/dist/bin/cmdr.js +64 -3
  3. package/dist/bin/cmdr.js.map +1 -1
  4. package/dist/package.json +1 -1
  5. package/dist/src/agents/executor.d.ts +1 -1
  6. package/dist/src/agents/executor.d.ts.map +1 -1
  7. package/dist/src/agents/executor.js +2 -2
  8. package/dist/src/agents/executor.js.map +1 -1
  9. package/dist/src/agents/subagent-tool.d.ts.map +1 -1
  10. package/dist/src/agents/subagent-tool.js +1 -1
  11. package/dist/src/agents/subagent-tool.js.map +1 -1
  12. package/dist/src/cli/args.d.ts +11 -2
  13. package/dist/src/cli/args.d.ts.map +1 -1
  14. package/dist/src/cli/args.js +49 -8
  15. package/dist/src/cli/args.js.map +1 -1
  16. package/dist/src/cli/buddy.d.ts +49 -0
  17. package/dist/src/cli/buddy.d.ts.map +1 -0
  18. package/dist/src/cli/buddy.js +187 -0
  19. package/dist/src/cli/buddy.js.map +1 -0
  20. package/dist/src/cli/commands.js +105 -11
  21. package/dist/src/cli/commands.js.map +1 -1
  22. package/dist/src/cli/daemon.d.ts +45 -0
  23. package/dist/src/cli/daemon.d.ts.map +1 -0
  24. package/dist/src/cli/daemon.js +157 -0
  25. package/dist/src/cli/daemon.js.map +1 -0
  26. package/dist/src/cli/ink/App.d.ts.map +1 -1
  27. package/dist/src/cli/ink/App.js +257 -1
  28. package/dist/src/cli/ink/App.js.map +1 -1
  29. package/dist/src/cli/repl.d.ts +5 -3
  30. package/dist/src/cli/repl.d.ts.map +1 -1
  31. package/dist/src/cli/repl.js +87 -26
  32. package/dist/src/cli/repl.js.map +1 -1
  33. package/dist/src/config/schema.d.ts +113 -12
  34. package/dist/src/config/schema.d.ts.map +1 -1
  35. package/dist/src/config/schema.js +26 -2
  36. package/dist/src/config/schema.js.map +1 -1
  37. package/dist/src/core/agent-runner.d.ts.map +1 -1
  38. package/dist/src/core/agent-runner.js +42 -3
  39. package/dist/src/core/agent-runner.js.map +1 -1
  40. package/dist/src/core/agent.d.ts +2 -0
  41. package/dist/src/core/agent.d.ts.map +1 -1
  42. package/dist/src/core/agent.js +13 -0
  43. package/dist/src/core/agent.js.map +1 -1
  44. package/dist/src/core/types.d.ts +16 -1
  45. package/dist/src/core/types.d.ts.map +1 -1
  46. package/dist/src/core/types.js +6 -0
  47. package/dist/src/core/types.js.map +1 -1
  48. package/dist/src/index.d.ts +13 -1
  49. package/dist/src/index.d.ts.map +1 -1
  50. package/dist/src/index.js +12 -0
  51. package/dist/src/index.js.map +1 -1
  52. package/dist/src/llm/anthropic.d.ts.map +1 -1
  53. package/dist/src/llm/anthropic.js +11 -0
  54. package/dist/src/llm/anthropic.js.map +1 -1
  55. package/dist/src/llm/ollama.d.ts.map +1 -1
  56. package/dist/src/llm/ollama.js +8 -1
  57. package/dist/src/llm/ollama.js.map +1 -1
  58. package/dist/src/llm/openai.d.ts.map +1 -1
  59. package/dist/src/llm/openai.js +21 -6
  60. package/dist/src/llm/openai.js.map +1 -1
  61. package/dist/src/memory/index-manager.d.ts +65 -0
  62. package/dist/src/memory/index-manager.d.ts.map +1 -0
  63. package/dist/src/memory/index-manager.js +241 -0
  64. package/dist/src/memory/index-manager.js.map +1 -0
  65. package/dist/src/plugins/plugin-manager.d.ts +2 -1
  66. package/dist/src/plugins/plugin-manager.d.ts.map +1 -1
  67. package/dist/src/plugins/plugin-manager.js +13 -2
  68. package/dist/src/plugins/plugin-manager.js.map +1 -1
  69. package/dist/src/server/index.d.ts +20 -0
  70. package/dist/src/server/index.d.ts.map +1 -0
  71. package/dist/src/server/index.js +281 -0
  72. package/dist/src/server/index.js.map +1 -0
  73. package/dist/src/session/branch-manager.d.ts +39 -0
  74. package/dist/src/session/branch-manager.d.ts.map +1 -0
  75. package/dist/src/session/branch-manager.js +142 -0
  76. package/dist/src/session/branch-manager.js.map +1 -0
  77. package/dist/src/session/checkpoint-manager.d.ts +28 -0
  78. package/dist/src/session/checkpoint-manager.d.ts.map +1 -0
  79. package/dist/src/session/checkpoint-manager.js +96 -0
  80. package/dist/src/session/checkpoint-manager.js.map +1 -0
  81. package/dist/src/tools/built-in/browser.d.ts +32 -0
  82. package/dist/src/tools/built-in/browser.d.ts.map +1 -0
  83. package/dist/src/tools/built-in/browser.js +168 -0
  84. package/dist/src/tools/built-in/browser.js.map +1 -0
  85. package/dist/src/tools/built-in/git-diff.d.ts +1 -0
  86. package/dist/src/tools/built-in/git-diff.d.ts.map +1 -1
  87. package/dist/src/tools/built-in/git-diff.js +7 -2
  88. package/dist/src/tools/built-in/git-diff.js.map +1 -1
  89. package/dist/src/tools/built-in/index.d.ts +7 -1
  90. package/dist/src/tools/built-in/index.d.ts.map +1 -1
  91. package/dist/src/tools/built-in/index.js +19 -1
  92. package/dist/src/tools/built-in/index.js.map +1 -1
  93. package/dist/src/tools/built-in/rag-search.d.ts +12 -0
  94. package/dist/src/tools/built-in/rag-search.d.ts.map +1 -0
  95. package/dist/src/tools/built-in/rag-search.js +37 -0
  96. package/dist/src/tools/built-in/rag-search.js.map +1 -0
  97. package/dist/src/tools/executor.d.ts.map +1 -1
  98. package/dist/src/tools/executor.js +55 -2
  99. package/dist/src/tools/executor.js.map +1 -1
  100. package/package.json +1 -1
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Daemon mode — background file watcher + on-change command execution.
3
+ *
4
+ * cmdr daemon start --watch src/ --on-change "npm run lint --fix"
5
+ * cmdr daemon status
6
+ * cmdr daemon stop
7
+ *
8
+ * Uses Node.js fs.watch (recursive) and spawns on-change commands.
9
+ */
10
+ import { watch } from 'node:fs';
11
+ import { spawn } from 'node:child_process';
12
+ import { join, resolve } from 'node:path';
13
+ import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises';
14
+ import { homedir } from 'node:os';
15
+ const PID_DIR = join(homedir(), '.cmdr', 'daemon');
16
+ function pidFilePath(cwd) {
17
+ // Use a hash of cwd for unique pid file
18
+ const { createHash } = require('node:crypto');
19
+ const hash = createHash('sha256').update(cwd).digest('hex').slice(0, 12);
20
+ return join(PID_DIR, `${hash}.json`);
21
+ }
22
+ export class CmdrDaemon {
23
+ config;
24
+ watchers = [];
25
+ debounceTimer = null;
26
+ running = false;
27
+ constructor(config) {
28
+ this.config = config;
29
+ }
30
+ /** Start watching and executing on change. */
31
+ async start() {
32
+ if (this.running)
33
+ return;
34
+ const debounceMs = this.config.debounceMs ?? 500;
35
+ for (const watchPath of this.config.watchPaths) {
36
+ const fullPath = resolve(this.config.cwd, watchPath);
37
+ try {
38
+ const watcher = watch(fullPath, { recursive: true }, (_eventType, filename) => {
39
+ if (!filename)
40
+ return;
41
+ // Skip hidden files and node_modules
42
+ if (filename.startsWith('.') || filename.includes('node_modules'))
43
+ return;
44
+ // Debounce rapid changes
45
+ if (this.debounceTimer)
46
+ clearTimeout(this.debounceTimer);
47
+ this.debounceTimer = setTimeout(() => {
48
+ this.executeOnChange(filename);
49
+ }, debounceMs);
50
+ });
51
+ this.watchers.push(watcher);
52
+ }
53
+ catch (err) {
54
+ console.error(`Cannot watch ${fullPath}: ${err instanceof Error ? err.message : String(err)}`);
55
+ }
56
+ }
57
+ this.running = true;
58
+ await this.writePidFile();
59
+ console.log(`Daemon started (PID ${process.pid})`);
60
+ console.log(` Watching: ${this.config.watchPaths.join(', ')}`);
61
+ console.log(` On change: ${this.config.onChange}`);
62
+ // Keep process alive
63
+ const keepAlive = setInterval(() => { }, 60_000);
64
+ // Graceful shutdown
65
+ const shutdown = async () => {
66
+ clearInterval(keepAlive);
67
+ await this.stop();
68
+ process.exit(0);
69
+ };
70
+ process.on('SIGINT', shutdown);
71
+ process.on('SIGTERM', shutdown);
72
+ }
73
+ /** Execute the on-change command. */
74
+ executeOnChange(filename) {
75
+ console.log(`[daemon] Change detected: ${filename}`);
76
+ console.log(`[daemon] Running: ${this.config.onChange}`);
77
+ const child = spawn('sh', ['-c', this.config.onChange], {
78
+ cwd: this.config.cwd,
79
+ stdio: 'inherit',
80
+ env: { ...process.env, CMDR_CHANGED_FILE: filename },
81
+ });
82
+ child.on('exit', (code) => {
83
+ if (code === 0) {
84
+ console.log(`[daemon] Command completed successfully`);
85
+ }
86
+ else {
87
+ console.log(`[daemon] Command exited with code ${code}`);
88
+ }
89
+ });
90
+ }
91
+ /** Stop watching. */
92
+ async stop() {
93
+ if (this.debounceTimer)
94
+ clearTimeout(this.debounceTimer);
95
+ for (const watcher of this.watchers) {
96
+ watcher.close();
97
+ }
98
+ this.watchers = [];
99
+ this.running = false;
100
+ await this.removePidFile();
101
+ console.log('Daemon stopped.');
102
+ }
103
+ /** Write PID file for status/stop from another process. */
104
+ async writePidFile() {
105
+ await mkdir(PID_DIR, { recursive: true });
106
+ const data = {
107
+ pid: process.pid,
108
+ cwd: this.config.cwd,
109
+ watchPaths: this.config.watchPaths,
110
+ onChange: this.config.onChange,
111
+ startedAt: new Date().toISOString(),
112
+ };
113
+ await writeFile(pidFilePath(this.config.cwd), JSON.stringify(data, null, 2));
114
+ }
115
+ /** Remove PID file on shutdown. */
116
+ async removePidFile() {
117
+ try {
118
+ await unlink(pidFilePath(this.config.cwd));
119
+ }
120
+ catch { /* already gone */ }
121
+ }
122
+ /** Read daemon status for a given cwd. */
123
+ static async status(cwd) {
124
+ try {
125
+ const raw = await readFile(pidFilePath(cwd), 'utf-8');
126
+ const data = JSON.parse(raw);
127
+ // Check if process is actually running
128
+ try {
129
+ process.kill(data.pid, 0);
130
+ return data;
131
+ }
132
+ catch {
133
+ // Process no longer running, clean up stale PID file
134
+ await unlink(pidFilePath(cwd)).catch(() => { });
135
+ return null;
136
+ }
137
+ }
138
+ catch {
139
+ return null;
140
+ }
141
+ }
142
+ /** Stop a running daemon for a given cwd. */
143
+ static async stopByPid(cwd) {
144
+ const info = await CmdrDaemon.status(cwd);
145
+ if (!info)
146
+ return false;
147
+ try {
148
+ process.kill(info.pid, 'SIGTERM');
149
+ await unlink(pidFilePath(cwd)).catch(() => { });
150
+ return true;
151
+ }
152
+ catch {
153
+ return false;
154
+ }
155
+ }
156
+ }
157
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../../src/cli/daemon.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink/App.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAA;AAGhF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAClE,OAAO,KAAK,EAAE,UAAU,EAAkE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACjI,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAI9D,OAAO,EACsD,cAAc,EAC1E,MAAM,sCAAsC,CAAA;AAE7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAA;AAqEvE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,KAAK,CAAA;IACZ,OAAO,EAAE,cAAc,CAAA;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,OAAO,EAAE,UAAU,CAAA;IACnB,YAAY,EAAE,YAAY,CAAA;IAC1B,gBAAgB,CAAC,EAAE,UAAU,CAAA;IAC7B,WAAW,EAAE,WAAW,CAAA;IACxB,WAAW,EAAE,WAAW,CAAA;IACxB,aAAa,EAAE,aAAa,CAAA;IAC5B,SAAS,EAAE,SAAS,CAAA;IACpB,YAAY,EAAE,YAAY,CAAA;IAC1B,aAAa,EAAE,aAAa,CAAA;IAC5B,aAAa,EAAE,aAAa,CAAA;IAC5B,aAAa,EAAE,aAAa,CAAA;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,SAAS,EAAE,cAAc,CAAA;IACzB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAoED,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,CAAC,YAAY,CA8jClE"}
1
+ {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink/App.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAA;AAGhF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAClE,OAAO,KAAK,EAAE,UAAU,EAAkE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACjI,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAI9D,OAAO,EACsD,cAAc,EAC1E,MAAM,sCAAsC,CAAA;AAE7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAA;AAqEvE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,KAAK,CAAA;IACZ,OAAO,EAAE,cAAc,CAAA;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,OAAO,EAAE,UAAU,CAAA;IACnB,YAAY,EAAE,YAAY,CAAA;IAC1B,gBAAgB,CAAC,EAAE,UAAU,CAAA;IAC7B,WAAW,EAAE,WAAW,CAAA;IACxB,WAAW,EAAE,WAAW,CAAA;IACxB,aAAa,EAAE,aAAa,CAAA;IAC5B,SAAS,EAAE,SAAS,CAAA;IACpB,YAAY,EAAE,YAAY,CAAA;IAC1B,aAAa,EAAE,aAAa,CAAA;IAC5B,aAAa,EAAE,aAAa,CAAA;IAC5B,aAAa,EAAE,aAAa,CAAA;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,SAAS,EAAE,cAAc,CAAA;IACzB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAoED,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,CAAC,YAAY,CAozClE"}
@@ -124,6 +124,7 @@ export default function App(props) {
124
124
  const [tokensOut, setTokensOut] = useState(0);
125
125
  const [turnCount, setTurnCount] = useState(0);
126
126
  const planModeRef = useRef(false);
127
+ const pendingImageRef = useRef(null);
127
128
  const currentModelRef = useRef(props.model);
128
129
  const activeTeamRef = useRef(props.activeTeamConfig);
129
130
  const stateRef = useRef(state);
@@ -291,6 +292,10 @@ export default function App(props) {
291
292
  const contextLimit = getDefaultContextLength(currentModelRef.current);
292
293
  if (session.tokenCount > contextLimit * 0.70) {
293
294
  try {
295
+ // Auto-checkpoint before compaction
296
+ const { CheckpointManager } = await import('../../session/checkpoint-manager.js');
297
+ const cpMgr = new CheckpointManager(session.id ?? 'default');
298
+ await cpMgr.save('auto-pre-compact', agent.getHistory(), currentModelRef.current).catch(() => { });
294
299
  const stats = await session.compact(adapter, currentModelRef.current);
295
300
  agent.replaceMessages(session.messages);
296
301
  appendOutput(` ${DIM(`◇ pre-compacted: ${stats.before} → ${stats.after} messages (saved ~${stats.tokensSaved} tokens)`)}`);
@@ -499,6 +504,10 @@ export default function App(props) {
499
504
  // Auto-compact if needed
500
505
  if (session.shouldCompact()) {
501
506
  try {
507
+ // Auto-checkpoint before compaction
508
+ const { CheckpointManager } = await import('../../session/checkpoint-manager.js');
509
+ const cpMgr = new CheckpointManager(session.id ?? 'default');
510
+ await cpMgr.save('auto-pre-compact', agent.getHistory(), currentModelRef.current).catch(() => { });
502
511
  const stats = await session.compact(adapter, currentModelRef.current);
503
512
  agent.replaceMessages(session.messages);
504
513
  appendOutput(` ${DIM(`◇ compacted: ${stats.before} messages → ${stats.after} messages (saved ~${stats.tokensSaved} tokens)`)}`);
@@ -612,6 +621,10 @@ export default function App(props) {
612
621
  }
613
622
  if (result === '__COMPACT__') {
614
623
  session.syncFromAgent(agent.getHistory());
624
+ // Auto-checkpoint before compaction
625
+ const { CheckpointManager: CpMgr } = await import('../../session/checkpoint-manager.js');
626
+ const cpMgr = new CpMgr(session.id ?? 'default');
627
+ await cpMgr.save('auto-pre-compact', agent.getHistory(), currentModelRef.current).catch(() => { });
615
628
  const stats = await session.compact(adapter, currentModelRef.current);
616
629
  agent.replaceMessages(session.messages);
617
630
  appendOutput(` ${DIM('ℹ')} ${WHITE(`${DIM(`◇ compacted: ${stats.before} messages → ${stats.after} messages (saved ~${stats.tokensSaved} tokens)`)}`)}`);
@@ -628,6 +641,233 @@ export default function App(props) {
628
641
  }
629
642
  return false;
630
643
  }
644
+ // /review command — get diff and send as structured review prompt
645
+ if (typeof result === 'string' && result.startsWith('__REVIEW__:')) {
646
+ const reviewArgs = result.slice('__REVIEW__:'.length).trim();
647
+ const gitTool = toolRegistry.get('git_diff');
648
+ if (!gitTool) {
649
+ appendOutput(` ${ERROR_SYM} ${RED('git_diff tool not available.')}`);
650
+ return false;
651
+ }
652
+ // Parse review args: --staged, range (e.g. HEAD~3..HEAD), or path
653
+ let diffInput = {};
654
+ if (!reviewArgs) {
655
+ // Default: HEAD~1..HEAD
656
+ diffInput = { range: 'HEAD~1..HEAD' };
657
+ }
658
+ else if (reviewArgs === '--staged') {
659
+ diffInput = { staged: true };
660
+ }
661
+ else if (reviewArgs.includes('..')) {
662
+ // Commit range like HEAD~3..HEAD
663
+ const parts = reviewArgs.split(/\s+/);
664
+ diffInput = { range: parts[0] };
665
+ if (parts[1])
666
+ diffInput.path = parts[1];
667
+ }
668
+ else {
669
+ // Path scope
670
+ diffInput = { range: 'HEAD~1..HEAD', path: reviewArgs };
671
+ }
672
+ const diffResult = await gitTool.execute(diffInput, {
673
+ agent: { name: 'cmdr', role: 'assistant', model: currentModelRef.current },
674
+ cwd: process.cwd(),
675
+ });
676
+ if (diffResult.isError || diffResult.data === '(no changes)') {
677
+ appendOutput(` ${DIM('ℹ')} ${WHITE(diffResult.data)}`);
678
+ return false;
679
+ }
680
+ const reviewPrompt = `Please review the following git diff. Analyze for:\n1. **Logic errors** — incorrect behavior, off-by-one, wrong conditions\n2. **Security** — injection, exposure, auth gaps\n3. **Performance** — unnecessary work, missing caching, O(n²) patterns\n4. **Style** — naming, consistency, readability\n5. **Error handling** — missing catches, swallowed errors, unclear messages\n\nBe specific. Reference line numbers and file names. Suggest fixes where applicable.\n\n\`\`\`diff\n${diffResult.data}\n\`\`\``;
681
+ await handleUserMessage(reviewPrompt);
682
+ return false;
683
+ }
684
+ // /effort command — set effort level
685
+ if (typeof result === 'string' && result.startsWith('__EFFORT__:')) {
686
+ const level = result.slice('__EFFORT__:'.length);
687
+ const { EFFORT_CONFIGS } = await import('../../core/types.js');
688
+ const config = EFFORT_CONFIGS[level];
689
+ const cfg = agent.config;
690
+ cfg.thinkingEnabled = config.thinkingEnabled;
691
+ cfg.temperature = config.temperature;
692
+ const levelColors = { low: GREEN, medium: YELLOW, high: PURPLE, max: RED };
693
+ const colorFn = levelColors[level] || WHITE;
694
+ appendOutput(` ${DIM('ℹ')} ${WHITE('Effort:')} ${colorFn(level)} ${DIM(`(think=${config.thinkingEnabled ?? 'auto'}, temp=${config.temperature})`)}`);
695
+ return false;
696
+ }
697
+ // /image command — set pending image for next prompt
698
+ if (typeof result === 'string' && result.startsWith('__IMAGE__:')) {
699
+ const imgPath = result.slice('__IMAGE__:'.length);
700
+ const { existsSync } = await import('fs');
701
+ const { resolve } = await import('path');
702
+ const resolvedPath = resolve(imgPath);
703
+ if (!existsSync(resolvedPath)) {
704
+ appendOutput(` ${ERROR_SYM} ${RED(`Image not found: ${imgPath}`)}`);
705
+ }
706
+ else {
707
+ pendingImageRef.current = resolvedPath;
708
+ appendOutput(` ${DIM('ℹ')} ${WHITE(`Image attached: ${GREEN(imgPath)} — will be included in your next message`)}`);
709
+ }
710
+ return false;
711
+ }
712
+ // /checkpoint command
713
+ if (typeof result === 'string' && result.startsWith('__CHECKPOINT__:')) {
714
+ const { CheckpointManager } = await import('../../session/checkpoint-manager.js');
715
+ const cpManager = new CheckpointManager(session.id ?? 'default');
716
+ const payload = result.slice('__CHECKPOINT__:'.length);
717
+ if (payload === 'list') {
718
+ const checkpoints = await cpManager.list();
719
+ if (checkpoints.length === 0) {
720
+ appendOutput(` ${DIM('No checkpoints saved. Use /checkpoint save [label]')}`);
721
+ }
722
+ else {
723
+ const lines = ['', ` ${PURPLE.bold('Checkpoints')}`, ''];
724
+ for (const cp of checkpoints) {
725
+ const date = new Date(cp.createdAt);
726
+ const timeStr = date.toLocaleTimeString();
727
+ lines.push(` ${GREEN('•')} ${DIM(cp.id.slice(0, 20))} ${WHITE(cp.label)} ${DIM(cp.model)} ${DIM(timeStr)} ${DIM(`${cp.messageCount} msgs`)}`);
728
+ }
729
+ lines.push('');
730
+ appendLines(lines);
731
+ }
732
+ }
733
+ else if (payload.startsWith('save:')) {
734
+ const label = payload.slice('save:'.length);
735
+ session.syncFromAgent(agent.getHistory());
736
+ const cp = await cpManager.save(label, session.messages, currentModelRef.current);
737
+ appendOutput(` ${DIM('ℹ')} ${WHITE(`Checkpoint saved: ${GREEN(cp.label)} (${cp.messageCount} messages)`)}`);
738
+ }
739
+ else if (payload.startsWith('restore:')) {
740
+ const id = payload.slice('restore:'.length);
741
+ const cp = await cpManager.restore(id);
742
+ if (cp) {
743
+ agent.replaceMessages(cp.messages);
744
+ session.syncFromAgent(cp.messages);
745
+ appendOutput(` ${DIM('ℹ')} ${WHITE(`Restored checkpoint: ${GREEN(cp.label)} (${cp.messageCount} messages)`)}`);
746
+ }
747
+ else {
748
+ appendOutput(` ${ERROR_SYM} ${RED(`Checkpoint not found: ${id}`)}`);
749
+ }
750
+ }
751
+ else if (payload.startsWith('delete:')) {
752
+ const id = payload.slice('delete:'.length);
753
+ const deleted = await cpManager.delete(id);
754
+ if (deleted) {
755
+ appendOutput(` ${DIM('ℹ')} ${WHITE('Checkpoint deleted.')}`);
756
+ }
757
+ else {
758
+ appendOutput(` ${ERROR_SYM} ${RED(`Checkpoint not found: ${id}`)}`);
759
+ }
760
+ }
761
+ return false;
762
+ }
763
+ // /fork, /branches, /switch, /merge commands
764
+ if (typeof result === 'string' && result.startsWith('__BRANCH__:')) {
765
+ const { BranchManager } = await import('../../session/branch-manager.js');
766
+ const brManager = new BranchManager(session.id ?? 'default');
767
+ const payload = result.slice('__BRANCH__:'.length);
768
+ if (payload === 'list') {
769
+ const branches = await brManager.list();
770
+ if (branches.length === 0) {
771
+ appendOutput(` ${DIM('No branches. Use /fork [name] to create one.')}`);
772
+ }
773
+ else {
774
+ const lines = ['', ` ${PURPLE.bold('Branches')}`, ''];
775
+ for (const br of branches) {
776
+ const date = new Date(br.createdAt);
777
+ const timeStr = date.toLocaleTimeString();
778
+ const active = br.id === brManager.activeBranch ? ` ${GREEN('← active')}` : '';
779
+ lines.push(` ${GREEN('•')} ${DIM(br.id.slice(0, 20))} ${WHITE(br.name)} ${DIM(timeStr)} ${DIM(`${br.messageCount} msgs`)}${active}`);
780
+ }
781
+ lines.push('');
782
+ appendLines(lines);
783
+ }
784
+ }
785
+ else if (payload.startsWith('fork:')) {
786
+ const name = payload.slice('fork:'.length);
787
+ session.syncFromAgent(agent.getHistory());
788
+ const br = await brManager.fork(name, session.messages, currentModelRef.current);
789
+ appendOutput(` ${DIM('ℹ')} ${WHITE(`Forked branch: ${GREEN(br.name)} (${br.messageCount} messages)`)}`);
790
+ }
791
+ else if (payload.startsWith('switch:')) {
792
+ const id = payload.slice('switch:'.length);
793
+ const br = await brManager.switch(id);
794
+ if (br) {
795
+ agent.replaceMessages(br.messages);
796
+ session.syncFromAgent(br.messages);
797
+ appendOutput(` ${DIM('ℹ')} ${WHITE(`Switched to branch: ${GREEN(br.name)} (${br.messageCount} messages)`)}`);
798
+ }
799
+ else {
800
+ appendOutput(` ${ERROR_SYM} ${RED(`Branch not found: ${id}`)}`);
801
+ }
802
+ }
803
+ else if (payload.startsWith('merge:')) {
804
+ const id = payload.slice('merge:'.length);
805
+ const merged = await brManager.merge(id, agent.getHistory());
806
+ if (merged) {
807
+ agent.replaceMessages(merged);
808
+ session.syncFromAgent(merged);
809
+ appendOutput(` ${DIM('ℹ')} ${WHITE(`Merged branch into current conversation (${merged.length} messages)`)}`);
810
+ }
811
+ else {
812
+ appendOutput(` ${ERROR_SYM} ${RED(`Branch not found: ${id}`)}`);
813
+ }
814
+ }
815
+ return false;
816
+ }
817
+ // /index and /search commands — RAG indexing
818
+ if (typeof result === 'string' && result.startsWith('__INDEX__:')) {
819
+ const { IndexManager } = await import('../../memory/index-manager.js');
820
+ const { setIndexManager } = await import('../../tools/built-in/rag-search.js');
821
+ const idxManager = new IndexManager(process.cwd(), props.ollamaUrl);
822
+ setIndexManager(idxManager);
823
+ const payload = result.slice('__INDEX__:'.length);
824
+ if (payload === 'status') {
825
+ const status = await idxManager.status();
826
+ appendOutput(` ${DIM('Index:')} ${WHITE(`${status.fileCount} files, ${status.chunkCount} chunks`)} ${DIM(`(model: ${status.model}, updated: ${status.updatedAt})`)}`);
827
+ }
828
+ else if (payload === 'clear') {
829
+ await idxManager.clear();
830
+ appendOutput(` ${DIM('ℹ')} ${WHITE('Index cleared.')}`);
831
+ }
832
+ else if (payload.startsWith('search:')) {
833
+ const query = payload.slice('search:'.length);
834
+ try {
835
+ const results = await idxManager.search(query);
836
+ if (results.length === 0) {
837
+ appendOutput(` ${DIM('No results. Make sure files are indexed with /index <path>.')}`);
838
+ }
839
+ else {
840
+ const lines = ['', ` ${PURPLE.bold('Search Results')}`, ''];
841
+ for (const r of results) {
842
+ lines.push(` ${GREEN('•')} ${CYAN(`${r.file}:${r.startLine}-${r.endLine}`)} ${DIM(`(score: ${r.score.toFixed(3)})`)}`);
843
+ const preview = r.text.split('\n').slice(0, 3).map(l => ` ${DIM(l)}`).join('\n');
844
+ lines.push(preview);
845
+ }
846
+ lines.push('');
847
+ appendLines(lines);
848
+ }
849
+ }
850
+ catch (err) {
851
+ const msg = err instanceof Error ? err.message : String(err);
852
+ appendOutput(` ${ERROR_SYM} ${RED(`Search failed: ${msg}`)}`);
853
+ }
854
+ }
855
+ else if (payload.startsWith('index:')) {
856
+ const path = payload.slice('index:'.length);
857
+ appendOutput(` ${DIM('Indexing')} ${WHITE(path)}${DIM('...')}`);
858
+ try {
859
+ const count = await idxManager.index([path], (msg) => {
860
+ appendOutput(` ${DIM(msg)}`);
861
+ });
862
+ appendOutput(` ${DIM('ℹ')} ${WHITE(`Indexed ${GREEN(String(count))} new chunks`)}`);
863
+ }
864
+ catch (err) {
865
+ const msg = err instanceof Error ? err.message : String(err);
866
+ appendOutput(` ${ERROR_SYM} ${RED(`Indexing failed: ${msg}`)}`);
867
+ }
868
+ }
869
+ return false;
870
+ }
631
871
  if (result === '__SESSION_SAVE__') {
632
872
  session.syncFromAgent(agent.getHistory());
633
873
  if (session.messages.length > 0) {
@@ -972,7 +1212,23 @@ export default function App(props) {
972
1212
  await handleUserMessage(planMsg);
973
1213
  }
974
1214
  else {
975
- await handleUserMessage(input);
1215
+ // If an image is pending, attach it to this message
1216
+ if (pendingImageRef.current) {
1217
+ const { readFile: readFs } = await import('fs/promises');
1218
+ const { extname } = await import('path');
1219
+ const ext = extname(pendingImageRef.current).toLowerCase();
1220
+ const mimeMap = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp' };
1221
+ const mediaType = mimeMap[ext] || 'image/png';
1222
+ const imageData = await readFs(pendingImageRef.current);
1223
+ const base64 = imageData.toString('base64');
1224
+ agent.addImageMessage(input, base64, mediaType);
1225
+ pendingImageRef.current = null;
1226
+ // Stream with empty message since we already added the user message with image
1227
+ await handleUserMessage('');
1228
+ }
1229
+ else {
1230
+ await handleUserMessage(input);
1231
+ }
976
1232
  }
977
1233
  }
978
1234
  catch (err) {