cmdr-agent 2.5.5 → 3.1.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/README.md +42 -2
- package/dist/bin/cmdr.js +64 -3
- package/dist/bin/cmdr.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/src/cli/args.d.ts +11 -2
- package/dist/src/cli/args.d.ts.map +1 -1
- package/dist/src/cli/args.js +51 -9
- package/dist/src/cli/args.js.map +1 -1
- package/dist/src/cli/buddy.d.ts +49 -0
- package/dist/src/cli/buddy.d.ts.map +1 -0
- package/dist/src/cli/buddy.js +187 -0
- package/dist/src/cli/buddy.js.map +1 -0
- package/dist/src/cli/commands.js +131 -11
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/daemon.d.ts +45 -0
- package/dist/src/cli/daemon.d.ts.map +1 -0
- package/dist/src/cli/daemon.js +157 -0
- package/dist/src/cli/daemon.js.map +1 -0
- package/dist/src/cli/ink/App.d.ts.map +1 -1
- package/dist/src/cli/ink/App.js +299 -3
- package/dist/src/cli/ink/App.js.map +1 -1
- package/dist/src/cli/repl.d.ts +5 -3
- package/dist/src/cli/repl.d.ts.map +1 -1
- package/dist/src/cli/repl.js +72 -20
- package/dist/src/cli/repl.js.map +1 -1
- package/dist/src/config/api-key-store.d.ts +42 -0
- package/dist/src/config/api-key-store.d.ts.map +1 -0
- package/dist/src/config/api-key-store.js +180 -0
- package/dist/src/config/api-key-store.js.map +1 -0
- package/dist/src/config/schema.d.ts +3 -3
- package/dist/src/config/schema.js +1 -1
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/core/agent.d.ts +2 -0
- package/dist/src/core/agent.d.ts.map +1 -1
- package/dist/src/core/agent.js +13 -0
- package/dist/src/core/agent.js.map +1 -1
- package/dist/src/core/types.d.ts +19 -4
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/core/types.js +6 -0
- package/dist/src/core/types.js.map +1 -1
- package/dist/src/index.d.ts +13 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +12 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/llm/anthropic.d.ts.map +1 -1
- package/dist/src/llm/anthropic.js +11 -0
- package/dist/src/llm/anthropic.js.map +1 -1
- package/dist/src/llm/model-registry.d.ts.map +1 -1
- package/dist/src/llm/model-registry.js +5 -0
- package/dist/src/llm/model-registry.js.map +1 -1
- package/dist/src/llm/ollama.d.ts.map +1 -1
- package/dist/src/llm/ollama.js +8 -1
- package/dist/src/llm/ollama.js.map +1 -1
- package/dist/src/llm/openai.d.ts.map +1 -1
- package/dist/src/llm/openai.js +21 -6
- package/dist/src/llm/openai.js.map +1 -1
- package/dist/src/llm/provider-factory.d.ts +4 -1
- package/dist/src/llm/provider-factory.d.ts.map +1 -1
- package/dist/src/llm/provider-factory.js +23 -0
- package/dist/src/llm/provider-factory.js.map +1 -1
- package/dist/src/memory/index-manager.d.ts +65 -0
- package/dist/src/memory/index-manager.d.ts.map +1 -0
- package/dist/src/memory/index-manager.js +241 -0
- package/dist/src/memory/index-manager.js.map +1 -0
- package/dist/src/server/index.d.ts +20 -0
- package/dist/src/server/index.d.ts.map +1 -0
- package/dist/src/server/index.js +283 -0
- package/dist/src/server/index.js.map +1 -0
- package/dist/src/session/branch-manager.d.ts +39 -0
- package/dist/src/session/branch-manager.d.ts.map +1 -0
- package/dist/src/session/branch-manager.js +142 -0
- package/dist/src/session/branch-manager.js.map +1 -0
- package/dist/src/session/checkpoint-manager.d.ts +28 -0
- package/dist/src/session/checkpoint-manager.d.ts.map +1 -0
- package/dist/src/session/checkpoint-manager.js +96 -0
- package/dist/src/session/checkpoint-manager.js.map +1 -0
- package/dist/src/tools/built-in/browser.d.ts +32 -0
- package/dist/src/tools/built-in/browser.d.ts.map +1 -0
- package/dist/src/tools/built-in/browser.js +168 -0
- package/dist/src/tools/built-in/browser.js.map +1 -0
- package/dist/src/tools/built-in/git-diff.d.ts +1 -0
- package/dist/src/tools/built-in/git-diff.d.ts.map +1 -1
- package/dist/src/tools/built-in/git-diff.js +7 -2
- package/dist/src/tools/built-in/git-diff.js.map +1 -1
- package/dist/src/tools/built-in/index.d.ts +7 -1
- package/dist/src/tools/built-in/index.d.ts.map +1 -1
- package/dist/src/tools/built-in/index.js +19 -1
- package/dist/src/tools/built-in/index.js.map +1 -1
- package/dist/src/tools/built-in/rag-search.d.ts +12 -0
- package/dist/src/tools/built-in/rag-search.d.ts.map +1 -0
- package/dist/src/tools/built-in/rag-search.js +37 -0
- package/dist/src/tools/built-in/rag-search.js.map +1 -0
- package/package.json +1 -1
package/dist/src/cli/ink/App.js
CHANGED
|
@@ -15,6 +15,7 @@ import { isSlashCommand, parseSlashCommand, getCommand } from '../commands.js';
|
|
|
15
15
|
import { saveSession, loadSession, } from '../../session/session-persistence.js';
|
|
16
16
|
import { getTeamPreset } from '../../core/presets.js';
|
|
17
17
|
import { listAvailableServers, getServerDefinition, toMcpConfig, getMissingEnvVars } from '../../config/mcp-registry.js';
|
|
18
|
+
import { saveApiKey } from '../../config/api-key-store.js';
|
|
18
19
|
import StatusBar from './StatusBar.js';
|
|
19
20
|
import PromptInput from './PromptInput.js';
|
|
20
21
|
import { StreamingMarkdownRenderer } from '../renderer.js';
|
|
@@ -124,6 +125,9 @@ export default function App(props) {
|
|
|
124
125
|
const [tokensOut, setTokensOut] = useState(0);
|
|
125
126
|
const [turnCount, setTurnCount] = useState(0);
|
|
126
127
|
const planModeRef = useRef(false);
|
|
128
|
+
const pendingImageRef = useRef(null);
|
|
129
|
+
const [apikeyProvider, setApikeyProvider] = useState(null);
|
|
130
|
+
const [apikeyInput, setApikeyInput] = useState('');
|
|
127
131
|
const currentModelRef = useRef(props.model);
|
|
128
132
|
const activeTeamRef = useRef(props.activeTeamConfig);
|
|
129
133
|
const stateRef = useRef(state);
|
|
@@ -162,7 +166,7 @@ export default function App(props) {
|
|
|
162
166
|
appendLines(prefixed);
|
|
163
167
|
}, [appendLines]);
|
|
164
168
|
const terminalRows = process.stdout.rows || 42;
|
|
165
|
-
const reservedRows = state === 'idle' ? 9 : state === 'waiting_approval' ? 13 : 6;
|
|
169
|
+
const reservedRows = state === 'idle' ? 9 : state === 'waiting_approval' ? 13 : state === 'apikey_input' ? 10 : 6;
|
|
166
170
|
const historyWindowSize = Math.max(8, terminalRows - reservedRows);
|
|
167
171
|
const visibleOutputLines = useMemo(() => {
|
|
168
172
|
const end = Math.max(0, outputLines.length - historyScrollOffset);
|
|
@@ -291,6 +295,10 @@ export default function App(props) {
|
|
|
291
295
|
const contextLimit = getDefaultContextLength(currentModelRef.current);
|
|
292
296
|
if (session.tokenCount > contextLimit * 0.70) {
|
|
293
297
|
try {
|
|
298
|
+
// Auto-checkpoint before compaction
|
|
299
|
+
const { CheckpointManager } = await import('../../session/checkpoint-manager.js');
|
|
300
|
+
const cpMgr = new CheckpointManager(session.id ?? 'default');
|
|
301
|
+
await cpMgr.save('auto-pre-compact', agent.getHistory(), currentModelRef.current).catch(() => { });
|
|
294
302
|
const stats = await session.compact(adapter, currentModelRef.current);
|
|
295
303
|
agent.replaceMessages(session.messages);
|
|
296
304
|
appendOutput(` ${DIM(`◇ pre-compacted: ${stats.before} → ${stats.after} messages (saved ~${stats.tokensSaved} tokens)`)}`);
|
|
@@ -499,6 +507,10 @@ export default function App(props) {
|
|
|
499
507
|
// Auto-compact if needed
|
|
500
508
|
if (session.shouldCompact()) {
|
|
501
509
|
try {
|
|
510
|
+
// Auto-checkpoint before compaction
|
|
511
|
+
const { CheckpointManager } = await import('../../session/checkpoint-manager.js');
|
|
512
|
+
const cpMgr = new CheckpointManager(session.id ?? 'default');
|
|
513
|
+
await cpMgr.save('auto-pre-compact', agent.getHistory(), currentModelRef.current).catch(() => { });
|
|
502
514
|
const stats = await session.compact(adapter, currentModelRef.current);
|
|
503
515
|
agent.replaceMessages(session.messages);
|
|
504
516
|
appendOutput(` ${DIM(`◇ compacted: ${stats.before} messages → ${stats.after} messages (saved ~${stats.tokensSaved} tokens)`)}`);
|
|
@@ -612,6 +624,10 @@ export default function App(props) {
|
|
|
612
624
|
}
|
|
613
625
|
if (result === '__COMPACT__') {
|
|
614
626
|
session.syncFromAgent(agent.getHistory());
|
|
627
|
+
// Auto-checkpoint before compaction
|
|
628
|
+
const { CheckpointManager: CpMgr } = await import('../../session/checkpoint-manager.js');
|
|
629
|
+
const cpMgr = new CpMgr(session.id ?? 'default');
|
|
630
|
+
await cpMgr.save('auto-pre-compact', agent.getHistory(), currentModelRef.current).catch(() => { });
|
|
615
631
|
const stats = await session.compact(adapter, currentModelRef.current);
|
|
616
632
|
agent.replaceMessages(session.messages);
|
|
617
633
|
appendOutput(` ${DIM('ℹ')} ${WHITE(`${DIM(`◇ compacted: ${stats.before} messages → ${stats.after} messages (saved ~${stats.tokensSaved} tokens)`)}`)}`);
|
|
@@ -628,6 +644,241 @@ export default function App(props) {
|
|
|
628
644
|
}
|
|
629
645
|
return false;
|
|
630
646
|
}
|
|
647
|
+
// /review command — get diff and send as structured review prompt
|
|
648
|
+
if (typeof result === 'string' && result.startsWith('__REVIEW__:')) {
|
|
649
|
+
const reviewArgs = result.slice('__REVIEW__:'.length).trim();
|
|
650
|
+
const gitTool = toolRegistry.get('git_diff');
|
|
651
|
+
if (!gitTool) {
|
|
652
|
+
appendOutput(` ${ERROR_SYM} ${RED('git_diff tool not available.')}`);
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
// Parse review args: --staged, range (e.g. HEAD~3..HEAD), or path
|
|
656
|
+
let diffInput = {};
|
|
657
|
+
if (!reviewArgs) {
|
|
658
|
+
// Default: HEAD~1..HEAD
|
|
659
|
+
diffInput = { range: 'HEAD~1..HEAD' };
|
|
660
|
+
}
|
|
661
|
+
else if (reviewArgs === '--staged') {
|
|
662
|
+
diffInput = { staged: true };
|
|
663
|
+
}
|
|
664
|
+
else if (reviewArgs.includes('..')) {
|
|
665
|
+
// Commit range like HEAD~3..HEAD
|
|
666
|
+
const parts = reviewArgs.split(/\s+/);
|
|
667
|
+
diffInput = { range: parts[0] };
|
|
668
|
+
if (parts[1])
|
|
669
|
+
diffInput.path = parts[1];
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
// Path scope
|
|
673
|
+
diffInput = { range: 'HEAD~1..HEAD', path: reviewArgs };
|
|
674
|
+
}
|
|
675
|
+
const diffResult = await gitTool.execute(diffInput, {
|
|
676
|
+
agent: { name: 'cmdr', role: 'assistant', model: currentModelRef.current },
|
|
677
|
+
cwd: process.cwd(),
|
|
678
|
+
});
|
|
679
|
+
if (diffResult.isError || diffResult.data === '(no changes)') {
|
|
680
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(diffResult.data)}`);
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
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\`\`\``;
|
|
684
|
+
await handleUserMessage(reviewPrompt);
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
// /effort command — set effort level
|
|
688
|
+
if (typeof result === 'string' && result.startsWith('__EFFORT__:')) {
|
|
689
|
+
const level = result.slice('__EFFORT__:'.length);
|
|
690
|
+
const { EFFORT_CONFIGS } = await import('../../core/types.js');
|
|
691
|
+
const config = EFFORT_CONFIGS[level];
|
|
692
|
+
const cfg = agent.config;
|
|
693
|
+
cfg.thinkingEnabled = config.thinkingEnabled;
|
|
694
|
+
cfg.temperature = config.temperature;
|
|
695
|
+
const levelColors = { low: GREEN, medium: YELLOW, high: PURPLE, max: RED };
|
|
696
|
+
const colorFn = levelColors[level] || WHITE;
|
|
697
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE('Effort:')} ${colorFn(level)} ${DIM(`(think=${config.thinkingEnabled ?? 'auto'}, temp=${config.temperature})`)}`);
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
700
|
+
// /image command — set pending image for next prompt
|
|
701
|
+
if (typeof result === 'string' && result.startsWith('__IMAGE__:')) {
|
|
702
|
+
const imgPath = result.slice('__IMAGE__:'.length);
|
|
703
|
+
const { existsSync } = await import('fs');
|
|
704
|
+
const { resolve } = await import('path');
|
|
705
|
+
const resolvedPath = resolve(imgPath);
|
|
706
|
+
if (!existsSync(resolvedPath)) {
|
|
707
|
+
appendOutput(` ${ERROR_SYM} ${RED(`Image not found: ${imgPath}`)}`);
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
pendingImageRef.current = resolvedPath;
|
|
711
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(`Image attached: ${GREEN(imgPath)} — will be included in your next message`)}`);
|
|
712
|
+
}
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
// /apikey command — prompt for masked key input
|
|
716
|
+
if (typeof result === 'string' && result.startsWith('__APIKEY__:')) {
|
|
717
|
+
const provider = result.slice('__APIKEY__:'.length);
|
|
718
|
+
setApikeyProvider(provider);
|
|
719
|
+
setApikeyInput('');
|
|
720
|
+
setState('apikey_input');
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
// /checkpoint command
|
|
724
|
+
if (typeof result === 'string' && result.startsWith('__CHECKPOINT__:')) {
|
|
725
|
+
const { CheckpointManager } = await import('../../session/checkpoint-manager.js');
|
|
726
|
+
const cpManager = new CheckpointManager(session.id ?? 'default');
|
|
727
|
+
const payload = result.slice('__CHECKPOINT__:'.length);
|
|
728
|
+
if (payload === 'list') {
|
|
729
|
+
const checkpoints = await cpManager.list();
|
|
730
|
+
if (checkpoints.length === 0) {
|
|
731
|
+
appendOutput(` ${DIM('No checkpoints saved. Use /checkpoint save [label]')}`);
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
const lines = ['', ` ${PURPLE.bold('Checkpoints')}`, ''];
|
|
735
|
+
for (const cp of checkpoints) {
|
|
736
|
+
const date = new Date(cp.createdAt);
|
|
737
|
+
const timeStr = date.toLocaleTimeString();
|
|
738
|
+
lines.push(` ${GREEN('•')} ${DIM(cp.id.slice(0, 20))} ${WHITE(cp.label)} ${DIM(cp.model)} ${DIM(timeStr)} ${DIM(`${cp.messageCount} msgs`)}`);
|
|
739
|
+
}
|
|
740
|
+
lines.push('');
|
|
741
|
+
appendLines(lines);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
else if (payload.startsWith('save:')) {
|
|
745
|
+
const label = payload.slice('save:'.length);
|
|
746
|
+
session.syncFromAgent(agent.getHistory());
|
|
747
|
+
const cp = await cpManager.save(label, session.messages, currentModelRef.current);
|
|
748
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(`Checkpoint saved: ${GREEN(cp.label)} (${cp.messageCount} messages)`)}`);
|
|
749
|
+
}
|
|
750
|
+
else if (payload.startsWith('restore:')) {
|
|
751
|
+
const id = payload.slice('restore:'.length);
|
|
752
|
+
const cp = await cpManager.restore(id);
|
|
753
|
+
if (cp) {
|
|
754
|
+
agent.replaceMessages(cp.messages);
|
|
755
|
+
session.syncFromAgent(cp.messages);
|
|
756
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(`Restored checkpoint: ${GREEN(cp.label)} (${cp.messageCount} messages)`)}`);
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
appendOutput(` ${ERROR_SYM} ${RED(`Checkpoint not found: ${id}`)}`);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
else if (payload.startsWith('delete:')) {
|
|
763
|
+
const id = payload.slice('delete:'.length);
|
|
764
|
+
const deleted = await cpManager.delete(id);
|
|
765
|
+
if (deleted) {
|
|
766
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE('Checkpoint deleted.')}`);
|
|
767
|
+
}
|
|
768
|
+
else {
|
|
769
|
+
appendOutput(` ${ERROR_SYM} ${RED(`Checkpoint not found: ${id}`)}`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
// /fork, /branches, /switch, /merge commands
|
|
775
|
+
if (typeof result === 'string' && result.startsWith('__BRANCH__:')) {
|
|
776
|
+
const { BranchManager } = await import('../../session/branch-manager.js');
|
|
777
|
+
const brManager = new BranchManager(session.id ?? 'default');
|
|
778
|
+
const payload = result.slice('__BRANCH__:'.length);
|
|
779
|
+
if (payload === 'list') {
|
|
780
|
+
const branches = await brManager.list();
|
|
781
|
+
if (branches.length === 0) {
|
|
782
|
+
appendOutput(` ${DIM('No branches. Use /fork [name] to create one.')}`);
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
const lines = ['', ` ${PURPLE.bold('Branches')}`, ''];
|
|
786
|
+
for (const br of branches) {
|
|
787
|
+
const date = new Date(br.createdAt);
|
|
788
|
+
const timeStr = date.toLocaleTimeString();
|
|
789
|
+
const active = br.id === brManager.activeBranch ? ` ${GREEN('← active')}` : '';
|
|
790
|
+
lines.push(` ${GREEN('•')} ${DIM(br.id.slice(0, 20))} ${WHITE(br.name)} ${DIM(timeStr)} ${DIM(`${br.messageCount} msgs`)}${active}`);
|
|
791
|
+
}
|
|
792
|
+
lines.push('');
|
|
793
|
+
appendLines(lines);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
else if (payload.startsWith('fork:')) {
|
|
797
|
+
const name = payload.slice('fork:'.length);
|
|
798
|
+
session.syncFromAgent(agent.getHistory());
|
|
799
|
+
const br = await brManager.fork(name, session.messages, currentModelRef.current);
|
|
800
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(`Forked branch: ${GREEN(br.name)} (${br.messageCount} messages)`)}`);
|
|
801
|
+
}
|
|
802
|
+
else if (payload.startsWith('switch:')) {
|
|
803
|
+
const id = payload.slice('switch:'.length);
|
|
804
|
+
const br = await brManager.switch(id);
|
|
805
|
+
if (br) {
|
|
806
|
+
agent.replaceMessages(br.messages);
|
|
807
|
+
session.syncFromAgent(br.messages);
|
|
808
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(`Switched to branch: ${GREEN(br.name)} (${br.messageCount} messages)`)}`);
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
appendOutput(` ${ERROR_SYM} ${RED(`Branch not found: ${id}`)}`);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
else if (payload.startsWith('merge:')) {
|
|
815
|
+
const id = payload.slice('merge:'.length);
|
|
816
|
+
const merged = await brManager.merge(id, agent.getHistory());
|
|
817
|
+
if (merged) {
|
|
818
|
+
agent.replaceMessages(merged);
|
|
819
|
+
session.syncFromAgent(merged);
|
|
820
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(`Merged branch into current conversation (${merged.length} messages)`)}`);
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
appendOutput(` ${ERROR_SYM} ${RED(`Branch not found: ${id}`)}`);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
// /index and /search commands — RAG indexing
|
|
829
|
+
if (typeof result === 'string' && result.startsWith('__INDEX__:')) {
|
|
830
|
+
const { IndexManager } = await import('../../memory/index-manager.js');
|
|
831
|
+
const { setIndexManager } = await import('../../tools/built-in/rag-search.js');
|
|
832
|
+
const idxManager = new IndexManager(process.cwd(), props.ollamaUrl);
|
|
833
|
+
setIndexManager(idxManager);
|
|
834
|
+
const payload = result.slice('__INDEX__:'.length);
|
|
835
|
+
if (payload === 'status') {
|
|
836
|
+
const status = await idxManager.status();
|
|
837
|
+
appendOutput(` ${DIM('Index:')} ${WHITE(`${status.fileCount} files, ${status.chunkCount} chunks`)} ${DIM(`(model: ${status.model}, updated: ${status.updatedAt})`)}`);
|
|
838
|
+
}
|
|
839
|
+
else if (payload === 'clear') {
|
|
840
|
+
await idxManager.clear();
|
|
841
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE('Index cleared.')}`);
|
|
842
|
+
}
|
|
843
|
+
else if (payload.startsWith('search:')) {
|
|
844
|
+
const query = payload.slice('search:'.length);
|
|
845
|
+
try {
|
|
846
|
+
const results = await idxManager.search(query);
|
|
847
|
+
if (results.length === 0) {
|
|
848
|
+
appendOutput(` ${DIM('No results. Make sure files are indexed with /index <path>.')}`);
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
const lines = ['', ` ${PURPLE.bold('Search Results')}`, ''];
|
|
852
|
+
for (const r of results) {
|
|
853
|
+
lines.push(` ${GREEN('•')} ${CYAN(`${r.file}:${r.startLine}-${r.endLine}`)} ${DIM(`(score: ${r.score.toFixed(3)})`)}`);
|
|
854
|
+
const preview = r.text.split('\n').slice(0, 3).map(l => ` ${DIM(l)}`).join('\n');
|
|
855
|
+
lines.push(preview);
|
|
856
|
+
}
|
|
857
|
+
lines.push('');
|
|
858
|
+
appendLines(lines);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
catch (err) {
|
|
862
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
863
|
+
appendOutput(` ${ERROR_SYM} ${RED(`Search failed: ${msg}`)}`);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
else if (payload.startsWith('index:')) {
|
|
867
|
+
const path = payload.slice('index:'.length);
|
|
868
|
+
appendOutput(` ${DIM('Indexing')} ${WHITE(path)}${DIM('...')}`);
|
|
869
|
+
try {
|
|
870
|
+
const count = await idxManager.index([path], (msg) => {
|
|
871
|
+
appendOutput(` ${DIM(msg)}`);
|
|
872
|
+
});
|
|
873
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE(`Indexed ${GREEN(String(count))} new chunks`)}`);
|
|
874
|
+
}
|
|
875
|
+
catch (err) {
|
|
876
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
877
|
+
appendOutput(` ${ERROR_SYM} ${RED(`Indexing failed: ${msg}`)}`);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
631
882
|
if (result === '__SESSION_SAVE__') {
|
|
632
883
|
session.syncFromAgent(agent.getHistory());
|
|
633
884
|
if (session.messages.length > 0) {
|
|
@@ -972,7 +1223,23 @@ export default function App(props) {
|
|
|
972
1223
|
await handleUserMessage(planMsg);
|
|
973
1224
|
}
|
|
974
1225
|
else {
|
|
975
|
-
|
|
1226
|
+
// If an image is pending, attach it to this message
|
|
1227
|
+
if (pendingImageRef.current) {
|
|
1228
|
+
const { readFile: readFs } = await import('fs/promises');
|
|
1229
|
+
const { extname } = await import('path');
|
|
1230
|
+
const ext = extname(pendingImageRef.current).toLowerCase();
|
|
1231
|
+
const mimeMap = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp' };
|
|
1232
|
+
const mediaType = mimeMap[ext] || 'image/png';
|
|
1233
|
+
const imageData = await readFs(pendingImageRef.current);
|
|
1234
|
+
const base64 = imageData.toString('base64');
|
|
1235
|
+
agent.addImageMessage(input, base64, mediaType);
|
|
1236
|
+
pendingImageRef.current = null;
|
|
1237
|
+
// Stream with empty message since we already added the user message with image
|
|
1238
|
+
await handleUserMessage('');
|
|
1239
|
+
}
|
|
1240
|
+
else {
|
|
1241
|
+
await handleUserMessage(input);
|
|
1242
|
+
}
|
|
976
1243
|
}
|
|
977
1244
|
}
|
|
978
1245
|
catch (err) {
|
|
@@ -1029,6 +1296,12 @@ export default function App(props) {
|
|
|
1029
1296
|
}
|
|
1030
1297
|
appendOutput(`\n ${DIM('Interrupt — press Ctrl+C again to exit.')}`);
|
|
1031
1298
|
}
|
|
1299
|
+
else if (stateRef.current === 'apikey_input') {
|
|
1300
|
+
setApikeyInput('');
|
|
1301
|
+
setApikeyProvider(null);
|
|
1302
|
+
setState('idle');
|
|
1303
|
+
appendOutput(` ${DIM('API key setup cancelled.')}`);
|
|
1304
|
+
}
|
|
1032
1305
|
else if (stateRef.current === 'idle') {
|
|
1033
1306
|
appendOutput(` ${DIM('Press Ctrl+C again to exit.')}`);
|
|
1034
1307
|
}
|
|
@@ -1040,6 +1313,29 @@ export default function App(props) {
|
|
|
1040
1313
|
}
|
|
1041
1314
|
});
|
|
1042
1315
|
// ---------------------------------------------------------------------------
|
|
1316
|
+
// Handle API key input
|
|
1317
|
+
// ---------------------------------------------------------------------------
|
|
1318
|
+
const handleApikeySubmit = useCallback(async (value) => {
|
|
1319
|
+
const key = value.trim();
|
|
1320
|
+
const provider = apikeyProvider;
|
|
1321
|
+
setApikeyInput('');
|
|
1322
|
+
setApikeyProvider(null);
|
|
1323
|
+
setState('idle');
|
|
1324
|
+
if (!key || !provider) {
|
|
1325
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE('API key setup cancelled.')}`);
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
try {
|
|
1329
|
+
const result = await saveApiKey(provider, key);
|
|
1330
|
+
appendOutput(` ${SUCCESS_SYM} ${WHITE(`${GREEN(provider)} API key saved`)} ${DIM(`→ ${result.envVar} in ~/${result.profilePath.split('/').pop()}`)}`);
|
|
1331
|
+
appendOutput(` ${DIM('ℹ')} ${WHITE('Key is active for this session. New terminals will pick it up automatically.')}`);
|
|
1332
|
+
}
|
|
1333
|
+
catch (err) {
|
|
1334
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1335
|
+
appendOutput(` ${ERROR_SYM} ${RED(`Failed to save API key: ${msg}`)}`);
|
|
1336
|
+
}
|
|
1337
|
+
}, [apikeyProvider, appendOutput]);
|
|
1338
|
+
// ---------------------------------------------------------------------------
|
|
1043
1339
|
// Handle approval input
|
|
1044
1340
|
// ---------------------------------------------------------------------------
|
|
1045
1341
|
const handleApprovalSubmit = useCallback((value) => {
|
|
@@ -1078,6 +1374,6 @@ export default function App(props) {
|
|
|
1078
1374
|
? val.length > 120 ? val.slice(0, 120) + DIM('...') : val
|
|
1079
1375
|
: JSON.stringify(val).slice(0, 120);
|
|
1080
1376
|
return _jsx(Text, { children: ` ${DIM(key + ':')} ${WHITE(display)}` }, key);
|
|
1081
|
-
}), _jsx(Text, { children: '' }), _jsx(Text, { children: ` ${GREEN('y')}${DIM('es')} ${DIM('/')} ${RED('n')}${DIM('o')} ${DIM('/')} ${PURPLE('a')}${DIM('lways allow this tool')}` }), _jsxs(Box, { children: [_jsx(Text, { children: ` ${YELLOW('?')} ` }), _jsx(TextInput, { value: approvalInput, onChange: setApprovalInput, onSubmit: handleApprovalSubmit })] })] })), _jsx(StatusBar, { model: currentModelRef.current, tokensIn: tokensIn, tokensOut: tokensOut, turns: turnCount, agentCount: agentRegistry.list().length, permissionMode: permissionManager.getMode(), cwd: process.cwd(), gitBranch: props.gitBranch, contextPct: session.maxContextTokens ? Math.round((session.tokenCount / session.maxContextTokens) * 100) : 0, version: props.version }), state === 'idle' && (_jsx(PromptInput, { onSubmit: handleSubmit, placeholder: "Ask anything, @file to include, /help for commands" }))] }));
|
|
1377
|
+
}), _jsx(Text, { children: '' }), _jsx(Text, { children: ` ${GREEN('y')}${DIM('es')} ${DIM('/')} ${RED('n')}${DIM('o')} ${DIM('/')} ${PURPLE('a')}${DIM('lways allow this tool')}` }), _jsxs(Box, { children: [_jsx(Text, { children: ` ${YELLOW('?')} ` }), _jsx(TextInput, { value: approvalInput, onChange: setApprovalInput, onSubmit: handleApprovalSubmit })] })] })), state === 'apikey_input' && apikeyProvider && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: '' }), _jsx(Text, { children: ` ${PURPLE('🔑')} ${WHITE('Enter API key for')} ${CYAN(apikeyProvider)}` }), _jsx(Text, { children: ` ${DIM('Your key will be saved to your shell profile and set for this session.')}` }), _jsx(Text, { children: ` ${DIM('Press Ctrl+C to cancel.')}` }), _jsx(Text, { children: '' }), _jsxs(Box, { children: [_jsx(Text, { children: ` ${YELLOW('⟩')} ` }), _jsx(TextInput, { value: apikeyInput, onChange: setApikeyInput, onSubmit: handleApikeySubmit, mask: "\u2022" })] })] })), _jsx(StatusBar, { model: currentModelRef.current, tokensIn: tokensIn, tokensOut: tokensOut, turns: turnCount, agentCount: agentRegistry.list().length, permissionMode: permissionManager.getMode(), cwd: process.cwd(), gitBranch: props.gitBranch, contextPct: session.maxContextTokens ? Math.round((session.tokenCount / session.maxContextTokens) * 100) : 0, version: props.version }), state === 'idle' && (_jsx(PromptInput, { onSubmit: handleSubmit, placeholder: "Ask anything, @file to include, /help for commands" }))] }));
|
|
1082
1378
|
}
|
|
1083
1379
|
//# sourceMappingURL=App.js.map
|