@heysalad/cheri-cli 0.6.0 → 0.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heysalad/cheri-cli",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Cheri CLI - AI-powered cloud IDE by HeySalad. Like Claude Code, but for cloud workspaces.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,151 +1,68 @@
1
1
  import { apiClient } from "../lib/api-client.js";
2
2
  import { log } from "../lib/logger.js";
3
+ import { getToolDefinitions, executeTool as executeLocalTool, requiresConfirmation } from "../lib/tools/index.js";
3
4
  import chalk from "chalk";
5
+ import readline from "readline";
6
+
7
+ const SYSTEM_PROMPT = `You are Cheri, an AI coding assistant by HeySalad. You are a powerful agentic coding tool that can read, write, edit, and search code, execute shell commands, and manage cloud workspaces.
8
+
9
+ You have two categories of tools:
10
+ 1. LOCAL CODING TOOLS — read_file, write_file, edit_file, run_command, search_files, search_content, list_directory. Use these to work directly with the user's local codebase.
11
+ 2. CLOUD PLATFORM TOOLS — get_account_info, list_workspaces, create_workspace, stop_workspace, get_workspace_status, get_memory, add_memory, clear_memory, get_usage, get_config, set_config. Use these to manage the Cheri cloud platform.
12
+
13
+ Guidelines:
14
+ - Read files before editing them. Understand existing code before making changes.
15
+ - Use search_files and search_content to explore unfamiliar codebases.
16
+ - Use list_directory to understand project structure.
17
+ - When writing code, match existing style and patterns.
18
+ - For shell commands, prefer specific commands over broad ones.
19
+ - Be concise. Show what you did and the result.
20
+ - Never guess file contents — always read first.
21
+ - Current working directory: ${process.cwd()}`;
22
+
23
+ // Cloud platform tools (executed via API)
24
+ const CLOUD_TOOLS = [
25
+ { name: "get_account_info", description: "Get the current user's account information", parameters: { type: "object", properties: {}, required: [] } },
26
+ { name: "list_workspaces", description: "List all cloud workspaces for the current user", parameters: { type: "object", properties: {}, required: [] } },
27
+ { name: "create_workspace", description: "Launch a new cloud workspace for a GitHub repository", parameters: { type: "object", properties: { repo: { type: "string", description: "GitHub repo in owner/name format" } }, required: ["repo"] } },
28
+ { name: "stop_workspace", description: "Stop and delete a running workspace", parameters: { type: "object", properties: { id: { type: "string", description: "Workspace ID to stop" } }, required: ["id"] } },
29
+ { name: "get_workspace_status", description: "Get the status of a specific workspace", parameters: { type: "object", properties: { id: { type: "string", description: "Workspace ID" } }, required: ["id"] } },
30
+ { name: "get_memory", description: "Retrieve all stored memory entries", parameters: { type: "object", properties: {}, required: [] } },
31
+ { name: "add_memory", description: "Add a new memory entry for the user", parameters: { type: "object", properties: { content: { type: "string", description: "Memory content to store" }, category: { type: "string", description: "Optional category (defaults to 'general')" } }, required: ["content"] } },
32
+ { name: "clear_memory", description: "Clear all stored memory entries", parameters: { type: "object", properties: {}, required: [] } },
33
+ { name: "get_usage", description: "Get the user's API usage and rate limit statistics", parameters: { type: "object", properties: {}, required: [] } },
34
+ { name: "get_config", description: "Get a configuration value by key (dot notation supported)", parameters: { type: "object", properties: { key: { type: "string", description: "Config key, e.g. 'ai.provider'" } }, required: ["key"] } },
35
+ { name: "set_config", description: "Set a configuration value", parameters: { type: "object", properties: { key: { type: "string", description: "Config key" }, value: { type: "string", description: "Value to set" } }, required: ["key", "value"] } },
36
+ ];
4
37
 
5
- const SYSTEM_PROMPT = `You are Cheri Agent, an AI assistant for the Cheri cloud IDE platform. You help users manage cloud workspaces, memory, configuration, and their account. Use the provided tools to get real data — never guess or fabricate information. Be concise. After performing actions, briefly summarize what happened and the result.`;
38
+ const CLOUD_TOOL_NAMES = new Set(CLOUD_TOOLS.map((t) => t.name));
6
39
 
