opencode-ai-cli 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/GEMINI.md +11 -0
  2. package/cli.ts +17 -0
  3. package/commands/agent.ts +102 -0
  4. package/commands/chat.ts +518 -0
  5. package/commands/models.ts +10 -0
  6. package/commands/providers/index.ts +10 -0
  7. package/commands/providers/login.ts +30 -0
  8. package/commands/providers/logout.ts +13 -0
  9. package/commands/providers/setProvider.ts +9 -0
  10. package/package.json +22 -0
  11. package/src/core/auth-storage.ts +136 -0
  12. package/src/core/model-registry.ts +23 -0
  13. package/src/engine/agentLoop.ts +389 -0
  14. package/src/engine/messages.ts +110 -0
  15. package/src/engine/systemPrompt.ts +58 -0
  16. package/src/engine/type.ts +133 -0
  17. package/src/providers/gemini.ts +122 -0
  18. package/src/providers/openai.ts +60 -0
  19. package/src/subagent/README.md +177 -0
  20. package/src/subagent/agents/planner.md +37 -0
  21. package/src/subagent/agents/reviewer.md +35 -0
  22. package/src/subagent/agents/scout.md +49 -0
  23. package/src/subagent/agents/worker.md +29 -0
  24. package/src/subagent/agents.ts +89 -0
  25. package/src/subagent/index.ts +224 -0
  26. package/src/subagent/prompts/implement-and-review.md +10 -0
  27. package/src/subagent/prompts/implement.md +10 -0
  28. package/src/subagent/prompts/scout-and-plan.md +9 -0
  29. package/src/tools/bash-tool.ts +44 -0
  30. package/src/tools/edit-tool.ts +85 -0
  31. package/src/tools/find-tool.ts +81 -0
  32. package/src/tools/grep-tool.ts +100 -0
  33. package/src/tools/index.ts +37 -0
  34. package/src/tools/ls-tool.ts +93 -0
  35. package/src/tools/plan-tool.ts +35 -0
  36. package/src/tools/read-tool.ts +89 -0
  37. package/src/tools/truncate.ts +21 -0
  38. package/src/tools/weather-tool.ts +55 -0
  39. package/src/tools/write-tool.ts +53 -0
  40. package/src/types.ts +28 -0
