codemaxxing 1.0.0 → 1.0.2
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 +18 -12
- package/dist/agent.d.ts +4 -0
- package/dist/agent.js +91 -16
- package/dist/commands/git.d.ts +2 -0
- package/dist/commands/git.js +50 -0
- package/dist/commands/ollama.d.ts +27 -0
- package/dist/commands/ollama.js +171 -0
- package/dist/commands/output.d.ts +2 -0
- package/dist/commands/output.js +18 -0
- package/dist/commands/registry.d.ts +2 -0
- package/dist/commands/registry.js +8 -0
- package/dist/commands/skills.d.ts +18 -0
- package/dist/commands/skills.js +121 -0
- package/dist/commands/types.d.ts +5 -0
- package/dist/commands/types.js +1 -0
- package/dist/commands/ui.d.ts +16 -0
- package/dist/commands/ui.js +79 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +13 -3
- package/dist/exec.js +4 -1
- package/dist/index.js +75 -401
- package/dist/tools/files.js +58 -3
- package/dist/utils/context.js +6 -0
- package/dist/utils/mcp.d.ts +7 -2
- package/dist/utils/mcp.js +34 -6
- package/package.json +8 -5
- package/src/agent.ts +0 -894
- package/src/auth-cli.ts +0 -287
- package/src/cli.ts +0 -37
- package/src/config.ts +0 -352
- package/src/exec.ts +0 -183
- package/src/index.tsx +0 -2647
- package/src/skills/registry.ts +0 -1436
- package/src/themes.ts +0 -335
- package/src/tools/files.ts +0 -374
- package/src/utils/auth.ts +0 -606
- package/src/utils/context.ts +0 -174
- package/src/utils/git.ts +0 -117
- package/src/utils/hardware.ts +0 -131
- package/src/utils/lint.ts +0 -116
- package/src/utils/mcp.ts +0 -307
- package/src/utils/models.ts +0 -218
- package/src/utils/ollama.ts +0 -352
- package/src/utils/repomap.ts +0 -220
- package/src/utils/sessions.ts +0 -254
- package/src/utils/skills.ts +0 -241
- package/tsconfig.json +0 -16
package/dist/index.js
CHANGED
|
@@ -7,15 +7,19 @@ import TextInput from "ink-text-input";
|
|
|
7
7
|
import { CodingAgent } from "./agent.js";
|
|
8
8
|
import { loadConfig, saveConfig, detectLocalProvider, detectLocalProviderDetailed, parseCLIArgs, applyOverrides, listModels } from "./config.js";
|
|
9
9
|
import { listSessions, getSession, loadMessages, deleteSession } from "./utils/sessions.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { isGitRepo, getBranch, getStatus } from "./utils/git.js";
|
|
11
|
+
import { tryHandleGitCommand } from "./commands/git.js";
|
|
12
|
+
import { tryHandleOllamaCommand } from "./commands/ollama.js";
|
|
13
|
+
import { dispatchRegisteredCommands } from "./commands/registry.js";
|
|
12
14
|
import { getTheme, listThemes, THEMES, DEFAULT_THEME } from "./themes.js";
|
|
15
|
+
import { tryHandleUiCommand } from "./commands/ui.js";
|
|
13
16
|
import { PROVIDERS, getCredentials, openRouterOAuth, anthropicSetupToken, importCodexToken, importQwenToken, copilotDeviceFlow } from "./utils/auth.js";
|
|
14
|
-
import { listInstalledSkills, installSkill, removeSkill, getRegistrySkills,
|
|
17
|
+
import { listInstalledSkills, installSkill, removeSkill, getRegistrySkills, getActiveSkills, getActiveSkillCount } from "./utils/skills.js";
|
|
15
18
|
import { listServers, addServer, removeServer, getConnectedServers } from "./utils/mcp.js";
|
|
19
|
+
import { tryHandleSkillsCommand } from "./commands/skills.js";
|
|
16
20
|
import { detectHardware, formatBytes } from "./utils/hardware.js";
|
|
17
21
|
import { getRecommendationsWithLlmfit, getFitIcon, isLlmfitAvailable } from "./utils/models.js";
|
|
18
|
-
import { isOllamaInstalled, isOllamaRunning, getOllamaInstallCommand, startOllama, stopOllama, pullModel, listInstalledModelsDetailed, deleteModel
|
|
22
|
+
import { isOllamaInstalled, isOllamaRunning, getOllamaInstallCommand, startOllama, stopOllama, pullModel, listInstalledModelsDetailed, deleteModel } from "./utils/ollama.js";
|
|
19
23
|
import { createRequire } from "module";
|
|
20
24
|
const _require = createRequire(import.meta.url);
|
|
21
25
|
const VERSION = _require("../package.json").version;
|
|
@@ -54,12 +58,6 @@ const SLASH_COMMANDS = [
|
|
|
54
58
|
{ cmd: "/session delete", desc: "delete a session" },
|
|
55
59
|
{ cmd: "/resume", desc: "resume a past session" },
|
|
56
60
|
{ cmd: "/skills", desc: "manage skill packs" },
|
|
57
|
-
{ cmd: "/skills install", desc: "install a skill" },
|
|
58
|
-
{ cmd: "/skills remove", desc: "remove a skill" },
|
|
59
|
-
{ cmd: "/skills list", desc: "show installed skills" },
|
|
60
|
-
{ cmd: "/skills search", desc: "search registry" },
|
|
61
|
-
{ cmd: "/skills on", desc: "enable skill for session" },
|
|
62
|
-
{ cmd: "/skills off", desc: "disable skill for session" },
|
|
63
61
|
{ cmd: "/architect", desc: "toggle architect mode" },
|
|
64
62
|
{ cmd: "/lint", desc: "show auto-lint status" },
|
|
65
63
|
{ cmd: "/lint on", desc: "enable auto-lint" },
|
|
@@ -70,6 +68,7 @@ const SLASH_COMMANDS = [
|
|
|
70
68
|
{ cmd: "/mcp remove", desc: "remove MCP server" },
|
|
71
69
|
{ cmd: "/mcp reconnect", desc: "reconnect MCP servers" },
|
|
72
70
|
{ cmd: "/ollama", desc: "Ollama status & models" },
|
|
71
|
+
{ cmd: "/ollama status", desc: "show Ollama status & models" },
|
|
73
72
|
{ cmd: "/ollama list", desc: "list installed models" },
|
|
74
73
|
{ cmd: "/ollama start", desc: "start Ollama server" },
|
|
75
74
|
{ cmd: "/ollama stop", desc: "stop Ollama server" },
|
|
@@ -407,8 +406,7 @@ function App() {
|
|
|
407
406
|
if (selected) {
|
|
408
407
|
// Commands that need args (like /commit, /model) — fill input instead of executing
|
|
409
408
|
if (selected.cmd === "/commit" || selected.cmd === "/model" || selected.cmd === "/session delete" ||
|
|
410
|
-
selected.cmd === "/
|
|
411
|
-
selected.cmd === "/skills on" || selected.cmd === "/skills off" || selected.cmd === "/architect") {
|
|
409
|
+
selected.cmd === "/architect") {
|
|
412
410
|
setInput(selected.cmd + " ");
|
|
413
411
|
setCmdIndex(0);
|
|
414
412
|
setInputKey((k) => k + 1);
|
|
@@ -502,346 +500,42 @@ function App() {
|
|
|
502
500
|
].join("\n"));
|
|
503
501
|
return;
|
|
504
502
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
const query = trimmed.replace("/skills search ", "").trim();
|
|
542
|
-
const results = searchRegistry(query);
|
|
543
|
-
if (results.length === 0) {
|
|
544
|
-
addMsg("info", `No skills found matching "${query}".`);
|
|
545
|
-
}
|
|
546
|
-
else {
|
|
547
|
-
const installed = listInstalledSkills().map((s) => s.name);
|
|
548
|
-
const lines = results.map((s) => {
|
|
549
|
-
const mark = installed.includes(s.name) ? " ✓" : "";
|
|
550
|
-
return ` ${s.name} — ${s.description}${mark}`;
|
|
551
|
-
});
|
|
552
|
-
addMsg("info", `Registry matches:\n${lines.join("\n")}`);
|
|
553
|
-
}
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
|
-
if (trimmed.startsWith("/skills create ")) {
|
|
557
|
-
const name = trimmed.replace("/skills create ", "").trim();
|
|
558
|
-
if (!name) {
|
|
559
|
-
addMsg("info", "Usage: /skills create <name>");
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
const result = createSkillScaffold(name);
|
|
563
|
-
addMsg(result.ok ? "info" : "error", result.ok ? `✅ ${result.message}\n Edit: ${result.path}/prompt.md` : `✗ ${result.message}`);
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
if (trimmed.startsWith("/skills on ")) {
|
|
567
|
-
const name = trimmed.replace("/skills on ", "").trim();
|
|
568
|
-
const installed = listInstalledSkills().map((s) => s.name);
|
|
569
|
-
if (!installed.includes(name)) {
|
|
570
|
-
addMsg("error", `Skill "${name}" is not installed.`);
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
setSessionDisabledSkills((prev) => { const next = new Set(prev); next.delete(name); return next; });
|
|
574
|
-
if (agent)
|
|
575
|
-
agent.enableSkill(name);
|
|
576
|
-
addMsg("info", `✅ Enabled skill: ${name}`);
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
if (trimmed.startsWith("/skills off ")) {
|
|
580
|
-
const name = trimmed.replace("/skills off ", "").trim();
|
|
581
|
-
const installed = listInstalledSkills().map((s) => s.name);
|
|
582
|
-
if (!installed.includes(name)) {
|
|
583
|
-
addMsg("error", `Skill "${name}" is not installed.`);
|
|
584
|
-
return;
|
|
585
|
-
}
|
|
586
|
-
setSessionDisabledSkills((prev) => { const next = new Set(prev); next.add(name); return next; });
|
|
587
|
-
if (agent)
|
|
588
|
-
agent.disableSkill(name);
|
|
589
|
-
addMsg("info", `✅ Disabled skill: ${name} (session only)`);
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
if (trimmed.startsWith("/theme")) {
|
|
593
|
-
const themeName = trimmed.replace("/theme", "").trim();
|
|
594
|
-
if (!themeName) {
|
|
595
|
-
const themeKeys = listThemes();
|
|
596
|
-
const currentIdx = themeKeys.indexOf(theme.name.toLowerCase());
|
|
597
|
-
setThemePicker(true);
|
|
598
|
-
setThemePickerIndex(currentIdx >= 0 ? currentIdx : 0);
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
if (!THEMES[themeName]) {
|
|
602
|
-
addMsg("error", `Theme "${themeName}" not found. Use /theme to see available themes.`);
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
|
-
setTheme(getTheme(themeName));
|
|
606
|
-
addMsg("info", `✅ Switched to theme: ${THEMES[themeName].name}`);
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
// ── Architect commands (work without agent) ──
|
|
610
|
-
if (trimmed === "/architect") {
|
|
611
|
-
if (!agent) {
|
|
612
|
-
addMsg("info", "🏗️ Architect mode: no agent connected. Connect first with /login or /connect.");
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
const current = agent.getArchitectModel();
|
|
616
|
-
if (current) {
|
|
617
|
-
agent.setArchitectModel(null);
|
|
618
|
-
addMsg("info", "🏗️ Architect mode OFF");
|
|
619
|
-
}
|
|
620
|
-
else {
|
|
621
|
-
// Use config default or a sensible default
|
|
622
|
-
const defaultModel = loadConfig().defaults.architectModel || agent.getModel();
|
|
623
|
-
agent.setArchitectModel(defaultModel);
|
|
624
|
-
addMsg("info", `🏗️ Architect mode ON (planner: ${defaultModel})`);
|
|
625
|
-
}
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
if (trimmed.startsWith("/architect ")) {
|
|
629
|
-
const model = trimmed.replace("/architect ", "").trim();
|
|
630
|
-
if (!model) {
|
|
631
|
-
addMsg("info", "Usage: /architect <model> or /architect to toggle");
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
if (agent) {
|
|
635
|
-
agent.setArchitectModel(model);
|
|
636
|
-
addMsg("info", `🏗️ Architect mode ON (planner: ${model})`);
|
|
637
|
-
}
|
|
638
|
-
else {
|
|
639
|
-
addMsg("info", "⚠ No agent connected. Connect first.");
|
|
640
|
-
}
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
// ── Lint commands (work without agent) ──
|
|
644
|
-
if (trimmed === "/lint") {
|
|
645
|
-
const { detectLinter } = await import("./utils/lint.js");
|
|
646
|
-
const linter = detectLinter(process.cwd());
|
|
647
|
-
const enabled = agent ? agent.isAutoLintEnabled() : true;
|
|
648
|
-
if (linter) {
|
|
649
|
-
addMsg("info", `🔍 Auto-lint: ${enabled ? "ON" : "OFF"}\n Detected: ${linter.name}\n Command: ${linter.command} <file>`);
|
|
650
|
-
}
|
|
651
|
-
else {
|
|
652
|
-
addMsg("info", `🔍 Auto-lint: ${enabled ? "ON" : "OFF"}\n No linter detected in this project.`);
|
|
653
|
-
}
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
if (trimmed === "/lint on") {
|
|
657
|
-
if (agent)
|
|
658
|
-
agent.setAutoLint(true);
|
|
659
|
-
addMsg("info", "🔍 Auto-lint ON");
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
if (trimmed === "/lint off") {
|
|
663
|
-
if (agent)
|
|
664
|
-
agent.setAutoLint(false);
|
|
665
|
-
addMsg("info", "🔍 Auto-lint OFF");
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
668
|
-
// ── Ollama commands (work without agent) ──
|
|
669
|
-
if (trimmed === "/ollama" || trimmed === "/ollama status") {
|
|
670
|
-
const running = await isOllamaRunning();
|
|
671
|
-
const lines = [`Ollama: ${running ? "running" : "stopped"}`];
|
|
672
|
-
if (running) {
|
|
673
|
-
const models = await listInstalledModelsDetailed();
|
|
674
|
-
if (models.length > 0) {
|
|
675
|
-
lines.push(`Installed models (${models.length}):`);
|
|
676
|
-
for (const m of models) {
|
|
677
|
-
const sizeGB = (m.size / (1024 * 1024 * 1024)).toFixed(1);
|
|
678
|
-
lines.push(` ${m.name} (${sizeGB} GB)`);
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
else {
|
|
682
|
-
lines.push("No models installed.");
|
|
683
|
-
}
|
|
684
|
-
const gpuMem = getGPUMemoryUsage();
|
|
685
|
-
if (gpuMem)
|
|
686
|
-
lines.push(`GPU: ${gpuMem}`);
|
|
687
|
-
}
|
|
688
|
-
else {
|
|
689
|
-
lines.push("Start with: /ollama start");
|
|
690
|
-
}
|
|
691
|
-
addMsg("info", lines.join("\n"));
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
if (trimmed === "/ollama list") {
|
|
695
|
-
const running = await isOllamaRunning();
|
|
696
|
-
if (!running) {
|
|
697
|
-
addMsg("info", "Ollama is not running. Start with /ollama start");
|
|
698
|
-
return;
|
|
699
|
-
}
|
|
700
|
-
const models = await listInstalledModelsDetailed();
|
|
701
|
-
if (models.length === 0) {
|
|
702
|
-
addMsg("info", "No models installed. Pull one with /ollama pull <model>");
|
|
703
|
-
}
|
|
704
|
-
else {
|
|
705
|
-
const lines = models.map((m) => {
|
|
706
|
-
const sizeGB = (m.size / (1024 * 1024 * 1024)).toFixed(1);
|
|
707
|
-
return ` ${m.name} (${sizeGB} GB)`;
|
|
708
|
-
});
|
|
709
|
-
addMsg("info", `Installed models:\n${lines.join("\n")}`);
|
|
710
|
-
}
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
if (trimmed === "/ollama start") {
|
|
714
|
-
const running = await isOllamaRunning();
|
|
715
|
-
if (running) {
|
|
716
|
-
addMsg("info", "Ollama is already running.");
|
|
717
|
-
return;
|
|
718
|
-
}
|
|
719
|
-
if (!isOllamaInstalled()) {
|
|
720
|
-
addMsg("error", `Ollama is not installed. Install with: ${getOllamaInstallCommand(process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : "linux")}`);
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
startOllama();
|
|
724
|
-
addMsg("info", "Starting Ollama server...");
|
|
725
|
-
// Wait for it to come up
|
|
726
|
-
for (let i = 0; i < 10; i++) {
|
|
727
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
728
|
-
if (await isOllamaRunning()) {
|
|
729
|
-
addMsg("info", "Ollama is running.");
|
|
730
|
-
await refreshConnectionBanner();
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
addMsg("error", "Ollama did not start in time. Try running 'ollama serve' manually.");
|
|
735
|
-
return;
|
|
736
|
-
}
|
|
737
|
-
if (trimmed === "/ollama stop") {
|
|
738
|
-
const running = await isOllamaRunning();
|
|
739
|
-
if (!running) {
|
|
740
|
-
addMsg("info", "Ollama is not running.");
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
addMsg("info", "Stopping Ollama...");
|
|
744
|
-
const result = await stopOllama();
|
|
745
|
-
addMsg(result.ok ? "info" : "error", result.ok ? `\u2705 ${result.message}` : `\u274C ${result.message}`);
|
|
746
|
-
if (result.ok)
|
|
747
|
-
await refreshConnectionBanner();
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
750
|
-
if (trimmed === "/ollama pull") {
|
|
751
|
-
// No model specified — show picker
|
|
752
|
-
setOllamaPullPicker(true);
|
|
753
|
-
setOllamaPullPickerIndex(0);
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
if (trimmed.startsWith("/ollama pull ")) {
|
|
757
|
-
const modelId = trimmed.replace("/ollama pull ", "").trim();
|
|
758
|
-
if (!modelId) {
|
|
759
|
-
setOllamaPullPicker(true);
|
|
760
|
-
setOllamaPullPickerIndex(0);
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
if (!isOllamaInstalled()) {
|
|
764
|
-
addMsg("error", "Ollama is not installed.");
|
|
765
|
-
return;
|
|
766
|
-
}
|
|
767
|
-
// Ensure ollama is running
|
|
768
|
-
let running = await isOllamaRunning();
|
|
769
|
-
if (!running) {
|
|
770
|
-
startOllama();
|
|
771
|
-
addMsg("info", "Starting Ollama server...");
|
|
772
|
-
for (let i = 0; i < 10; i++) {
|
|
773
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
774
|
-
if (await isOllamaRunning()) {
|
|
775
|
-
running = true;
|
|
776
|
-
break;
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
if (!running) {
|
|
780
|
-
addMsg("error", "Could not start Ollama. Run 'ollama serve' manually.");
|
|
781
|
-
return;
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
setOllamaPulling({ model: modelId, progress: { status: "starting", percent: 0 } });
|
|
785
|
-
try {
|
|
786
|
-
await pullModel(modelId, (p) => {
|
|
787
|
-
setOllamaPulling({ model: modelId, progress: p });
|
|
788
|
-
});
|
|
789
|
-
setOllamaPulling(null);
|
|
790
|
-
addMsg("info", `\u2705 Downloaded ${modelId}`);
|
|
791
|
-
}
|
|
792
|
-
catch (err) {
|
|
793
|
-
setOllamaPulling(null);
|
|
794
|
-
addMsg("error", `Failed to pull ${modelId}: ${err.message}`);
|
|
795
|
-
}
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
if (trimmed === "/ollama delete") {
|
|
799
|
-
// Ensure Ollama is running so we can list models
|
|
800
|
-
let running = await isOllamaRunning();
|
|
801
|
-
if (!running) {
|
|
802
|
-
addMsg("info", "Starting Ollama to list models...");
|
|
803
|
-
startOllama();
|
|
804
|
-
for (let i = 0; i < 10; i++) {
|
|
805
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
806
|
-
if (await isOllamaRunning()) {
|
|
807
|
-
running = true;
|
|
808
|
-
break;
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
if (!running) {
|
|
812
|
-
addMsg("error", "Could not start Ollama. Start it manually first.");
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
const models = await listInstalledModelsDetailed();
|
|
817
|
-
if (models.length === 0) {
|
|
818
|
-
addMsg("info", "No models installed.");
|
|
819
|
-
return;
|
|
820
|
-
}
|
|
821
|
-
setOllamaDeletePicker({ models: models.map(m => ({ name: m.name, size: m.size })) });
|
|
822
|
-
setOllamaDeletePickerIndex(0);
|
|
823
|
-
return;
|
|
824
|
-
}
|
|
825
|
-
if (trimmed.startsWith("/ollama delete ")) {
|
|
826
|
-
const modelId = trimmed.replace("/ollama delete ", "").trim();
|
|
827
|
-
if (!modelId) {
|
|
828
|
-
const models = await listInstalledModelsDetailed();
|
|
829
|
-
if (models.length === 0) {
|
|
830
|
-
addMsg("info", "No models installed.");
|
|
831
|
-
return;
|
|
832
|
-
}
|
|
833
|
-
setOllamaDeletePicker({ models: models.map(m => ({ name: m.name, size: m.size })) });
|
|
834
|
-
setOllamaDeletePickerIndex(0);
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
// Look up size for confirmation
|
|
838
|
-
const models = await listInstalledModelsDetailed();
|
|
839
|
-
const found = models.find((m) => m.name === modelId || m.name.startsWith(modelId));
|
|
840
|
-
if (!found) {
|
|
841
|
-
addMsg("error", `Model "${modelId}" not found. Use /ollama list to see installed models.`);
|
|
842
|
-
return;
|
|
843
|
-
}
|
|
844
|
-
setOllamaDeleteConfirm({ model: found.name, size: found.size });
|
|
503
|
+
if (await dispatchRegisteredCommands([
|
|
504
|
+
() => tryHandleSkillsCommand({
|
|
505
|
+
trimmed,
|
|
506
|
+
cwd: process.cwd(),
|
|
507
|
+
addMsg,
|
|
508
|
+
agent,
|
|
509
|
+
sessionDisabledSkills,
|
|
510
|
+
setSkillsPicker,
|
|
511
|
+
setSkillsPickerIndex,
|
|
512
|
+
setSessionDisabledSkills,
|
|
513
|
+
setInput,
|
|
514
|
+
setInputKey,
|
|
515
|
+
}),
|
|
516
|
+
() => tryHandleOllamaCommand({
|
|
517
|
+
trimmed,
|
|
518
|
+
addMsg,
|
|
519
|
+
refreshConnectionBanner,
|
|
520
|
+
setOllamaPullPicker,
|
|
521
|
+
setOllamaPullPickerIndex,
|
|
522
|
+
setOllamaPulling,
|
|
523
|
+
setOllamaDeletePicker,
|
|
524
|
+
setOllamaDeletePickerIndex,
|
|
525
|
+
setOllamaDeleteConfirm,
|
|
526
|
+
}),
|
|
527
|
+
() => tryHandleGitCommand(trimmed, process.cwd(), addMsg),
|
|
528
|
+
() => tryHandleUiCommand({
|
|
529
|
+
trimmed,
|
|
530
|
+
cwd: process.cwd(),
|
|
531
|
+
addMsg,
|
|
532
|
+
agent,
|
|
533
|
+
theme,
|
|
534
|
+
setTheme,
|
|
535
|
+
setThemePicker,
|
|
536
|
+
setThemePickerIndex,
|
|
537
|
+
}),
|
|
538
|
+
], { trimmed })) {
|
|
845
539
|
return;
|
|
846
540
|
}
|
|
847
541
|
// ── MCP commands (partially work without agent) ──
|
|
@@ -992,16 +686,6 @@ function App() {
|
|
|
992
686
|
}
|
|
993
687
|
return;
|
|
994
688
|
}
|
|
995
|
-
if (trimmed === "/diff") {
|
|
996
|
-
const diff = getDiff(process.cwd());
|
|
997
|
-
addMsg("info", diff);
|
|
998
|
-
return;
|
|
999
|
-
}
|
|
1000
|
-
if (trimmed === "/undo") {
|
|
1001
|
-
const result = undoLastCommit(process.cwd());
|
|
1002
|
-
addMsg("info", result.success ? `✅ ${result.message}` : `✗ ${result.message}`);
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
689
|
if (trimmed === "/git on") {
|
|
1006
690
|
if (!agent.isGitEnabled()) {
|
|
1007
691
|
addMsg("info", "✗ Not a git repository");
|
|
@@ -1096,32 +780,6 @@ function App() {
|
|
|
1096
780
|
setSessionPickerIndex(0);
|
|
1097
781
|
return;
|
|
1098
782
|
}
|
|
1099
|
-
if (trimmed === "/push") {
|
|
1100
|
-
try {
|
|
1101
|
-
const output = execSync("git push", { cwd: process.cwd(), encoding: "utf-8", stdio: "pipe" });
|
|
1102
|
-
addMsg("info", `✅ Pushed to remote${output.trim() ? "\n" + output.trim() : ""}`);
|
|
1103
|
-
}
|
|
1104
|
-
catch (e) {
|
|
1105
|
-
addMsg("error", `Push failed: ${e.stderr || e.message}`);
|
|
1106
|
-
}
|
|
1107
|
-
return;
|
|
1108
|
-
}
|
|
1109
|
-
if (trimmed.startsWith("/commit")) {
|
|
1110
|
-
const msg = trimmed.replace("/commit", "").trim();
|
|
1111
|
-
if (!msg) {
|
|
1112
|
-
addMsg("info", "Usage: /commit your commit message here");
|
|
1113
|
-
return;
|
|
1114
|
-
}
|
|
1115
|
-
try {
|
|
1116
|
-
execSync("git add -A", { cwd: process.cwd(), stdio: "pipe" });
|
|
1117
|
-
execSync(`git commit -m "${msg.replace(/"/g, '\\"')}"`, { cwd: process.cwd(), stdio: "pipe" });
|
|
1118
|
-
addMsg("info", `✅ Committed: ${msg}`);
|
|
1119
|
-
}
|
|
1120
|
-
catch (e) {
|
|
1121
|
-
addMsg("error", `Commit failed: ${e.stderr || e.message}`);
|
|
1122
|
-
}
|
|
1123
|
-
return;
|
|
1124
|
-
}
|
|
1125
783
|
setLoading(true);
|
|
1126
784
|
setStreaming(false);
|
|
1127
785
|
setSpinnerMsg(SPINNER_MESSAGES[Math.floor(Math.random() * SPINNER_MESSAGES.length)]);
|
|
@@ -2090,6 +1748,30 @@ process.stdout.write("\x1b[?2004h");
|
|
|
2090
1748
|
// Bracketed paste: \x1b[200~ ... \x1b[201~
|
|
2091
1749
|
let pasteBuffer = "";
|
|
2092
1750
|
let inPaste = false;
|
|
1751
|
+
function emitPasteChunk(content) {
|
|
1752
|
+
const normalized = content.replace(/\r\n/g, "\n").trim();
|
|
1753
|
+
if (!normalized)
|
|
1754
|
+
return true;
|
|
1755
|
+
const lineCount = normalized.split("\n").length;
|
|
1756
|
+
if (lineCount > 2) {
|
|
1757
|
+
pasteEvents.emit("paste", { content: normalized, lines: lineCount });
|
|
1758
|
+
return true;
|
|
1759
|
+
}
|
|
1760
|
+
const sanitized = normalized.replace(/\n/g, " ");
|
|
1761
|
+
if (sanitized) {
|
|
1762
|
+
origPush(sanitized, "utf-8");
|
|
1763
|
+
}
|
|
1764
|
+
return true;
|
|
1765
|
+
}
|
|
1766
|
+
function looksLikeRawMultilinePaste(data) {
|
|
1767
|
+
const normalized = data.replace(/\x1b\[20[01]~/g, "");
|
|
1768
|
+
const newlineMatches = normalized.match(/\r?\n/g) ?? [];
|
|
1769
|
+
const newlineCount = newlineMatches.length;
|
|
1770
|
+
const contentLength = normalized.replace(/[\r\n]/g, "").trim().length;
|
|
1771
|
+
// Avoid swallowing normal Enter presses while still catching terminals that
|
|
1772
|
+
// don't support bracketed paste and dump the whole paste as one raw chunk.
|
|
1773
|
+
return newlineCount >= 2 || (newlineCount >= 1 && contentLength >= 40);
|
|
1774
|
+
}
|
|
2093
1775
|
const origPush = process.stdin.push.bind(process.stdin);
|
|
2094
1776
|
process.stdin.push = function (chunk, encoding) {
|
|
2095
1777
|
if (chunk === null)
|
|
@@ -2105,26 +1787,18 @@ process.stdin.push = function (chunk, encoding) {
|
|
|
2105
1787
|
data = data.replace(/\x1b\[201~/g, "");
|
|
2106
1788
|
pasteBuffer += data;
|
|
2107
1789
|
inPaste = false;
|
|
2108
|
-
const content = pasteBuffer
|
|
1790
|
+
const content = pasteBuffer;
|
|
2109
1791
|
pasteBuffer = "";
|
|
2110
|
-
|
|
2111
|
-
if (lineCount > 2) {
|
|
2112
|
-
// Multi-line paste → store as chunk, don't send to input
|
|
2113
|
-
pasteEvents.emit("paste", { content, lines: lineCount });
|
|
2114
|
-
return true;
|
|
2115
|
-
}
|
|
2116
|
-
// Short paste (1-2 lines) — send as normal input
|
|
2117
|
-
const sanitized = content.replace(/\r?\n/g, " ");
|
|
2118
|
-
if (sanitized) {
|
|
2119
|
-
return origPush(sanitized, "utf-8");
|
|
2120
|
-
}
|
|
2121
|
-
return true;
|
|
1792
|
+
return emitPasteChunk(content);
|
|
2122
1793
|
}
|
|
2123
1794
|
if (inPaste) {
|
|
2124
1795
|
pasteBuffer += data;
|
|
2125
1796
|
return true;
|
|
2126
1797
|
}
|
|
2127
1798
|
data = data.replace(/\x1b\[20[01]~/g, "");
|
|
1799
|
+
if (looksLikeRawMultilinePaste(data)) {
|
|
1800
|
+
return emitPasteChunk(data);
|
|
1801
|
+
}
|
|
2128
1802
|
return origPush(typeof chunk === "string" ? data : Buffer.from(data), encoding);
|
|
2129
1803
|
};
|
|
2130
1804
|
// Disable bracketed paste on exit
|
package/dist/tools/files.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "fs";
|
|
2
|
-
import { join, relative } from "path";
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync, mkdirSync } from "fs";
|
|
2
|
+
import { join, relative, dirname } from "path";
|
|
3
3
|
/**
|
|
4
4
|
* Tool definitions for the OpenAI function calling API
|
|
5
5
|
*/
|
|
@@ -25,7 +25,7 @@ export const FILE_TOOLS = [
|
|
|
25
25
|
type: "function",
|
|
26
26
|
function: {
|
|
27
27
|
name: "write_file",
|
|
28
|
-
description: "Write content to a file. Creates the file if it doesn't exist, overwrites if it does.",
|
|
28
|
+
description: "Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Use this for new files or full rewrites only.",
|
|
29
29
|
parameters: {
|
|
30
30
|
type: "object",
|
|
31
31
|
properties: {
|
|
@@ -42,6 +42,35 @@ export const FILE_TOOLS = [
|
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
|
+
{
|
|
46
|
+
type: "function",
|
|
47
|
+
function: {
|
|
48
|
+
name: "edit_file",
|
|
49
|
+
description: "Edit an existing file by replacing exact text. Prefer this over write_file for small or localized changes.",
|
|
50
|
+
parameters: {
|
|
51
|
+
type: "object",
|
|
52
|
+
properties: {
|
|
53
|
+
path: {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: "Path to the file to edit (relative to project root)",
|
|
56
|
+
},
|
|
57
|
+
oldText: {
|
|
58
|
+
type: "string",
|
|
59
|
+
description: "Exact text to find in the file",
|
|
60
|
+
},
|
|
61
|
+
newText: {
|
|
62
|
+
type: "string",
|
|
63
|
+
description: "Replacement text",
|
|
64
|
+
},
|
|
65
|
+
replaceAll: {
|
|
66
|
+
type: "boolean",
|
|
67
|
+
description: "Replace all exact matches instead of only the first one (default: false)",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
required: ["path", "oldText", "newText"],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
45
74
|
{
|
|
46
75
|
type: "function",
|
|
47
76
|
function: {
|
|
@@ -120,6 +149,7 @@ export async function executeTool(name, args, cwd) {
|
|
|
120
149
|
case "write_file": {
|
|
121
150
|
const filePath = join(cwd, args.path);
|
|
122
151
|
try {
|
|
152
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
123
153
|
writeFileSync(filePath, args.content, "utf-8");
|
|
124
154
|
return `✅ Wrote ${args.content.length} bytes to ${args.path}`;
|
|
125
155
|
}
|
|
@@ -127,6 +157,31 @@ export async function executeTool(name, args, cwd) {
|
|
|
127
157
|
return `Error writing file: ${e}`;
|
|
128
158
|
}
|
|
129
159
|
}
|
|
160
|
+
case "edit_file": {
|
|
161
|
+
const filePath = join(cwd, args.path);
|
|
162
|
+
if (!existsSync(filePath))
|
|
163
|
+
return `Error: File not found: ${args.path}`;
|
|
164
|
+
try {
|
|
165
|
+
const oldText = String(args.oldText ?? "");
|
|
166
|
+
const newText = String(args.newText ?? "");
|
|
167
|
+
const replaceAll = Boolean(args.replaceAll);
|
|
168
|
+
const content = readFileSync(filePath, "utf-8");
|
|
169
|
+
if (!oldText)
|
|
170
|
+
return "Error: oldText cannot be empty.";
|
|
171
|
+
if (!content.includes(oldText)) {
|
|
172
|
+
return `Error: Could not find exact text in ${args.path}`;
|
|
173
|
+
}
|
|
174
|
+
const matchCount = content.split(oldText).length - 1;
|
|
175
|
+
const nextContent = replaceAll
|
|
176
|
+
? content.split(oldText).join(newText)
|
|
177
|
+
: content.replace(oldText, newText);
|
|
178
|
+
writeFileSync(filePath, nextContent, "utf-8");
|
|
179
|
+
return `✅ Edited ${args.path} (${replaceAll ? matchCount : 1} replacement${replaceAll ? (matchCount === 1 ? "" : "s") : ""})`;
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
return `Error editing file: ${e}`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
130
185
|
case "list_files": {
|
|
131
186
|
const dirPath = join(cwd, args.path || ".");
|
|
132
187
|
if (!existsSync(dirPath))
|
package/dist/utils/context.js
CHANGED
|
@@ -116,6 +116,12 @@ You help developers understand, write, debug, and refactor code. You have access
|
|
|
116
116
|
- Use the run_command tool for building, testing, and linters
|
|
117
117
|
- Never delete files without explicit confirmation
|
|
118
118
|
|
|
119
|
+
## Editing Strategy
|
|
120
|
+
- Prefer edit_file for small or localized changes — use it when you only need to change part of a file.
|
|
121
|
+
- edit_file requires the exact text to be found in the file, so read the file first to confirm the exact content.
|
|
122
|
+
- Use write_file only when creating a new file or when the changes affect most of the file.
|
|
123
|
+
- When in doubt about scope, prefer edit_file — it is safer and easier to review.
|
|
124
|
+
|
|
119
125
|
## Repository Map
|
|
120
126
|
The project context below includes a map of the codebase structure. Use this map to understand what files, functions, classes, and types exist where. Use read_file to see full implementations when needed.
|
|
121
127
|
|
package/dist/utils/mcp.d.ts
CHANGED
|
@@ -27,11 +27,16 @@ export declare function loadMCPConfig(cwd: string): MCPConfig;
|
|
|
27
27
|
export declare function connectToServers(config: MCPConfig, onStatus?: (name: string, status: string) => void): Promise<ConnectedServer[]>;
|
|
28
28
|
export declare function disconnectAll(): Promise<void>;
|
|
29
29
|
export declare function getConnectedServers(): ConnectedServer[];
|
|
30
|
+
export declare function buildMCPToolName(serverName: string, toolName: string): string;
|
|
30
31
|
export declare function getAllMCPTools(servers: ConnectedServer[]): ChatCompletionTool[];
|
|
31
32
|
/**
|
|
32
33
|
* Parse an MCP tool call name to extract server name and tool name.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
34
|
+
*
|
|
35
|
+
* New format (collision-safe):
|
|
36
|
+
* mcp_<base64url(server)>__<base64url(tool)>
|
|
37
|
+
*
|
|
38
|
+
* Legacy format still supported for backwards compatibility:
|
|
39
|
+
* mcp_<serverName>_<toolName>
|
|
35
40
|
*/
|
|
36
41
|
export declare function parseMCPToolName(fullName: string): {
|
|
37
42
|
serverName: string;
|