7
- const TOOLS = [
8
- {
9
- type: "function",
10
- function: {
11
- name: "get_account_info",
12
- description: "Get the current user's account information",
13
- parameters: { type: "object", properties: {}, required: [] },
14
- },
15
- },
16
- {
17
- type: "function",
18
- function: {
19
- name: "list_workspaces",
20
- description: "List all cloud workspaces for the current user",
21
- parameters: { type: "object", properties: {}, required: [] },
22
- },
23
- },
24
- {
25
- type: "function",
26
- function: {
27
- name: "create_workspace",
28
- description: "Launch a new cloud workspace for a GitHub repository",
29
- parameters: {
30
- type: "object",
31
- properties: { repo: { type: "string", description: "GitHub repo in owner/name format" } },
32
- required: ["repo"],
33
- },
34
- },
35
- },
36
- {
37
- type: "function",
38
- function: {
39
- name: "stop_workspace",
40
- description: "Stop and delete a running workspace",
41
- parameters: {
42
- type: "object",
43
- properties: { id: { type: "string", description: "Workspace ID to stop" } },
44
- required: ["id"],
45
- },
46
- },
47
- },
48
- {
49
- type: "function",
50
- function: {
51
- name: "get_workspace_status",
52
- description: "Get the status of a specific workspace",
53
- parameters: {
54
- type: "object",
55
- properties: { id: { type: "string", description: "Workspace ID" } },
56
- required: ["id"],
57
- },
58
- },
59
- },
60
- {
61
- type: "function",
62
- function: {
63
- name: "get_memory",
64
- description: "Retrieve all stored memory entries",
65
- parameters: { type: "object", properties: {}, required: [] },
66
- },
67
- },
68
- {
40
+ // Build unified tool list in OpenAI function-calling format
41
+ function buildTools() {
42
+ const localDefs = getToolDefinitions().map((t) => ({
69
43
  type: "function",
70
- function: {
71
- name: "add_memory",
72
- description: "Add a new memory entry for the user",
73
- parameters: {
74
- type: "object",
75
- properties: {
76
- content: { type: "string", description: "Memory content to store" },
77
- category: { type: "string", description: "Optional category (defaults to 'general')" },
78
- },
79
- required: ["content"],
80
- },
81
- },
82
- },
83
- {
44
+ function: { name: t.name, description: t.description, parameters: t.parameters },
45
+ }));
46
+ const cloudDefs = CLOUD_TOOLS.map((t) => ({
84
47
  type: "function",
85
- function: {
86
- name: "clear_memory",
87
- description: "Clear all stored memory entries",
88
- parameters: { type: "object", properties: {}, required: [] },
89
- },
90
- },
91
- {
92
- type: "function",
93
- function: {
94
- name: "get_usage",
95
- description: "Get the user's API usage and rate limit statistics",
96
- parameters: { type: "object", properties: {}, required: [] },
97
- },
98
- },
99
- {
100
- type: "function",
101
- function: {
102
- name: "get_config",
103
- description: "Get a configuration value by key (dot notation supported)",
104
- parameters: {
105
- type: "object",
106
- properties: { key: { type: "string", description: "Config key, e.g. 'ai.provider'" } },
107
- required: ["key"],
108
- },
109
- },
110
- },
111
- {
112
- type: "function",
113
- function: {
114
- name: "set_config",
115
- description: "Set a configuration value",
116
- parameters: {
117
- type: "object",
118
- properties: {
119
- key: { type: "string", description: "Config key" },
120
- value: { type: "string", description: "Value to set" },
121
- },
122
- required: ["key", "value"],
123
- },
124
- },
125
- },
126
- ];
48
+ function: { name: t.name, description: t.description, parameters: t.parameters },
49
+ }));
50
+ return [...localDefs, ...cloudDefs];
51
+ }
127
52
 