package/GEMINI.md ADDED
@@ -0,0 +1,11 @@
1
+ # Gemini CLI Instructions
2
+
3
+ ## Safety & Stability
4
+ - **Code Integrity:** When editing files that define system prompts or configurations (e.g., `src/engine/systemPrompt.ts`), always ensure that special characters (like backticks in template literals) are properly escaped to prevent syntax errors and application crashes.
5
+ - **Verification:** After making changes to the core engine or system prompts, verify that the application still compiles and runs.
6
+
7
+ ## Reporting & Transparency
8
+ - **File Mutations:** When using mutating tools (`write_file`, `edit_file`, `bash`), you MUST explicitly describe your action.
9
+ - For `edit_file`, clearly state what is being removed and what is being added, and identify the affected lines.
10
+ - For `write_file`, state that you are creating/overwriting a file at a specific path.
11
+ - For `bash`, state exactly what command you are running and its purpose.
package/cli.ts ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bun
2
+ import { program } from 'commander';
3
+ import { modelsCommand } from './commands/models';
4
+ import { agentCommand } from './commands/agent';
5
+ import { providerCommand } from './commands/providers';
6
+ import { chatCommand } from './commands/chat';
7
+
8
+ program
9
+ .name('tuichat')
10
+ .description('Coding agent cli')
11
+ .version('0.1.0')
12
+ .addCommand(modelsCommand)
13
+ .addCommand(agentCommand)
14
+ .addCommand(providerCommand)
15
+ .addCommand(chatCommand);
16
+
17
+ program.parse();
@@ -0,0 +1,102 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+
4
+ import { getOrchestratorTools } from "../src/tools";
5
+ import { AuthStorage } from "../src/core/auth-storage";
6
+ import { runAgentLoop } from "../src/engine/agentLoop";
7
+ import { buildSystemPrompt } from "../src/engine/systemPrompt";
8
+
9
+ export const agentCommand = new Command("agent")
10
+ .description("Runs the agent")
11
+ .option("-p, --prompt <prompt>", "prompt", "")
12
+ .action(async (options) => {
13
+ const prompt = options.prompt;
14
+ if (!prompt) {
15
+ console.error("Error: Please provide a prompt using the -p flag.");
16
+ process.exit(1);
17
+ }
18
+
19
+ const providers = AuthStorage.getAuthenticatedProviders();
20
+ if (providers.length === 0) {
21
+ console.error(
22
+ "Error: No authenticated providers found. Run 'opencode providers login' or use '/login' in chat.",
23
+ );
24
+ process.exit(1);
25
+ }
26
+
27
+ const activeProvider = providers[0];
28
+ const apiKey = AuthStorage.getApiKey(activeProvider);
29
+
30
+ if (!apiKey) {
31
+ console.error(`Error: API key for ${activeProvider} not found.`);
32
+ process.exit(1);
33
+ }
34
+
35
+ const activeTools = getOrchestratorTools();
36
+
37
+ console.log(`Asking ${activeProvider} (and checking tools)...`);
38
+
39
+ try {
40
+ const results = await runAgentLoop(
41
+ [
42
+ { role: "system", content: buildSystemPrompt() },
43
+ { role: "user", content: prompt },
44
+ ],
45
+ {
46
+ cwd: process.cwd(),
47
+ messages: [],
48
+ activeToolNames: activeTools.map((t) => t.name),
49
+ tools: activeTools,
50
+ },
51
+ {
52
+ provider: activeProvider,
53
+ apiKey: apiKey,
54
+ },
55
+ (event) => {
56
+ if (event.type === "tool_execution_start") {
57
+ const { toolName, args } = event;
58
+ let logMsg = "";
59
+ if (toolName === "edit_file") {
60
+ logMsg = `runing 'edit' on ${chalk.cyan(args.file_path)}`;
61
+ } else if (toolName === "write_file") {
62
+ logMsg = `runing 'write' to ${chalk.cyan(args.file_path)}`;
63
+ } else if (toolName === "bash") {
64
+ logMsg = `runing 'bash' command: ${chalk.yellow(args.command)}`;
65
+ } else if (toolName === "read_file") {
66
+ logMsg = `runing 'read' for ${chalk.cyan(args.file_path)}`;
67
+ } else if (toolName === "grep_search") {
68
+ logMsg = `runing 'grep' for pattern ${chalk.yellow(args.pattern)} in ${chalk.cyan(args.dir_path || ".")}`;
69
+ } else if (toolName === "list_directory") {
70
+ logMsg = `runing 'ls' for ${chalk.cyan(args.dir_path)}`;
71
+ } else if (toolName === "find_files") {
72
+ logMsg = `runing 'find' for pattern ${chalk.yellow(args.pattern)} in ${chalk.cyan(args.dir_path || ".")}`;
73
+ } else {
74
+ logMsg = `runing '${toolName}'...`;
75
+ }
76
+ console.log(chalk.dim(` [agent] system ${logMsg}`));
77
+ }
78
+ if (event.type === "message_end" && event.message.role === "assistant") {
79
+ const textContent = event.message.content
80
+ .filter((p) => p.type === "text")
81
+ .map((p: any) => p.text)
82
+ .join("");
83
+ if (textContent) {
84
+ process.stdout.write(textContent);
85
+ }
86
+ }
87
+ },
88
+ );
89
+
90
+ const lastMessage = results[results.length - 1];
91
+ if (lastMessage && lastMessage.role === "assistant") {
92
+ const text = lastMessage.content
93
+ .filter((p) => p.type === "text")
94
+ .map((p: any) => p.text)
95
+ .join("");
96
+ console.log(`\n\nAgent: ${text}`);
97
+ }
98
+ } catch (error) {
99
+ console.error("Agent Loop Failed:", error);
100
+ process.exit(1);
101
+ }
102
+ });
@@ -0,0 +1,518 @@
1
+ import { Command } from "commander";
2
+
3
+ import { input, password, select, editor } from "@inquirer/prompts";
4
+ import chalk from "chalk";
5
+ import { getOrchestratorTools } from "../src/tools";
6
+ import { AuthStorage, ProviderName } from "../src/core/auth-storage";
7
+ import { runAgentLoop } from "../src/engine/agentLoop";
8
+ import {
9
+ AgentMessage,
10
+ AssistantMessage,
11
+ UserMessage,
12
+ ToolResultMessage,
13
+ } from "../src/engine/type";
14
+ import { buildSystemPrompt } from "../src/engine/systemPrompt";
15
+
16
+ function getPersonalizedTip(
17
+ currentPrompt: string,
18
+ history: AgentMessage[],
19
+ ): string | null {
20
+ if (currentPrompt.length > 500) {
21
+ return "Your prompt is quite long. Being concise can help save input tokens.";
22
+ }
23
+
24
+ if (currentPrompt.length < 15 && currentPrompt.length > 0) {
25
+ return "Providing more clear and specific instructions can avoid multiple turns for clarification, saving tokens.";
26
+ }
27
+
28
+ if (history.length > 20) {
29
+ return "Your chat history is getting long. Clearing it if previous context is no longer needed can significantly reduce input tokens.";
30
+ }
31
+
32
+ const userPrompts = history
33
+ .filter((m): m is UserMessage => m.role === "user")
34
+ .map((m) => m.content.trim().toLowerCase());
35
+
36
+ if (userPrompts.length > 1) {
37
+ const lastPrompt = userPrompts[userPrompts.length - 1];
38
+ const previousPrompts = userPrompts.slice(0, -1);
39
+ if (previousPrompts.some((p) => p === lastPrompt)) {
40
+ return "I noticed you asked something very similar before. Try to be more specific or ask for a summary to avoid redundant information.";
41
+ }
42
+ }
43
+
44
+ const lastTurnToolMessages = history
45
+ .slice(-15)
46
+ .filter((m): m is ToolResultMessage => m.role === "tool");
47
+
48
+ const readActions = lastTurnToolMessages.filter(
49
+ (m) => m.name === "read_file" || m.name === "list_directory",
50
+ );
51
+ const scoutActions = lastTurnToolMessages.filter(
52
+ (m) => m.name === "subagent" || m.name === "find_files",
53
+ );
54
+
55
+ if (readActions.length > 3 && scoutActions.length === 0) {
56
+ return "I noticed several file reads. Using the 'scout' tool first can help focus research and avoid reading large files unnecessarily.";
57
+ }
58
+
59
+ const lastAssistantMessage = history.findLast(
60
+ (m): m is AssistantMessage => m.role === "assistant",
61
+ );
62
+ if (lastAssistantMessage) {
63
+ const textContent = lastAssistantMessage.content
64
+ .filter((p) => p.type === "text")
65
+ .map((p: any) => p.text)
66
+ .join("");
67
+ if (textContent.length > 2000) {
68
+ return "The last response was quite long. You could ask for a summary if you only need the key points, saving tokens in future turns.";
69
+ }
70
+ }
71
+
72
+ return null;
73
+ }
74
+
75
+ function getContextWindow(provider: ProviderName): number {
76
+ if (provider === "openai") {
77
+ return 128000;
78
+ }
79
+ if (provider === "gemini") {
80
+ return 1048576;
81
+ }
82
+ return 0;
83
+ }
84
+
85
+ export const chatCommand = new Command("chat")
86
+ .description("start chat session..")
87
+ .action(async () => {
88
+ console.log(chalk.bold.blue("Welcome to tui \n"));
89
+
90
+ let activeAbortController: AbortController | null = null;
91
+
92
+ let activeProvider = AuthStorage.getAuthenticatedProviders()[0] ?? "openai";
93
+ const chatHistory: AgentMessage[] = [
94
+ { role: "system", content: buildSystemPrompt() },
95
+ ];
96
+
97
+ let totalTokensUsed = 0;
98
+
99
+ if (!AuthStorage.getApiKey(activeProvider)) {
100
+ if (!AuthStorage.isTrialActive()) {
101
+ console.log(
102
+ chalk.yellow(" You are not logged in. Type '/login' to login or try our free Gemini trial!\n"),
103
+ );
104
+ } else if (activeProvider === "gemini") {
105
+ console.log(
106
+ chalk.cyan(" Using Gemini Free Trial (5000 tokens/day). Type '/login' to use your own key.\n"),
107
+ );
108
+ }
109
+ }
110
+
111
+ const activeTools = getOrchestratorTools();
112
+
113
+ while (true) {
114
+ let promptStr: string;
115
+ try {
116
+ promptStr = await input({ message: chalk.bold.green("You:") });
117
+ } catch (error) {
118
+ // Inquirer throws when Ctrl+C is pressed
119
+ process.exit(0);
120
+ }
121
+
122
+ if (promptStr.trim() === "/") {
123
+ try {
124
+ promptStr = await select({
125
+ message: "Choose a command:",
126
+ choices: [
127
+ { name: "login - Login to a provider", value: "/login" },
128
+ { name: "logout - Logout from a provider", value: "/logout" },
129
+ { name: "model - Switch AI provider", value: "/model" },
130
+ { name: "whoami - Show current session info", value: "/whoami" },
131
+ { name: "help - Show all commands", value: "/help" },
132
+ { name: "paste - Paste long text/code", value: "/paste" },
133
+ { name: "exit - Exit the chat", value: "/exit" },
134
+ ],
135
+ });
136
+ } catch (error) {
137
+ process.exit(0);
138
+ }
139
+ }
140
+
141
+ if (promptStr.trim() === "/paste") {
142
+ try {
143
+ promptStr = await editor({
144
+ message:
145
+ "Paste your code/text here (save and close the editor to submit):",
146
+ });
147
+ if (!promptStr.trim()) continue;
148
+ } catch (error) {
149
+ process.exit(0);
150
+ }
151
+ }
152
+
153
+ const promptLower = promptStr.trim().toLowerCase();
154
+
155
+ if (!promptLower) continue;
156
+
157
+ if (promptLower === "/exit" || promptLower === "/quit") {
158
+ console.log(chalk.gray("Exiting..."));
159
+ break;
160
+ }
161
+
162
+ if (promptLower === "/login") {
163
+ try {
164
+ const choices = [
165
+ { name: "OpenAI / gpt", value: "openai" },
166
+ { name: "Gemini", value: "gemini" },
167
+ ];
168
+
169
+ if (!AuthStorage.isTrialActive() && !AuthStorage.hasStoredApiKey("gemini")) {
170
+ choices.push({ name: chalk.cyan("Try Gemini Free (5000 tokens/day)"), value: "trial" as any });
171
+ }
172
+
173
+ const chosenProvider = (await select({
174
+ message: "Which ai provider do you want to login ? ",
175
+ choices: choices,
176
+ })) as ProviderName | "trial";
177
+
178
+ if (chosenProvider === "trial") {
179
+ AuthStorage.setTrialActive(true);
180
+ activeProvider = "gemini";
181
+ console.log(chalk.green("Free trial activated for Gemini! Enjoy 5000 tokens per day.\n"));
182
+ continue;
183
+ }
184
+
185
+ console.log(chalk.dim(`Logging into ${chosenProvider}....`));
186
+ const newKey = await password({
187
+ message: `Enter the ${chosenProvider} API Key:`,
188
+ });
189
+
190
+ if (newKey.trim()) {
191
+ AuthStorage.setApiKey(chosenProvider, newKey.trim());
192
+ activeProvider = chosenProvider;
193
+ console.log(
194
+ chalk.green(`Successfully logged in! ${chosenProvider}\n`),
195
+ );
196
+ } else {
197
+ console.log(chalk.red(" Login cancelled: Key cannot be empty.\n"));
198
+ }
199
+ } catch (error) {
200
+ process.exit(0);
201
+ }
202
+ continue;
203
+ }
204
+
205
+ if (promptLower === "/logout") {
206
+ try {
207
+ const providers = AuthStorage.getAuthenticatedProviders();
208
+ if (providers.length === 0) {
209
+ console.log(chalk.yellow("No providers are currently logged in.\n"));
210
+ } else {
211
+ const chosenProvider = (await select({
212
+ message: "Which provider do you want to logout from?",
213
+ choices: providers.map((p) => ({ name: p, value: p })),
214
+ })) as ProviderName;
215
+
216
+ AuthStorage.deleteApiKey(chosenProvider);
217
+ console.log(chalk.green(`Successfully logged out from ${chosenProvider}.\n`));
218
+
219
+ // Update activeProvider if it was the one we just logged out from
220
+ if (activeProvider === chosenProvider) {
221
+ activeProvider = AuthStorage.getAuthenticatedProviders()[0] ?? "openai";
222
+ }
223
+ }
224
+ } catch (error) {
225
+ process.exit(0);
226
+ }
227
+ continue;
228
+ }
229
+
230
+ if (promptLower === "/model") {
231
+ try {
232
+ const providers = AuthStorage.getAuthenticatedProviders();
233
+ if (providers.length === 0) {
234
+ console.log(chalk.yellow("No authenticated providers available. Please /login first.\n"));
235
+ } else {
236
+ activeProvider = (await select({
237
+ message: "Select active provider:",
238
+ choices: providers.map((p) => ({
239
+ name: p + (p === activeProvider ? " (active)" : ""),
240
+ value: p
241
+ })),
242
+ })) as ProviderName;
243
+ console.log(chalk.green(`Switched to ${activeProvider}.\n`));
244
+ }
245
+ } catch (error) {
246
+ process.exit(0);
247
+ }
248
+ continue;
249
+ }
250
+
251
+ if (promptLower === "/whoami") {
252
+ const providers = AuthStorage.getAuthenticatedProviders();
253
+ console.log(chalk.bold("\n--- Session Info ---"));
254
+ console.log(`Active Provider: ${chalk.cyan(activeProvider)}`);
255
+
256
+ const isTrial = activeProvider === "gemini" && AuthStorage.isTrialActive() && !AuthStorage.hasStoredApiKey("gemini");
257
+ if (isTrial) {
258
+ const remaining = AuthStorage.getTrialRemainingTokens();
259
+ console.log(`Trial Status: ${chalk.green("Active")} (Gemini)`);
260
+ console.log(`Daily Trial Remaining: ${chalk.yellow(remaining)} / 5000 tokens`);
261
+ }
262
+
263
+ console.log(`Authenticated Providers: ${providers.length > 0 ? chalk.green(providers.join(", ")) : chalk.red("None")}`);
264
+ console.log(`Total Session Tokens: ${chalk.yellow(totalTokensUsed)}`);
265
+ console.log("--------------------\n");
266
+ continue;
267
+ }
268
+
269
+ if (promptLower === "/help") {
270
+ console.log(chalk.bold("\n--- Available Commands ---"));
271
+ console.log(`${chalk.cyan("/login")} - Login to an AI provider`);
272
+ console.log(`${chalk.cyan("/logout")} - Logout from an AI provider`);
273
+ console.log(`${chalk.cyan("/model")} - Switch active AI provider`);
274
+ console.log(`${chalk.cyan("/whoami")} - Show current session and auth info`);
275
+ console.log(`${chalk.cyan("/paste")} - Paste multi-line text or code`);
276
+ console.log(`${chalk.cyan("/help")} - Show this help message`);
277
+ console.log(`${chalk.cyan("/exit")} - Exit the chat session`);
278
+ console.log(`${chalk.cyan("/quit")} - Exit the chat session`);
279
+ console.log("--------------------------\n");
280
+ continue;
281
+ }
282
+
283
+ const activeKey = AuthStorage.getApiKey(activeProvider);
284
+ if (!activeKey) {
285
+ console.log(chalk.red("You cannot chat Please type '/login' first \n"));
286
+ continue;
287
+ }
288
+
289
+ if (activeProvider === "gemini" && AuthStorage.isTrialActive() && !AuthStorage.hasStoredApiKey("gemini")) {
290
+ const remaining = AuthStorage.getTrialRemainingTokens();
291
+ if (remaining <= 0) {
292
+ console.log(chalk.red("You have reached your daily free trial limit of 5000 tokens."));
293
+ console.log(chalk.yellow("Please login with your own Gemini API key to continue using Gemini today, or wait until tomorrow.\n"));
294
+ continue;
295
+ }
296
+ }
297
+
298
+ try {
299
+ console.log(chalk.dim("Thinking..."));
300
+
301
+ let isAgentFinished = false;
302
+ let currentPrompts: AgentMessage[] = [
303
+ { role: "user", content: promptStr },
304
+ ];
305
+ while (!isAgentFinished) {
306
+ activeAbortController = new AbortController();
307
+ const results = await runAgentLoop(
308
+ currentPrompts,
309
+ {
310
+ cwd: process.cwd(),
311
+ messages: chatHistory,
312
+ activeToolNames: activeTools.map((t) => t.name),
313
+ tools: activeTools,
314
+ },
315
+ {
316
+ provider: activeProvider,
317
+ apiKey: activeKey,
318
+ },
319
+ (event) => {
320
+ if (event.type === "tool_execution_start") {
321
+ const { toolName, args } = event;
322
+ let logMsg = "";
323
+ if (toolName === "edit_file") {
324
+ logMsg = `runing 'edit' on ${chalk.cyan(args.file_path)}`;
325
+ } else if (toolName === "write_file") {
326
+ logMsg = `runing 'write' to ${chalk.cyan(args.file_path)}`;
327
+ } else if (toolName === "bash") {
328
+ logMsg = `runing 'bash' command: ${chalk.yellow(args.command)}`;
329
+ } else if (toolName === "read_file") {
330
+ logMsg = `runing 'read' for ${chalk.cyan(args.path)}`;
331
+ } else if (toolName === "grep_search") {
332
+ logMsg = `runing 'grep' for pattern ${chalk.yellow(args.pattern)} in ${chalk.cyan(args.dir_path || ".")}`;
333
+ } else if (toolName === "list_directory") {
334
+ logMsg = `runing 'ls' for ${chalk.cyan(args.dir_path)}`;
335
+ } else if (toolName === "find_files") {
336
+ logMsg = `runing 'find' for pattern ${chalk.yellow(args.pattern)} in ${chalk.cyan(args.dir_path || ".")}`;
337
+ } else if (toolName === "read") {
338
+ // Handle alias
339
+ logMsg = `runing 'read' for ${chalk.cyan(args.path)}`;
340
+ } else {
341
+ logMsg = `runing '${toolName}'...`;
342
+ }
343
+ console.log(chalk.dim(` [agent] system ${logMsg}`));
344
+ }
345
+ if (
346
+ event.type === "message_update" &&
347
+ event.message.role === "assistant"
348
+ ) {
349
+ const textPart = event.message.content.find(
350
+ (p) => p.type === "text" && "text" in p,
351
+ );
352
+ if (textPart && "text" in textPart) {
353
+ }
354
+ }
355
+ },
356
+ activeAbortController.signal,
357
+ ).finally(() => {
358
+ activeAbortController = null;
359
+ });
360
+
361
+ chatHistory.push(...results);
362
+
363
+ let turnPromptTokens = 0;
364
+ let turnCompletionTokens = 0;
365
+ let turnTotalTokens = 0;
366
+ let toolUsageTokens = 0;
367
+
368
+ results.forEach((msg) => {
369
+ if (msg.role === "assistant" && msg.usage) {
370
+ turnPromptTokens = msg.usage.promptTokens;
371
+ turnCompletionTokens += msg.usage.completionTokens;
372
+ }
373
+ if (msg.role === "tool" && msg.usage) {
374
+ toolUsageTokens += msg.usage.totalTokens;
375
+ }
376
+ });
377
+ turnTotalTokens =
378
+ turnPromptTokens + turnCompletionTokens + toolUsageTokens;
379
+
380
+ let sessionIncrement = 0;
381
+ results.forEach((msg) => {
382
+ if (msg.role === "assistant" && msg.usage) {
383
+ sessionIncrement += msg.usage.totalTokens;
384
+ }
385
+ if (msg.role === "tool" && msg.usage) {
386
+ sessionIncrement += msg.usage.totalTokens;
387
+ }
388
+ });
389
+ totalTokensUsed += sessionIncrement;
390
+
391
+ if (activeProvider === "gemini" && AuthStorage.isTrialActive() && !AuthStorage.hasStoredApiKey("gemini")) {
392
+ AuthStorage.updateTrialUsage(sessionIncrement);
393
+ }
394
+
395
+ const lastMessage = results[results.length - 1];
396
+
397
+ if (
398
+ lastMessage &&
399
+ lastMessage.role === "assistant" &&
400
+ lastMessage.stopReason === "aborted"
401
+ ) {
402
+ console.log(chalk.yellow("LLM stopped."));
403
+ isAgentFinished = true;
404
+ continue;
405
+ }
406
+
407
+ if (
408
+ lastMessage &&
409
+ lastMessage.role === "tool" &&
410
+ lastMessage.name === "propose_plan"
411
+ ) {
412
+ const planData = JSON.parse(lastMessage.content);
413
+ console.log(`\n${chalk.bold.magenta("Agent Proposed a Plan:")}`);
414
+ planData.steps.forEach((step: string, index: number) => {
415
+ console.log(chalk.magenta(`${index + 1}. ${step}`));
416
+ });
417
+
418
+ let feedback: string;
419
+ try {
420
+ feedback = await input({
421
+ message: chalk.bold.yellow(
422
+ "Do you approve this plan? (Type 'yes' to approve, or type your feedback to change it): ",
423
+ ),
424
+ });
425
+ } catch (error) {
426
+ process.exit(0);
427
+ }
428
+
429
+ if (
430
+ feedback.toLowerCase() === "yes" ||
431
+ feedback.toLowerCase() === "y"
432
+ ) {
433
+ console.log(chalk.green("Plan approved. Executing..."));
434
+ currentPrompts = [
435
+ {
436
+ role: "user",
437
+ content:
438
+ "Plan approved. Please execute the plan exactly as proposed.",
439
+ },
440
+ ];
441
+ } else {
442
+ console.log(
443
+ chalk.yellow("Sending feedback and squashing history..."),
444
+ );
445
+
446
+ // Robust Squashing: Remove the last assistant message and all subsequent tool results
447
+ let lastAssistantIndex = -1;
448
+ for (let i = chatHistory.length - 1; i >= 0; i--) {
449
+ if (chatHistory[i].role === "assistant") {
450
+ lastAssistantIndex = i;
451
+ break;
452
+ }
453
+ }
454
+ if (lastAssistantIndex !== -1) {
455
+ chatHistory.splice(lastAssistantIndex);
456
+ }
457
+
458
+ currentPrompts = [
459
+ {
460
+ role: "user",
461
+ content: `Your previous plan was rejected. The user provided this feedback: "${feedback}". Please propose a new plan.`,
462
+ },
463
+ ];
464
+ }
465
+ } else {
466
+ if (lastMessage && lastMessage.role === "assistant") {
467
+ const text = lastMessage.content
468
+ .filter((p: any) => p.type === "text")
469
+ .map((p: any) => p.text)
470
+ .join("");
471
+ if (text) {
472
+ console.log(
473
+ `\n${chalk.bold.cyan("Agent:")} ${chalk.cyan(text)}\n`,
474
+ );
475
+ }
476
+ }
477
+ isAgentFinished = true;
478
+ }
479
+
480
+ if (turnTotalTokens > 0) {
481
+ const contextWindow = getContextWindow(activeProvider);
482
+ const tokensRemaining = Math.max(
483
+ 0,
484
+ contextWindow - totalTokensUsed,
485
+ );
486
+ const modelName =
487
+ activeProvider === "openai"
488
+ ? "gpt-4o-mini"
489
+ : "gemini-3-flash-preview";
490
+
491
+ let statusLine = `${chalk.bold("Provider:")} ${activeProvider} | ` +
492
+ `${chalk.bold("Model:")} ${modelName} | ` +
493
+ `${chalk.bold("Tokens:")} ${chalk.cyan(turnTotalTokens)} (P:${turnPromptTokens} C:${turnCompletionTokens}) | ` +
494
+ `${chalk.bold("Total Session:")} ${totalTokensUsed}`;
495
+
496
+ if (activeProvider === "gemini" && AuthStorage.isTrialActive() && !AuthStorage.hasStoredApiKey("gemini")) {
497
+ const trialRemaining = AuthStorage.getTrialRemainingTokens();
498
+ statusLine += ` | ${chalk.bold("Trial Day Rem:")} ${chalk.yellow(trialRemaining)}`;
499
+ } else {
500
+ statusLine += ` | ${chalk.bold("Est. Remaining:")} ${tokensRemaining}`;
501
+ }
502
+
503
+ console.log(chalk.dim("─".repeat(process.stdout.columns || 40)));
504
+ console.log(statusLine);
505
+ const personalizedTip = getPersonalizedTip(promptStr, chatHistory);
506
+ if (personalizedTip) {
507
+ console.log(chalk.cyan(`💡 Tip: ${personalizedTip}`));
508
+ }
509
+ console.log(
510
+ chalk.dim("─".repeat(process.stdout.columns || 40)) + "\n",
511
+ );
512
+ }
513
+ }
514
+ } catch (error) {
515
+ console.error(chalk.red("Agent Error:"), error);
516
+ }
517
+ }
518
+ });
@@ -0,0 +1,10 @@
1
+ import { Command } from "commander";
2
+
3
+ export const modelsCommand = new Command("models")
4
+ .description("Returns all the supported models")
5
+ .option("-m, --model <modelName>", "name of the model", "all")
6
+ .action((options) => {
7
+ console.log("Listing models...");
8
+
9
+ console.log(options);
10
+ });
@@ -0,0 +1,10 @@
1
+ import { Command, program } from 'commander';
2
+ import { loginCommand } from './login';
3
+ import { logoutCommand } from './logout';
4
+ import { setProviderCommand } from './setProvider';
5
+
6
+ export const providerCommand = new Command("providers")
7
+ .description("Provider related information")
8
+ .addCommand(loginCommand)
9
+ .addCommand(logoutCommand)
10
+ .addCommand(setProviderCommand)