@abacus-ai/cli 1.106.25007 → 2.0.0-canary.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/.oxlintrc.json +8 -0
- package/dist/index.mjs +12603 -0
- package/package.json +7 -39
- package/resources/abacus.ico +0 -0
- package/resources/entitlements.plist +9 -0
- package/src/__e2e__/README.md +196 -0
- package/src/__e2e__/agent-interactions.e2e.test.tsx +61 -0
- package/src/__e2e__/cli-commands.e2e.test.tsx +77 -0
- package/src/__e2e__/conversation-throttle.e2e.test.ts +453 -0
- package/src/__e2e__/conversation.e2e.test.tsx +56 -0
- package/src/__e2e__/diff-preview.e2e.test.tsx +3399 -0
- package/src/__e2e__/file-creation.e2e.test.tsx +149 -0
- package/src/__e2e__/helpers/test-helpers.ts +450 -0
- package/src/__e2e__/keyboard-navigation.e2e.test.tsx +34 -0
- package/src/__e2e__/llm-models.e2e.test.ts +402 -0
- package/src/__e2e__/mcp/mcp-callback-flow.e2e.test.tsx +71 -0
- package/src/__e2e__/mcp/mcp-full-app-ui.e2e.test.tsx +167 -0
- package/src/__e2e__/mcp/mcp-ui-rendering.e2e.test.tsx +185 -0
- package/src/__e2e__/repl.e2e.test.tsx +78 -0
- package/src/__e2e__/shell-compatibility.e2e.test.tsx +76 -0
- package/src/__e2e__/theme-mcp.e2e.test.tsx +98 -0
- package/src/__e2e__/tool-permissions.e2e.test.tsx +66 -0
- package/src/args.ts +22 -0
- package/src/components/__tests__/react-compiler.test.tsx +78 -0
- package/src/components/__tests__/status-indicator.test.tsx +403 -0
- package/src/components/composer/__tests__/bash-runner.test.tsx +263 -0
- package/src/components/composer/agent-mode-indicator.tsx +63 -0
- package/src/components/composer/bash-runner.tsx +54 -0
- package/src/components/composer/commands/default-commands.tsx +615 -0
- package/src/components/composer/commands/handler.tsx +59 -0
- package/src/components/composer/commands/picker.tsx +273 -0
- package/src/components/composer/commands/registry.ts +233 -0
- package/src/components/composer/commands/types.ts +33 -0
- package/src/components/composer/context.tsx +88 -0
- package/src/components/composer/file-mention-picker.tsx +83 -0
- package/src/components/composer/help.tsx +44 -0
- package/src/components/composer/index.tsx +1006 -0
- package/src/components/composer/mentions.ts +57 -0
- package/src/components/composer/message-queue.tsx +70 -0
- package/src/components/composer/mode-panel.tsx +35 -0
- package/src/components/composer/modes/__tests__/bash-handler.test.tsx +755 -0
- package/src/components/composer/modes/__tests__/bash-renderer.test.tsx +1108 -0
- package/src/components/composer/modes/bash-handler.tsx +132 -0
- package/src/components/composer/modes/bash-renderer.tsx +175 -0
- package/src/components/composer/modes/default-handlers.tsx +33 -0
- package/src/components/composer/modes/index.ts +41 -0
- package/src/components/composer/modes/types.ts +21 -0
- package/src/components/composer/persistent-shell.ts +283 -0
- package/src/components/composer/process.ts +65 -0
- package/src/components/composer/types.ts +9 -0
- package/src/components/composer/use-mention-search.ts +68 -0
- package/src/components/error-boundry.tsx +60 -0
- package/src/components/exit-message.tsx +29 -0
- package/src/components/expanded-view.tsx +74 -0
- package/src/components/file-completion.tsx +127 -0
- package/src/components/header.tsx +47 -0
- package/src/components/logo.tsx +37 -0
- package/src/components/segments.tsx +356 -0
- package/src/components/status-indicator.tsx +306 -0
- package/src/components/tool-group-summary.tsx +263 -0
- package/src/components/tool-permissions/ask-user-question-permission-ui.tsx +312 -0
- package/src/components/tool-permissions/diff-preview.tsx +355 -0
- package/src/components/tool-permissions/index.ts +5 -0
- package/src/components/tool-permissions/permission-options.tsx +375 -0
- package/src/components/tool-permissions/permission-preview-header.tsx +57 -0
- package/src/components/tool-permissions/tool-permission-ui.tsx +398 -0
- package/src/components/tools/agent/ask-user-question.tsx +101 -0
- package/src/components/tools/agent/enter-plan-mode.tsx +49 -0
- package/src/components/tools/agent/exit-plan-mode.tsx +75 -0
- package/src/components/tools/agent/handoff-to-main.tsx +27 -0
- package/src/components/tools/agent/subagent.tsx +37 -0
- package/src/components/tools/agent/todo-write.tsx +104 -0
- package/src/components/tools/browser/close-tab.tsx +58 -0
- package/src/components/tools/browser/computer.tsx +70 -0
- package/src/components/tools/browser/get-interactive-elements.tsx +54 -0
- package/src/components/tools/browser/get-tab-content.tsx +51 -0
- package/src/components/tools/browser/navigate-to.tsx +59 -0
- package/src/components/tools/browser/new-tab.tsx +60 -0
- package/src/components/tools/browser/perform-action.tsx +63 -0
- package/src/components/tools/browser/refresh-tab.tsx +43 -0
- package/src/components/tools/browser/switch-tab.tsx +58 -0
- package/src/components/tools/filesystem/delete-file.tsx +104 -0
- package/src/components/tools/filesystem/edit.tsx +220 -0
- package/src/components/tools/filesystem/list-dir.tsx +78 -0
- package/src/components/tools/filesystem/read-file.tsx +180 -0
- package/src/components/tools/filesystem/upload-image.tsx +76 -0
- package/src/components/tools/ide/ide-diagnostics.tsx +62 -0
- package/src/components/tools/index.ts +91 -0
- package/src/components/tools/mcp/mcp-tool.tsx +158 -0
- package/src/components/tools/search/fetch-url.tsx +73 -0
- package/src/components/tools/search/file-search.tsx +78 -0
- package/src/components/tools/search/grep.tsx +90 -0
- package/src/components/tools/search/semantic-search.tsx +66 -0
- package/src/components/tools/search/web-search.tsx +71 -0
- package/src/components/tools/shared/index.tsx +48 -0
- package/src/components/tools/shared/zod-coercion.ts +35 -0
- package/src/components/tools/terminal/bash-tool-output.tsx +174 -0
- package/src/components/tools/terminal/get-terminal-output.tsx +85 -0
- package/src/components/tools/terminal/run-in-terminal.tsx +106 -0
- package/src/components/tools/types.ts +16 -0
- package/src/components/tools.tsx +66 -0
- package/src/components/ui/__tests__/divider.test.tsx +61 -0
- package/src/components/ui/__tests__/gradient.test.tsx +125 -0
- package/src/components/ui/__tests__/input.test.tsx +166 -0
- package/src/components/ui/__tests__/select.test.tsx +273 -0
- package/src/components/ui/__tests__/shimmer.test.tsx +99 -0
- package/src/components/ui/blinking-indicator.tsx +25 -0
- package/src/components/ui/divider.tsx +162 -0
- package/src/components/ui/gradient.tsx +56 -0
- package/src/components/ui/input.tsx +228 -0
- package/src/components/ui/select.tsx +151 -0
- package/src/components/ui/shimmer.tsx +84 -0
- package/src/context/agent-mode.tsx +95 -0
- package/src/context/extension-file.tsx +136 -0
- package/src/context/network-activity.tsx +45 -0
- package/src/context/notification.tsx +62 -0
- package/src/context/shell-size.tsx +49 -0
- package/src/context/shell-title.tsx +38 -0
- package/src/entrypoints/print-mode.ts +312 -0
- package/src/entrypoints/repl.tsx +401 -0
- package/src/hooks/use-agent.ts +15 -0
- package/src/hooks/use-api-client.ts +1 -0
- package/src/hooks/use-available-height.ts +8 -0
- package/src/hooks/use-cleanup.ts +29 -0
- package/src/hooks/use-interrupt-manager.ts +242 -0
- package/src/hooks/use-models.ts +22 -0
- package/src/index.ts +217 -0
- package/src/lib/__tests__/ansi.test.ts +255 -0
- package/src/lib/__tests__/cli.test.ts +122 -0
- package/src/lib/__tests__/commands.test.ts +325 -0
- package/src/lib/__tests__/constants.test.ts +15 -0
- package/src/lib/__tests__/focusables.test.ts +25 -0
- package/src/lib/__tests__/fs.test.ts +231 -0
- package/src/lib/__tests__/markdown.test.tsx +348 -0
- package/src/lib/__tests__/mcpCommandHandler.test.ts +173 -0
- package/src/lib/__tests__/mcpManagement.test.ts +38 -0
- package/src/lib/__tests__/path-paste.test.ts +144 -0
- package/src/lib/__tests__/path.test.ts +300 -0
- package/src/lib/__tests__/queries.test.ts +39 -0
- package/src/lib/__tests__/standaloneMcpService.test.ts +71 -0
- package/src/lib/__tests__/text-buffer.test.ts +328 -0
- package/src/lib/__tests__/text-utils.test.ts +32 -0
- package/src/lib/__tests__/timing.test.ts +78 -0
- package/src/lib/__tests__/utils.test.ts +238 -0
- package/src/lib/__tests__/vim-buffer-actions.test.ts +154 -0
- package/src/lib/ansi.ts +150 -0
- package/src/lib/cli-push-server.ts +112 -0
- package/src/lib/cli.ts +44 -0
- package/src/lib/clipboard.ts +226 -0
- package/src/lib/command-utils.ts +93 -0
- package/src/lib/commands.ts +270 -0
- package/src/lib/constants.ts +3 -0
- package/src/lib/extension-connection.ts +181 -0
- package/src/lib/focusables.ts +7 -0
- package/src/lib/fs.ts +533 -0
- package/src/lib/markdown/code-block.tsx +63 -0
- package/src/lib/markdown/index.ts +4 -0
- package/src/lib/markdown/link.tsx +19 -0
- package/src/lib/markdown/markdown.tsx +372 -0
- package/src/lib/markdown/types.ts +15 -0
- package/src/lib/mcpCommandHandler.ts +121 -0
- package/src/lib/mcpManagement.ts +44 -0
- package/src/lib/path-paste.ts +185 -0
- package/src/lib/path.ts +179 -0
- package/src/lib/queries.ts +15 -0
- package/src/lib/standaloneMcpService.ts +688 -0
- package/src/lib/status-utils.ts +237 -0
- package/src/lib/test-utils.tsx +72 -0
- package/src/lib/text-buffer.ts +2415 -0
- package/src/lib/text-utils.ts +272 -0
- package/src/lib/timing.ts +63 -0
- package/src/lib/types.ts +295 -0
- package/src/lib/utils.ts +182 -0
- package/src/lib/vim-buffer-actions.ts +732 -0
- package/src/providers/agent.tsx +1075 -0
- package/src/providers/api-client.tsx +43 -0
- package/src/services/logger.ts +85 -0
- package/src/terminal/detection.ts +187 -0
- package/src/terminal/exit.ts +279 -0
- package/src/terminal/notification.ts +83 -0
- package/src/terminal/progress.ts +201 -0
- package/src/terminal/setup.ts +797 -0
- package/src/terminal/suspend.ts +58 -0
- package/src/terminal/types.ts +51 -0
- package/src/theme/context.tsx +57 -0
- package/src/theme/index.ts +4 -0
- package/src/theme/themed.tsx +35 -0
- package/src/theme/themes.json +546 -0
- package/src/theme/types.ts +110 -0
- package/src/tools/types.ts +59 -0
- package/src/tools/utils/__tests__/zod-coercion.test.ts +33 -0
- package/src/tools/utils/tool-ui-components.tsx +631 -0
- package/src/tools/utils/zod-coercion.ts +35 -0
- package/tsconfig.json +11 -0
- package/tsconfig.node.json +29 -0
- package/tsconfig.test.json +27 -0
- package/tsdown.config.ts +17 -0
- package/vitest.config.ts +76 -0
- package/README.md +0 -28
- package/dist/index.js +0 -26
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
import type { McpManager } from "@codellm/agent";
|
|
2
|
+
|
|
3
|
+
import { product } from "@codellm/product";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
|
|
7
|
+
import packageJson from "@/package.json" with { type: "json" };
|
|
8
|
+
|
|
9
|
+
import { McpCommandHandler } from "../../../lib/mcpCommandHandler.js";
|
|
10
|
+
import { AgentStatus } from "../../../providers/agent.js";
|
|
11
|
+
import { setupTerminal } from "../../../terminal/setup.js";
|
|
12
|
+
import themesData from "../../../theme/themes.json" with { type: "json" };
|
|
13
|
+
import { ComposerMode } from "../types.js";
|
|
14
|
+
import { commandRegistry } from "./registry.js";
|
|
15
|
+
import { CommandDefinition } from "./types.js";
|
|
16
|
+
|
|
17
|
+
interface ThemeDefinition {
|
|
18
|
+
name: string;
|
|
19
|
+
displayName: string;
|
|
20
|
+
colors: Record<string, string>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ThemesRegistry {
|
|
24
|
+
version: string;
|
|
25
|
+
default: string;
|
|
26
|
+
themes: ThemeDefinition[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const themesRegistry: ThemesRegistry = themesData as ThemesRegistry;
|
|
30
|
+
|
|
31
|
+
function formatCommandHelp(): string {
|
|
32
|
+
const commands = commandRegistry.getAll();
|
|
33
|
+
const lines: string[] = [];
|
|
34
|
+
const maxUsageLength = Math.max(
|
|
35
|
+
0,
|
|
36
|
+
...commands.map((cmd) => {
|
|
37
|
+
if (cmd.subcommands && cmd.subcommands.length > 0) {
|
|
38
|
+
return Math.max(0, ...cmd.subcommands.map((sub) => sub.usage?.length || 0));
|
|
39
|
+
}
|
|
40
|
+
return cmd.usage?.length || 0;
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
lines.push(
|
|
45
|
+
"Abacus.AI CLI",
|
|
46
|
+
"",
|
|
47
|
+
"Abacus.AI CLI has read access to files in the current directory and can run commands and edit files with your permission.",
|
|
48
|
+
"",
|
|
49
|
+
"Use npx -y @abacus-ai/cli@latest to run latest version of abacusai interactive mode directly from your terminal",
|
|
50
|
+
"",
|
|
51
|
+
"Usage Modes:",
|
|
52
|
+
"- REPL: abacusai (interactive session)",
|
|
53
|
+
'- Non-interactive: abacusai -p "question"',
|
|
54
|
+
"",
|
|
55
|
+
"Run abacusai --help for all command line options",
|
|
56
|
+
"",
|
|
57
|
+
"For first time users, use /login to authenticate to Abacus.AI CLI",
|
|
58
|
+
"",
|
|
59
|
+
"Common Tasks:",
|
|
60
|
+
"- Ask questions about your codebase > How does agent.py work?",
|
|
61
|
+
"- Edit files > Update code.ts to...",
|
|
62
|
+
"- Fix errors > cargo build",
|
|
63
|
+
"- Run commands > /help",
|
|
64
|
+
"- Run bash commands > wc",
|
|
65
|
+
"",
|
|
66
|
+
"Interactive Mode Commands:",
|
|
67
|
+
"",
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
for (const cmd of commands) {
|
|
71
|
+
if (cmd.subcommands && cmd.subcommands.length > 0) {
|
|
72
|
+
for (const sub of cmd.subcommands) {
|
|
73
|
+
lines.push(` ${(sub.usage || "").padEnd(maxUsageLength + 2)} ${sub.description || ""}`);
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
lines.push(` ${(cmd.usage || "").padEnd(maxUsageLength + 2)} ${cmd.description || ""}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
lines.push("", "Type any command and press Enter to execute.");
|
|
81
|
+
|
|
82
|
+
return lines.join("\n");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface ModelInfo {
|
|
86
|
+
label: string;
|
|
87
|
+
value: string;
|
|
88
|
+
model: any;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function registerDefaultCommands(handlers: {
|
|
92
|
+
onExit?: () => void;
|
|
93
|
+
onNewChat?: () => void;
|
|
94
|
+
onClearChat?: () => void;
|
|
95
|
+
onModelChange?: (model: string) => void;
|
|
96
|
+
onResumeChat?: (title: string) => void;
|
|
97
|
+
onStop?: () => void;
|
|
98
|
+
onHelp?: () => void;
|
|
99
|
+
onLogin?: (emailOrApiKey?: string) => void;
|
|
100
|
+
onLogout?: () => void;
|
|
101
|
+
getModels?: () => Promise<ModelInfo[]> | ModelInfo[];
|
|
102
|
+
getChatTitles?: () => Promise<string[]> | string[];
|
|
103
|
+
addNotification?: (
|
|
104
|
+
message: string,
|
|
105
|
+
severity?: "info" | "warning" | "error",
|
|
106
|
+
temp?: boolean,
|
|
107
|
+
) => void;
|
|
108
|
+
mcp?: McpManager;
|
|
109
|
+
addAllowedDirectory?: (dirPath: string) => void;
|
|
110
|
+
refreshMcpTools?: (skipReload?: boolean, showNotification?: boolean) => Promise<void>;
|
|
111
|
+
getAgentStatus?: () => string;
|
|
112
|
+
getUserInfo?: () => Promise<void>;
|
|
113
|
+
setTheme?: (themeName: string) => void;
|
|
114
|
+
checkEnvAuth?: () => { isEnvAuth: boolean; shouldHide: boolean };
|
|
115
|
+
}): void {
|
|
116
|
+
const mcpHandler = handlers.mcp ? new McpCommandHandler(handlers.mcp) : undefined;
|
|
117
|
+
|
|
118
|
+
const addNotification = (
|
|
119
|
+
message: string,
|
|
120
|
+
severity: "info" | "error" | "warning" = "info",
|
|
121
|
+
temp?: boolean,
|
|
122
|
+
) => {
|
|
123
|
+
handlers.addNotification?.(message, severity, temp);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const commands: CommandDefinition[] = [
|
|
127
|
+
{
|
|
128
|
+
id: "version",
|
|
129
|
+
description: "Show CLI version",
|
|
130
|
+
usage: "/version",
|
|
131
|
+
execute: () => {
|
|
132
|
+
addNotification(`Abacus.AI CLI version ${packageJson.version}`, "info");
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "exit",
|
|
137
|
+
description: "Exit the application",
|
|
138
|
+
usage: "/exit",
|
|
139
|
+
alias: { [ComposerMode.Prompt]: ["exit", "quit"] },
|
|
140
|
+
execute: () => {
|
|
141
|
+
handlers.onExit?.();
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: "new",
|
|
146
|
+
description: "Start a new conversation",
|
|
147
|
+
usage: "/new",
|
|
148
|
+
execute: () => {
|
|
149
|
+
handlers.onNewChat?.();
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: "clear",
|
|
154
|
+
description: "Clear the output display while keeping the current conversation",
|
|
155
|
+
usage: "/clear",
|
|
156
|
+
alias: { [ComposerMode.Prompt]: ["cls", "clear"] },
|
|
157
|
+
execute: () => {
|
|
158
|
+
handlers.onClearChat?.();
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: "stop",
|
|
163
|
+
description: "Stop the current streaming response and any running bash tools",
|
|
164
|
+
usage: "/stop",
|
|
165
|
+
execute: async () => {
|
|
166
|
+
const agentStatus = handlers.getAgentStatus?.();
|
|
167
|
+
const isAgentActive =
|
|
168
|
+
agentStatus === AgentStatus.Submitted ||
|
|
169
|
+
agentStatus === AgentStatus.Streaming ||
|
|
170
|
+
agentStatus === AgentStatus.ExecutingTool ||
|
|
171
|
+
agentStatus === AgentStatus.WaitingForToolPermission;
|
|
172
|
+
|
|
173
|
+
handlers.onStop?.();
|
|
174
|
+
if (isAgentActive) {
|
|
175
|
+
addNotification("Agent stopped successfully", "info");
|
|
176
|
+
} else {
|
|
177
|
+
addNotification("No active agent process to stop", "warning");
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: "help",
|
|
183
|
+
description: "Show commands and functionalities",
|
|
184
|
+
usage: "/help",
|
|
185
|
+
alias: { [ComposerMode.Prompt]: ["help"] },
|
|
186
|
+
execute: () => {
|
|
187
|
+
if (handlers.onHelp) {
|
|
188
|
+
handlers.onHelp();
|
|
189
|
+
} else {
|
|
190
|
+
addNotification(formatCommandHelp(), "info");
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: "model",
|
|
196
|
+
description: "Switch to a different model",
|
|
197
|
+
usage: "/model <model_name>",
|
|
198
|
+
requiresArgument: true,
|
|
199
|
+
completionProvider: async (query: string) => {
|
|
200
|
+
if (!handlers.getModels) {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const models = await handlers.getModels();
|
|
205
|
+
|
|
206
|
+
if (!models || models.length === 0) {
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const lowerQuery = query.trim().toLowerCase();
|
|
211
|
+
|
|
212
|
+
if (!lowerQuery) {
|
|
213
|
+
return models.map((m) => m.label);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return models
|
|
217
|
+
.filter(
|
|
218
|
+
(m) =>
|
|
219
|
+
m.label.toLowerCase().includes(lowerQuery) ||
|
|
220
|
+
m.value.toLowerCase().includes(lowerQuery),
|
|
221
|
+
)
|
|
222
|
+
.map((m) => m.label);
|
|
223
|
+
},
|
|
224
|
+
execute: async (args: string[]) => {
|
|
225
|
+
const modelName = args.join(" ").trim();
|
|
226
|
+
if (!modelName) {
|
|
227
|
+
addNotification("Please specify a model name", "error");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (!handlers.getModels) {
|
|
232
|
+
addNotification("Models not available", "error");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const models = await handlers.getModels();
|
|
237
|
+
const matchedModel = models.find((m) => m.label === modelName);
|
|
238
|
+
|
|
239
|
+
if (matchedModel) {
|
|
240
|
+
handlers.onModelChange?.(matchedModel.value);
|
|
241
|
+
} else {
|
|
242
|
+
addNotification(`Model "${modelName}" not found`, "error");
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: "resume",
|
|
248
|
+
description: "Resume a previous chat conversation",
|
|
249
|
+
usage: "/resume <chat_title>",
|
|
250
|
+
requiresArgument: true,
|
|
251
|
+
completionProvider: async (query: string) => {
|
|
252
|
+
if (!handlers.getChatTitles) {
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
const titles = await handlers.getChatTitles();
|
|
256
|
+
const lowerQuery = query.toLowerCase();
|
|
257
|
+
return titles.filter((title) => title.toLowerCase().includes(lowerQuery));
|
|
258
|
+
},
|
|
259
|
+
execute: (args: string[]) => {
|
|
260
|
+
const title = args.join(" ").trim();
|
|
261
|
+
if (!title) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
handlers.onResumeChat?.(title);
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: "login",
|
|
269
|
+
description: "Log in to Abacus.AI CLI with email or API key",
|
|
270
|
+
usage: "/login <email|api_key>",
|
|
271
|
+
execute: async (args: string[]) => {
|
|
272
|
+
// Check if user is authenticated via environment variables first
|
|
273
|
+
if (handlers.checkEnvAuth) {
|
|
274
|
+
const { isEnvAuth } = handlers.checkEnvAuth();
|
|
275
|
+
if (isEnvAuth) {
|
|
276
|
+
addNotification(
|
|
277
|
+
`You are logged in via environment variables. Remove ${product.envPrefix}API_KEY to logout.`,
|
|
278
|
+
"warning",
|
|
279
|
+
);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const argument = args.filter(Boolean).join(" ").trim();
|
|
285
|
+
|
|
286
|
+
if (!argument) {
|
|
287
|
+
addNotification(
|
|
288
|
+
"This command requires an argument. Usage: /login <email> or /login <api_key>\n\n" +
|
|
289
|
+
"Get your API key from: https://abacus.ai/app/abacusai-cli",
|
|
290
|
+
"warning",
|
|
291
|
+
);
|
|
292
|
+
// Throw error to prevent clearing the input buffer so user can add the argument
|
|
293
|
+
throw new Error("Missing required argument");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
handlers.onLogin?.(argument);
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
id: "logout",
|
|
301
|
+
description: "Log out of Abacus.AI CLI",
|
|
302
|
+
usage: "/logout",
|
|
303
|
+
execute: async () => {
|
|
304
|
+
// Check if user is authenticated via environment variables
|
|
305
|
+
if (handlers.checkEnvAuth) {
|
|
306
|
+
const { isEnvAuth } = handlers.checkEnvAuth();
|
|
307
|
+
if (isEnvAuth) {
|
|
308
|
+
addNotification(
|
|
309
|
+
`You are logged in via environment variables. Remove ${product.envPrefix}API_KEY to logout.`,
|
|
310
|
+
"warning",
|
|
311
|
+
);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
handlers.onLogout?.();
|
|
318
|
+
addNotification("Logged out successfully", "info");
|
|
319
|
+
} catch (error) {
|
|
320
|
+
const errorMessage = `Logout failed: ${error instanceof Error ? error.message : "Unknown error occurred"}`;
|
|
321
|
+
addNotification(errorMessage, "error");
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
id: "status",
|
|
327
|
+
description: "Show current user status",
|
|
328
|
+
usage: "/status",
|
|
329
|
+
execute: async () => {
|
|
330
|
+
try {
|
|
331
|
+
await handlers.getUserInfo?.();
|
|
332
|
+
} catch (error) {
|
|
333
|
+
const errorMessage = `Failed to get status: ${error instanceof Error ? error.message : "Unknown error occurred"}`;
|
|
334
|
+
addNotification(errorMessage, "error");
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
id: "usage",
|
|
340
|
+
description: "Show total credits consumed in current session",
|
|
341
|
+
usage: "/usage",
|
|
342
|
+
execute: async () => {
|
|
343
|
+
addNotification("Usage information not available in this version", "info");
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: "terminal-setup",
|
|
348
|
+
description: "Automatically configure terminal for Shift+Enter newlines",
|
|
349
|
+
usage: "/terminal-setup",
|
|
350
|
+
execute: async () => {
|
|
351
|
+
try {
|
|
352
|
+
addNotification("Setting up terminal keybindings...", "info");
|
|
353
|
+
|
|
354
|
+
const result = await setupTerminal();
|
|
355
|
+
|
|
356
|
+
if (result.success) {
|
|
357
|
+
let message = result.message;
|
|
358
|
+
if (result.backupPath) {
|
|
359
|
+
message += `\n\nBackup created: ${result.backupPath}`;
|
|
360
|
+
}
|
|
361
|
+
if (result.requiresRestart) {
|
|
362
|
+
message += "\n\nPlease restart your terminal for changes to take effect.";
|
|
363
|
+
}
|
|
364
|
+
addNotification(message, "info");
|
|
365
|
+
} else {
|
|
366
|
+
addNotification(result.message, "warning");
|
|
367
|
+
}
|
|
368
|
+
} catch (error) {
|
|
369
|
+
addNotification(
|
|
370
|
+
`Terminal setup failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
371
|
+
"error",
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
id: "theme",
|
|
378
|
+
description: "Switch between color themes",
|
|
379
|
+
usage: "/theme [theme_name]",
|
|
380
|
+
completionProvider: async (query: string) => {
|
|
381
|
+
try {
|
|
382
|
+
const themes = themesRegistry.themes;
|
|
383
|
+
const themeNames = themes.map((t) => t.displayName);
|
|
384
|
+
|
|
385
|
+
if (!query.trim()) {
|
|
386
|
+
return themeNames;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const lowerQuery = query.toLowerCase();
|
|
390
|
+
return themeNames.filter((name) => name.toLowerCase().includes(lowerQuery));
|
|
391
|
+
} catch {
|
|
392
|
+
return [];
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
execute: async (args: string[]) => {
|
|
396
|
+
const themeName = args.join(" ").trim();
|
|
397
|
+
|
|
398
|
+
if (!themeName) {
|
|
399
|
+
// Don't show notification - completion provider already shows themes
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
const themes = themesRegistry.themes;
|
|
405
|
+
|
|
406
|
+
const matchedTheme = themes.find(
|
|
407
|
+
(t) =>
|
|
408
|
+
t.displayName.toLowerCase() === themeName.toLowerCase() ||
|
|
409
|
+
t.name.toLowerCase() === themeName.toLowerCase(),
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
if (matchedTheme) {
|
|
413
|
+
// Switch theme via handler
|
|
414
|
+
if (handlers.setTheme) {
|
|
415
|
+
handlers.setTheme(matchedTheme.name);
|
|
416
|
+
addNotification(`${matchedTheme.displayName} applied`, "info");
|
|
417
|
+
} else {
|
|
418
|
+
addNotification("Theme switching not available", "error");
|
|
419
|
+
}
|
|
420
|
+
} else {
|
|
421
|
+
addNotification(`Theme "${themeName}" not found`, "error");
|
|
422
|
+
}
|
|
423
|
+
} catch (error) {
|
|
424
|
+
addNotification(
|
|
425
|
+
`Failed to switch theme: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
426
|
+
"error",
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
id: "mcp",
|
|
433
|
+
description: "MCP server management",
|
|
434
|
+
usage: "/mcp <subcommand>",
|
|
435
|
+
subcommands: [
|
|
436
|
+
{
|
|
437
|
+
id: "list",
|
|
438
|
+
description: "List configured MCP servers",
|
|
439
|
+
usage: "/mcp list",
|
|
440
|
+
execute: async () => {
|
|
441
|
+
if (!mcpHandler) {
|
|
442
|
+
addNotification("MCP service not available", "error");
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
const result = await mcpHandler.listServers();
|
|
447
|
+
addNotification(result, "info");
|
|
448
|
+
} catch (error) {
|
|
449
|
+
const errorMessage = `Failed to list MCP servers: ${error instanceof Error ? error.message : "Unknown error occurred"}`;
|
|
450
|
+
addNotification(errorMessage, "error");
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
id: "add",
|
|
456
|
+
description: "Add an MCP server",
|
|
457
|
+
usage: "/mcp add <name> <commandOrUrl> [args...]",
|
|
458
|
+
requiresArgument: true,
|
|
459
|
+
execute: async (args: string[]) => {
|
|
460
|
+
if (!mcpHandler) {
|
|
461
|
+
addNotification("MCP service not available", "error");
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (args.length < 2) {
|
|
465
|
+
addNotification("Usage: /mcp add <name> <commandOrUrl> [args...]", "error");
|
|
466
|
+
addNotification("[TIP] Use /mcp add-json for JSON configuration", "info");
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const [name, commandOrUrl, ...extraArgs] = args;
|
|
470
|
+
if (commandOrUrl.trim().startsWith("{")) {
|
|
471
|
+
addNotification(
|
|
472
|
+
"Invalid: JSON detected. Use /mcp add-json for JSON configuration",
|
|
473
|
+
"error",
|
|
474
|
+
);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
try {
|
|
478
|
+
await mcpHandler.addServer(name, commandOrUrl, extraArgs);
|
|
479
|
+
addNotification(`Added MCP server '${name}'`, "info");
|
|
480
|
+
} catch (error) {
|
|
481
|
+
const errorMessage = `Failed to add MCP server: ${error instanceof Error ? error.message : "Unknown error occurred"}`;
|
|
482
|
+
addNotification(errorMessage, "error");
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
id: "add-json",
|
|
488
|
+
description: "Add an MCP server with JSON configuration",
|
|
489
|
+
usage: "/mcp add-json <name> <json>",
|
|
490
|
+
requiresArgument: true,
|
|
491
|
+
execute: async (args: string[]) => {
|
|
492
|
+
if (!mcpHandler) {
|
|
493
|
+
addNotification("MCP service not available", "error");
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
if (args.length !== 2) {
|
|
497
|
+
addNotification("Usage: /mcp add-json <name> <json>", "error");
|
|
498
|
+
addNotification(
|
|
499
|
+
'Example: /mcp add-json github \'{"command":"npx","args":["-y","@modelcontextprotocol/server-github"],"env":{"GITHUB_PERSONAL_ACCESS_TOKEN":"your_token"}}\'',
|
|
500
|
+
"info",
|
|
501
|
+
);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
const [name, jsonConfig] = args;
|
|
506
|
+
await mcpHandler.addServerFromJson(name, jsonConfig);
|
|
507
|
+
addNotification(`Added MCP server '${name}' from JSON`, "info");
|
|
508
|
+
} catch (error) {
|
|
509
|
+
const errorMessage = `Failed to add MCP server: ${error instanceof Error ? error.message : "Unknown error occurred"}`;
|
|
510
|
+
addNotification(errorMessage, "error");
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
id: "remove",
|
|
516
|
+
description: "Remove an MCP server",
|
|
517
|
+
usage: "/mcp remove <name>",
|
|
518
|
+
requiresArgument: true,
|
|
519
|
+
execute: async (args: string[]) => {
|
|
520
|
+
if (!mcpHandler) {
|
|
521
|
+
addNotification("MCP service not available", "error");
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (args.length !== 1) {
|
|
525
|
+
addNotification("Usage: /mcp remove <name>", "error");
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
await mcpHandler.removeServer(args[0]);
|
|
530
|
+
addNotification(`Removed MCP server '${args[0]}'`, "info");
|
|
531
|
+
} catch (error) {
|
|
532
|
+
const errorMessage = `Failed to remove MCP server: ${error instanceof Error ? error.message : "Unknown error occurred"}`;
|
|
533
|
+
addNotification(errorMessage, "error");
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
id: "get",
|
|
539
|
+
description: "Get details about an MCP server",
|
|
540
|
+
usage: "/mcp get <name>",
|
|
541
|
+
requiresArgument: true,
|
|
542
|
+
execute: async (args: string[]) => {
|
|
543
|
+
if (!mcpHandler) {
|
|
544
|
+
addNotification("MCP service not available", "error");
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (args.length !== 1) {
|
|
548
|
+
addNotification("Usage: /mcp get <name>", "error");
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
try {
|
|
552
|
+
const result = await mcpHandler.getServer(args[0]);
|
|
553
|
+
if (result) {
|
|
554
|
+
addNotification(result, "info");
|
|
555
|
+
} else {
|
|
556
|
+
addNotification(`Server '${args[0]}' not found`, "error");
|
|
557
|
+
}
|
|
558
|
+
} catch (error) {
|
|
559
|
+
const errorMessage = `Failed to get MCP server: ${error instanceof Error ? error.message : "Unknown error occurred"}`;
|
|
560
|
+
addNotification(errorMessage, "error");
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
],
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
id: "add-dir",
|
|
568
|
+
description: "Add a directory to the agent's allowed access paths for this session",
|
|
569
|
+
usage: "/add-dir <path>",
|
|
570
|
+
requiresArgument: true,
|
|
571
|
+
execute: async (args: string[]) => {
|
|
572
|
+
const rawPath = args.join(" ").trim();
|
|
573
|
+
if (!rawPath) {
|
|
574
|
+
addNotification("Usage: /add-dir <path>", "error");
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (!handlers.addAllowedDirectory) {
|
|
578
|
+
addNotification("Cannot add directory: session not available", "error");
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
try {
|
|
582
|
+
const expanded = rawPath.replace(/^~/, homedir());
|
|
583
|
+
const absolute = resolve(expanded);
|
|
584
|
+
handlers.addAllowedDirectory(absolute);
|
|
585
|
+
addNotification(`Added '${absolute}' to allowed directories`, "info");
|
|
586
|
+
} catch (error) {
|
|
587
|
+
addNotification(
|
|
588
|
+
`Failed to add directory: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
589
|
+
"error",
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
];
|
|
595
|
+
|
|
596
|
+
commandRegistry.clear();
|
|
597
|
+
|
|
598
|
+
// Filter commands based on environment auth status
|
|
599
|
+
const registerCommands = async () => {
|
|
600
|
+
let commandsToRegister = commands;
|
|
601
|
+
|
|
602
|
+
if (handlers.checkEnvAuth) {
|
|
603
|
+
const { shouldHide } = handlers.checkEnvAuth();
|
|
604
|
+
if (shouldHide) {
|
|
605
|
+
// Hide login and logout commands when in no-auth mode with valid env auth
|
|
606
|
+
commandsToRegister = commands.filter((cmd) => cmd.id !== "login" && cmd.id !== "logout");
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
commandsToRegister.forEach((cmd) => commandRegistry.register(cmd));
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// Register commands asynchronously
|
|
614
|
+
void registerCommands();
|
|
615
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
import { ModeRenderProps } from "../modes/types.js";
|
|
4
|
+
import { CommandPicker } from "./picker.js";
|
|
5
|
+
import { commandRegistry } from "./registry.js";
|
|
6
|
+
|
|
7
|
+
export function useCommandHandler(clearBuffer: () => void) {
|
|
8
|
+
const executeCommand = useCallback(
|
|
9
|
+
async (value: string) => {
|
|
10
|
+
const trimmed = value.trim();
|
|
11
|
+
|
|
12
|
+
if (!trimmed.startsWith("/")) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const match = commandRegistry.match(trimmed);
|
|
17
|
+
if (!match || match.needsSubcommand) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const executor = match.subcommand || match.command;
|
|
22
|
+
if (executor.completionProvider && executor.requiresArgument) {
|
|
23
|
+
clearBuffer();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (await commandRegistry.execute(match)) {
|
|
29
|
+
clearBuffer();
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
// If command throws an error, don't clear buffer
|
|
33
|
+
// This allows commands to show validation errors without losing user input
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
[clearBuffer],
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const CommandRenderer = useCallback(({ prompt, onExit, availableHeight }: ModeRenderProps) => {
|
|
40
|
+
return (
|
|
41
|
+
<CommandPicker
|
|
42
|
+
prompt={prompt}
|
|
43
|
+
onAccept={onExit}
|
|
44
|
+
onCancel={onExit}
|
|
45
|
+
availableHeight={availableHeight}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
const shouldPreventDefault = useCallback((key: string) => {
|
|
51
|
+
return ["up", "down", "escape", "tab"].includes(key);
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
executeCommand,
|
|
56
|
+
renderer: CommandRenderer,
|
|
57
|
+
shouldPreventDefault,
|
|
58
|
+
};
|
|
59
|
+
}
|