128
- async function executeTool(name, args) {
53
+ // Execute a cloud platform tool via the API
54
+ async function executeCloudTool(name, args) {
129
55
  try {
130
56
  switch (name) {
131
- case "get_account_info":
132
- return await apiClient.getMe();
133
- case "list_workspaces":
134
- return await apiClient.listWorkspaces();
135
- case "create_workspace":
136
- return await apiClient.createWorkspace(args.repo);
137
- case "stop_workspace":
138
- return await apiClient.deleteWorkspace(args.id);
139
- case "get_workspace_status":
140
- return await apiClient.getWorkspaceStatus(args.id);
141
- case "get_memory":
142
- return await apiClient.getMemory();
143
- case "add_memory":
144
- return await apiClient.addMemory(args.content, args.category);
145
- case "clear_memory":
146
- return await apiClient.clearMemory();
147
- case "get_usage":
148
- return await apiClient.getUsage();
57
+ case "get_account_info": return await apiClient.getMe();
58
+ case "list_workspaces": return await apiClient.listWorkspaces();
59
+ case "create_workspace": return await apiClient.createWorkspace(args.repo);
60
+ case "stop_workspace": return await apiClient.deleteWorkspace(args.id);
61
+ case "get_workspace_status": return await apiClient.getWorkspaceStatus(args.id);
62
+ case "get_memory": return await apiClient.getMemory();
63
+ case "add_memory": return await apiClient.addMemory(args.content, args.category);
64
+ case "clear_memory": return await apiClient.clearMemory();
65
+ case "get_usage": return await apiClient.getUsage();
149
66
  case "get_config": {
150
67
  const { getConfigValue } = await import("../lib/config-store.js");
151
68
  return { key: args.key, value: getConfigValue(args.key) };
@@ -155,14 +72,42 @@ async function executeTool(name, args) {
155
72
  setConfigValue(args.key, args.value);
156
73
  return { key: args.key, value: args.value, status: "updated" };
157
74
  }
158
- default:
159
- return { error: `Unknown tool: ${name}` };
75
+ default: return { error: `Unknown cloud tool: ${name}` };
160
76
  }
161
77
  } catch (err) {
162
78
  return { error: err.message };
163
79
  }
164
80
  }
165
81
 
82
+ // Ask user for confirmation before destructive operations
83
+ async function confirmAction(toolName, input) {
84
+ const desc = toolName === "run_command" ? input.command : JSON.stringify(input);
85
+ return new Promise((resolve) => {
86
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
87
+ rl.question(chalk.yellow(` Allow ${chalk.cyan(toolName)}: ${chalk.dim(desc)}? [Y/n] `), (answer) => {
88
+ rl.close();
89
+ resolve(answer.trim().toLowerCase() !== "n");
90
+ });
91
+ });
92
+ }
93
+
94
+ // Unified tool executor
95
+ async function executeTool(name, args) {
96
+ if (CLOUD_TOOL_NAMES.has(name)) {
97
+ return executeCloudTool(name, args);
98
+ }
99
+
100
+ // Local tool — check if it needs confirmation
101
+ if (requiresConfirmation(name)) {
102
+ const allowed = await confirmAction(name, args);
103
+ if (!allowed) {
104
+ return { error: "User denied execution" };
105
+ }
106
+ }
107
+
108
+ return executeLocalTool(name, args);
109
+ }
110
+
166
111
  // Parse SSE stream from the cloud proxy
167
112
  async function* parseSSEStream(response) {
168
113
  const reader = response.body.getReader();
@@ -194,15 +139,17 @@ async function* parseSSEStream(response) {
194
139
  }
195
140
 
196
141
  export async function runAgent(userRequest) {
142
+ const ALL_TOOLS = buildTools();
143
+
197
144
  const messages = [
198
145
  { role: "system", content: SYSTEM_PROMPT },
199
146
  { role: "user", content: userRequest },
200
147
  ];
201
148
 
202
- const MAX_ITERATIONS = 10;
149
+ const MAX_ITERATIONS = 15;
203
150
 
204
151
  for (let i = 0; i < MAX_ITERATIONS; i++) {
205
- const response = await apiClient.chatStream(messages, TOOLS);
152
+ const response = await apiClient.chatStream(messages, ALL_TOOLS);
206
153
 
207
154
  let fullText = "";
208
155
  const toolCalls = {};
@@ -255,7 +202,9 @@ export async function runAgent(userRequest) {
255
202
  let input = {};
256
203
  try { input = JSON.parse(tc.arguments); } catch {}
257
204
 
258
- log.info(`Calling ${chalk.cyan(tc.name)}${Object.keys(input).length ? chalk.dim(" " + JSON.stringify(input)) : ""}`);
205
+ const isLocal = !CLOUD_TOOL_NAMES.has(tc.name);
206
+ const prefix = isLocal ? chalk.magenta("local") : chalk.blue("cloud");
207
+ log.info(`${prefix} ${chalk.cyan(tc.name)}${Object.keys(input).length ? chalk.dim(" " + truncate(JSON.stringify(input), 80)) : ""}`);
259
208
 
260
209
  const result = await executeTool(tc.name, input);
261
210
 
@@ -265,22 +214,30 @@ export async function runAgent(userRequest) {
265
214
  log.success(tc.name);
266
215
  }
267
216
 
217
+ // Truncate large tool results to avoid blowing context
218
+ const resultStr = JSON.stringify(result);
219
+ const truncatedResult = resultStr.length > 8000 ? resultStr.slice(0, 8000) + "...(truncated)" : resultStr;
220
+
268
221
  messages.push({
269
222
  role: "tool",
270
223
  tool_call_id: tc.id,
271
- content: JSON.stringify(result),
224
+ content: truncatedResult,
272
225
  });
273
226
  }
274
227
  }
275
228
 
276
- log.warn("Agent reached maximum iterations (10). Stopping.");
229
+ log.warn("Agent reached maximum iterations (15). Stopping.");
230
+ }
231
+
232
+ function truncate(str, max) {
233
+ return str.length > max ? str.slice(0, max) + "..." : str;
277
234
  }
278
235
 
279
236
  export function registerAgentCommand(program) {
280
237
  program
281
238
  .command("agent")
282
239
  .argument("<request...>")
283
- .description("AI agent — natural language command interface")
240
+ .description("AI coding agent — natural language command interface")
284
241
  .action(async (requestParts) => {
285
242
  const request = requestParts.join(" ");
286
243
  try {
@@ -13,12 +13,10 @@ export async function loginFlow() {
13
13
 
14
14
  log.info("Step 1: Open this URL in your browser to authenticate:");
15
15
  log.blank();
16
- console.log(` ${chalk.cyan.underline(`${apiUrl}/auth/github`)}`);
16
+ console.log(` ${chalk.cyan.underline(`${apiUrl}/auth/github?source=cli`)}`);
17
17
  log.blank();
18
- log.info("Step 2: After login, visit the token page:");
19
- console.log(` ${chalk.cyan.underline(`${apiUrl}/auth/token?user=YOUR_USERNAME`)}`);
20
- log.blank();
21
- log.info("Step 3: Copy your API token and paste it below.");
18
+ log.info("Step 2: After GitHub login, your token will be shown automatically.");
19
+ log.info(" Copy it and paste it below.");
22
20
  log.blank();
23
21
 
24
22
  const { token } = await inquirer.prompt([
@@ -0,0 +1,34 @@
1
+ import { execSync } from "child_process";
2
+
3
+ export const runCommand = {
4
+ name: "run_command",
5
+ description: "Execute a shell command and return its output. Use for running builds, tests, git commands, installing packages, etc.",
6
+ parameters: {
7
+ type: "object",
8
+ properties: {
9
+ command: { type: "string", description: "The shell command to execute" },
10
+ cwd: { type: "string", description: "Working directory (optional, defaults to current directory)" },
11
+ },
12
+ required: ["command"],
13
+ },
14
+ requiresConfirmation: true,
15
+ handler: async ({ command, cwd }) => {
16
+ try {
17
+ const output = execSync(command, {
18
+ cwd: cwd || process.cwd(),
19
+ encoding: "utf-8",
20
+ timeout: 120_000,
21
+ maxBuffer: 1024 * 1024 * 10,
22
+ stdio: ["pipe", "pipe", "pipe"],
23
+ });
24
+ return { command, exitCode: 0, stdout: output, stderr: "" };
25
+ } catch (err) {
26
+ return {
27
+ command,
28
+ exitCode: err.status ?? 1,
29
+ stdout: err.stdout || "",
30
+ stderr: err.stderr || err.message,
31
+ };
32
+ }
33
+ },
34
+ };
@@ -0,0 +1,72 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
2
+ import { dirname, resolve } from "path";
3
+
4
+ export const readFile = {
5
+ name: "read_file",
6
+ description: "Read the contents of a file at the given path. Returns the file contents as a string.",
7
+ parameters: {
8
+ type: "object",
9
+ properties: {
10
+ path: { type: "string", description: "Absolute or relative file path to read" },
11
+ },
12
+ required: ["path"],
13
+ },
14
+ handler: async ({ path }) => {
15
+ const resolved = resolve(path);
16
+ if (!existsSync(resolved)) {
17
+ return { error: `File not found: ${resolved}` };
18
+ }
19
+ const content = readFileSync(resolved, "utf-8");
20
+ return { path: resolved, content, lines: content.split("\n").length };
21
+ },
22
+ };
23
+
24
+ export const writeFile = {
25
+ name: "write_file",
26
+ description: "Create or overwrite a file with the given content. Creates parent directories if needed.",
27
+ parameters: {
28
+ type: "object",
29
+ properties: {
30
+ path: { type: "string", description: "Absolute or relative file path to write" },
31
+ content: { type: "string", description: "The content to write to the file" },
32
+ },
33
+ required: ["path", "content"],
34
+ },
35
+ handler: async ({ path, content }) => {
36
+ const resolved = resolve(path);
37
+ const dir = dirname(resolved);
38
+ if (!existsSync(dir)) {
39
+ mkdirSync(dir, { recursive: true });
40
+ }
41
+ writeFileSync(resolved, content, "utf-8");
42
+ return { path: resolved, bytesWritten: Buffer.byteLength(content, "utf-8") };
43
+ },
44
+ };
45
+
46
+ export const editFile = {
47
+ name: "edit_file",
48
+ description: "Edit a file by replacing an exact string match with new content. The old_string must match exactly (including whitespace and indentation).",
49
+ parameters: {
50
+ type: "object",
51
+ properties: {
52
+ path: { type: "string", description: "Absolute or relative file path to edit" },
53
+ old_string: { type: "string", description: "The exact string to find and replace" },
54
+ new_string: { type: "string", description: "The replacement string" },
55
+ },
56
+ required: ["path", "old_string", "new_string"],
57
+ },
58
+ handler: async ({ path, old_string, new_string }) => {
59
+ const resolved = resolve(path);
60
+ if (!existsSync(resolved)) {
61
+ return { error: `File not found: ${resolved}` };
62
+ }
63
+ const content = readFileSync(resolved, "utf-8");
64
+ if (!content.includes(old_string)) {
65
+ return { error: "old_string not found in file. Make sure it matches exactly, including whitespace." };
66
+ }
67
+ const count = content.split(old_string).length - 1;
68
+ const newContent = content.replace(old_string, new_string);
69
+ writeFileSync(resolved, newContent, "utf-8");
70
+ return { path: resolved, replacements: 1, totalOccurrences: count };
71
+ },
72
+ };
@@ -0,0 +1,32 @@
1
+ import { readFile, writeFile, editFile } from "./file-tools.js";
2
+ import { runCommand } from "./command-tools.js";
3
+ import { searchFiles, searchContent, listDirectory } from "./search-tools.js";
4
+
5
+ const tools = [readFile, writeFile, editFile, runCommand, searchFiles, searchContent, listDirectory];
6
+
7
+ const toolMap = new Map(tools.map((t) => [t.name, t]));
8
+
9
+ export function getToolDefinitions() {
10
+ return tools.map(({ name, description, parameters }) => ({ name, description, parameters }));
11
+ }
12
+
13
+ export function getTool(name) {
14
+ return toolMap.get(name);
15
+ }
16
+
17
+ export async function executeTool(name, input) {
18
+ const tool = toolMap.get(name);
19
+ if (!tool) {
20
+ return { error: `Unknown tool: ${name}` };
21
+ }
22
+ try {
23
+ return await tool.handler(input);
24
+ } catch (err) {
25
+ return { error: err.message };
26
+ }
27
+ }
28
+
29
+ export function requiresConfirmation(name) {
30
+ const tool = toolMap.get(name);
31
+ return tool?.requiresConfirmation === true;
32
+ }
@@ -0,0 +1,95 @@
1
+ import { readdirSync, statSync, existsSync } from "fs";
2
+ import { resolve, join } from "path";
3
+ import { execSync } from "child_process";
4
+
5
+ export const searchFiles = {
6
+ name: "search_files",
7
+ description: "Search for files matching a glob/name pattern. Returns matching file paths.",
8
+ parameters: {
9
+ type: "object",
10
+ properties: {
11
+ pattern: { type: "string", description: "File name pattern to search for (e.g., '*.js', 'config*', 'test')" },
12
+ path: { type: "string", description: "Directory to search in (defaults to current directory)" },
13
+ },
14
+ required: ["pattern"],
15
+ },
16
+ handler: async ({ pattern, path }) => {
17
+ const dir = resolve(path || ".");
18
+ try {
19
+ const result = execSync(`find ${JSON.stringify(dir)} -name ${JSON.stringify(pattern)} -not -path '*/node_modules/*' -not -path '*/.git/*' 2>/dev/null | head -50`, {
20
+ encoding: "utf-8",
21
+ timeout: 10_000,
22
+ });
23
+ const files = result.trim().split("\n").filter(Boolean);
24
+ return { pattern, searchPath: dir, matches: files, count: files.length };
25
+ } catch {
26
+ return { pattern, searchPath: dir, matches: [], count: 0 };
27
+ }
28
+ },
29
+ };
30
+
31
+ export const searchContent = {
32
+ name: "search_content",
33
+ description: "Search for a text pattern inside files (like grep). Returns matching lines with file paths and line numbers.",
34
+ parameters: {
35
+ type: "object",
36
+ properties: {
37
+ pattern: { type: "string", description: "Text or regex pattern to search for" },
38
+ path: { type: "string", description: "Directory to search in (defaults to current directory)" },
39
+ include: { type: "string", description: "File glob to filter (e.g., '*.js')" },
40
+ },
41
+ required: ["pattern"],
42
+ },
43
+ handler: async ({ pattern, path, include }) => {
44
+ const dir = resolve(path || ".");
45
+ try {
46
+ let cmd = `grep -rn --include='${include || "*"}' ${JSON.stringify(pattern)} ${JSON.stringify(dir)} 2>/dev/null | head -50`;
47
+ const result = execSync(cmd, { encoding: "utf-8", timeout: 10_000 });
48
+ const lines = result.trim().split("\n").filter(Boolean);
49
+ const matches = lines.map((line) => {
50
+ const match = line.match(/^(.+?):(\d+):(.*)$/);
51
+ if (match) return { file: match[1], line: parseInt(match[2]), content: match[3].trim() };
52
+ return { raw: line };
53
+ });
54
+ return { pattern, searchPath: dir, matches, count: matches.length };
55
+ } catch {
56
+ return { pattern, searchPath: dir, matches: [], count: 0 };
57
+ }
58
+ },
59
+ };
60
+
61
+ export const listDirectory = {
62
+ name: "list_directory",
63
+ description: "List files and directories at the given path. Shows names, types, and sizes.",
64
+ parameters: {
65
+ type: "object",
66
+ properties: {
67
+ path: { type: "string", description: "Directory path to list (defaults to current directory)" },
68
+ },
69
+ required: [],
70
+ },
71
+ handler: async ({ path }) => {
72
+ const dir = resolve(path || ".");
73
+ if (!existsSync(dir)) {
74
+ return { error: `Directory not found: ${dir}` };
75
+ }
76
+ try {
77
+ const entries = readdirSync(dir).map((name) => {
78
+ try {
79
+ const fullPath = join(dir, name);
80
+ const stat = statSync(fullPath);
81
+ return {
82
+ name,
83
+ type: stat.isDirectory() ? "directory" : "file",
84
+ size: stat.isDirectory() ? undefined : stat.size,
85
+ };
86
+ } catch {
87
+ return { name, type: "unknown" };
88
+ }
89
+ });
90
+ return { path: dir, entries, count: entries.length };
91
+ } catch (err) {
92
+ return { error: err.message };
93
+ }
94
+ },
95
+ };
package/src/repl.js CHANGED
@@ -180,7 +180,8 @@ async function dispatch(input) {
180
180
  }
181
181
 
182
182
  default:
183
- log.warn(`Unknown command: '${cmd}'. Type ${chalk.cyan("help")} for available commands.`);
183
+ // Treat any unrecognized input as an agent request
184
+ await runAgent(input);
184
185
  }
185
186
  }
186
187