minovative-mind-cli 1.1.5 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -10
- package/dist/commands/chat.d.ts +20 -0
- package/dist/commands/chat.js +39 -2
- package/dist/help.d.ts +4 -0
- package/dist/help.js +15 -0
- package/dist/services/agent-tools.d.ts +1 -1
- package/dist/services/agent-tools.js +6 -4
- package/dist/services/agent.d.ts +1 -1
- package/dist/services/agent.js +73 -19
- package/dist/services/ai.d.ts +2 -2
- package/dist/services/ai.js +7 -5
- package/dist/services/contextAgent.js +3 -0
- package/dist/services/verificationService.d.ts +2 -2
- package/dist/services/verificationService.js +11 -3
- package/dist/utils/config.d.ts +2 -3
- package/dist/utils/config.js +2 -3
- package/oclif.manifest.json +4 -4
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Minovative Mind CLI (
|
|
1
|
+
# Minovative Mind CLI (w/ Gemini 3.5 Flash & Gemini 3.1 Pro)
|
|
2
2
|
|
|
3
|
-
## Fast & Handy AI agent without the bloat. Avaliable in virtually all terminals.
|
|
3
|
+
## Fast & Handy AI agent without the bloat. Avaliable in virtually all terminals. [(Click Demo)](https://youtu.be/KjE5nbKEf3w?si=mcjb5_2QLf4vofRC)
|
|
4
4
|
|
|
5
5
|
An automated AI agent powered by Vertex AI that helps you write software right inside your terminal.
|
|
6
6
|
|
|
@@ -8,21 +8,27 @@ An automated AI agent powered by Vertex AI that helps you write software right i
|
|
|
8
8
|
[](https://npmjs.org/package/minovative-mind-cli)
|
|
9
9
|
[](https://npmjs.org/package/minovative-mind-cli)
|
|
10
10
|
|
|
11
|
-
Official Website: [https://www.minovativemind.dev/](https://www.minovativemind.dev/)
|
|
12
|
-
Latest Updates: [https://www.minovativemind.dev/updates](https://www.minovativemind.dev/updates)
|
|
11
|
+
- Official Website: [https://www.minovativemind.dev/](https://www.minovativemind.dev/)
|
|
12
|
+
- Latest Updates: [https://www.minovativemind.dev/updates](https://www.minovativemind.dev/updates)
|
|
13
13
|
|
|
14
14
|
## Supported Models
|
|
15
15
|
|
|
16
16
|
The CLI supports the following advanced reasoning and coding models via Vertex AI Model Garden. You can hot-swap between these models at any time during a chat session by typing `/models` in the CLI:
|
|
17
17
|
|
|
18
|
-
- **Gemini 3.5 Flash** (Default): Lightning fast, top-tier performance for everyday coding tasks.
|
|
19
18
|
- **Gemini 3.1 Pro**: Best for highly complex architectural reasoning and large context windows.
|
|
20
|
-
- **Gemini
|
|
19
|
+
- **Gemini 3.5 Flash** (Default): Lightning fast, top-tier performance for everyday coding tasks.
|
|
20
|
+
- **Gemini 3.1 Flash Lite**: Lightning fast, cost-effective performance for everyday coding tasks.
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## Table of Contents
|
|
23
23
|
|
|
24
|
-
- [
|
|
24
|
+
- [Quick Start](#quick-start)
|
|
25
25
|
- [Commands](#commands)
|
|
26
|
+
- [Using the Chat](#using-the-chat)
|
|
27
|
+
- [Writing Prompts](#writing-prompts)
|
|
28
|
+
- [Command Menu (Slash Commands)](#command-menu-slash-commands)
|
|
29
|
+
- [How the Agent Works](#how-the-agent-works)
|
|
30
|
+
- [Chained Messages & Interrupts](#chained-messages--interrupts)
|
|
31
|
+
- [Stopping the Agent](#stopping-the-agent)
|
|
26
32
|
|
|
27
33
|
<!-- tocstop -->
|
|
28
34
|
|
|
@@ -62,6 +68,8 @@ Getting started with Minovative Mind CLI is easy!
|
|
|
62
68
|
npm update -g minovative-mind-cli
|
|
63
69
|
```
|
|
64
70
|
|
|
71
|
+
---
|
|
72
|
+
|
|
65
73
|
# Commands
|
|
66
74
|
|
|
67
75
|
<!-- commands -->
|
|
@@ -81,10 +89,24 @@ USAGE
|
|
|
81
89
|
DESCRIPTION
|
|
82
90
|
Start an interactive AI coding agent session powered by Vertex AI.
|
|
83
91
|
|
|
92
|
+
Inside the chat session, you can use the following commands in the slash menu:
|
|
93
|
+
/models - Select the active model
|
|
94
|
+
/paste - Enter multi-line paste mode for long snippets
|
|
95
|
+
/clear - Clear conversation history
|
|
96
|
+
/debug - Debug tests or command execution in a sandbox loop
|
|
97
|
+
/auto-approve - Toggle automatic approval of tool/command runs
|
|
98
|
+
/revert - Revert the last file modification made by the agent
|
|
99
|
+
/commit - Commit current workspace changes to Git
|
|
100
|
+
|
|
101
|
+
Chat Controls:
|
|
102
|
+
- Multi-line Input: End a line with \ to continue on the next line
|
|
103
|
+
- Stop/Abort: Type "stop" to immediately interrupt agent generation
|
|
104
|
+
- Exit Session: Type "exit" or "quit" to end the agent session
|
|
105
|
+
|
|
84
106
|
EXAMPLES
|
|
85
|
-
$ minovative-mind-cli
|
|
107
|
+
$ minovative-mind-cli chat
|
|
86
108
|
|
|
87
|
-
$ minovative-mind-cli --help
|
|
109
|
+
$ minovative-mind-cli chat --help
|
|
88
110
|
```
|
|
89
111
|
|
|
90
112
|
## `minovative-mind-cli help [COMMAND]`
|
package/dist/commands/chat.d.ts
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
2
|
import { Command } from '@oclif/core';
|
|
3
|
+
/**
|
|
4
|
+
* @class DefaultCommand
|
|
5
|
+
* @extends Command
|
|
6
|
+
* @description The entry point for the "chat" CLI command. Manages user authentication,
|
|
7
|
+
* environment initialization, and initializes the interactive agent loop session.
|
|
8
|
+
*/
|
|
3
9
|
export default class DefaultCommand extends Command {
|
|
10
|
+
/**
|
|
11
|
+
* The description displayed in the CLI help output.
|
|
12
|
+
* Details the available slash commands, chat controls, and functionality.
|
|
13
|
+
*/
|
|
4
14
|
static description: string;
|
|
15
|
+
/**
|
|
16
|
+
* Example usages for the chat command shown in help output.
|
|
17
|
+
*/
|
|
5
18
|
static examples: string[];
|
|
19
|
+
/**
|
|
20
|
+
* The main execution logic for the chat command.
|
|
21
|
+
* - Initializes update notifications.
|
|
22
|
+
* - Validates user authentication via Firebase/GitHub.
|
|
23
|
+
* - Sets up the workspace environment.
|
|
24
|
+
* - Launches the main agent loop.
|
|
25
|
+
*/
|
|
6
26
|
run(): Promise<void>;
|
|
7
27
|
}
|
package/dist/commands/chat.js
CHANGED
|
@@ -7,9 +7,46 @@ import pc from 'picocolors';
|
|
|
7
7
|
import { startAgentLoop } from '../services/agent.js';
|
|
8
8
|
import { getAuthorizedIdToken, login } from '../services/auth.js';
|
|
9
9
|
import { updateWorkspaceStatus } from '../services/workspace.js';
|
|
10
|
+
/**
|
|
11
|
+
* @class DefaultCommand
|
|
12
|
+
* @extends Command
|
|
13
|
+
* @description The entry point for the "chat" CLI command. Manages user authentication,
|
|
14
|
+
* environment initialization, and initializes the interactive agent loop session.
|
|
15
|
+
*/
|
|
10
16
|
export default class DefaultCommand extends Command {
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The description displayed in the CLI help output.
|
|
19
|
+
* Details the available slash commands, chat controls, and functionality.
|
|
20
|
+
*/
|
|
21
|
+
static description = `Start an interactive AI coding agent session powered by Vertex AI.
|
|
22
|
+
|
|
23
|
+
Inside the chat session, you can use the following commands in the slash menu:
|
|
24
|
+
/models - Select the active model
|
|
25
|
+
/paste - Enter multi-line paste mode for long snippets
|
|
26
|
+
/clear - Clear conversation history
|
|
27
|
+
/debug - Debug tests or command execution in a sandbox loop
|
|
28
|
+
/auto-approve - Toggle automatic approval of tool/command runs
|
|
29
|
+
/revert - Revert the last file modification made by the agent
|
|
30
|
+
/commit - Commit current workspace changes to Git
|
|
31
|
+
|
|
32
|
+
Chat Controls:
|
|
33
|
+
- Multi-line Input: End a line with \\ to continue on the next line
|
|
34
|
+
- Stop/Abort: Type "stop" to immediately interrupt agent generation
|
|
35
|
+
- Exit Session: Type "exit" or "quit" to end the agent session`;
|
|
36
|
+
/**
|
|
37
|
+
* Example usages for the chat command shown in help output.
|
|
38
|
+
*/
|
|
39
|
+
static examples = [
|
|
40
|
+
'<%= config.bin %> chat',
|
|
41
|
+
'<%= config.bin %> chat --help',
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* The main execution logic for the chat command.
|
|
45
|
+
* - Initializes update notifications.
|
|
46
|
+
* - Validates user authentication via Firebase/GitHub.
|
|
47
|
+
* - Sets up the workspace environment.
|
|
48
|
+
* - Launches the main agent loop.
|
|
49
|
+
*/
|
|
13
50
|
async run() {
|
|
14
51
|
const workspaceRoot = process.cwd();
|
|
15
52
|
console.clear();
|
package/dist/help.d.ts
ADDED
package/dist/help.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Help } from '@oclif/core';
|
|
2
|
+
export default class CustomHelp extends Help {
|
|
3
|
+
formatRoot() {
|
|
4
|
+
const original = super.formatRoot();
|
|
5
|
+
const lines = original.split('\n');
|
|
6
|
+
const usageIndex = lines.findIndex((line) => line.includes('USAGE'));
|
|
7
|
+
if (usageIndex !== -1 && lines[usageIndex + 1]) {
|
|
8
|
+
const firstUsageLine = lines[usageIndex + 1];
|
|
9
|
+
const secondUsageLine = firstUsageLine.replace('[COMMAND]', 'help [COMMAND]');
|
|
10
|
+
lines.splice(usageIndex + 2, 0, secondUsageLine);
|
|
11
|
+
return lines.join('\n');
|
|
12
|
+
}
|
|
13
|
+
return original;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -25,7 +25,7 @@ export declare function modifyFile(workspaceRoot: string, filePath: string, edit
|
|
|
25
25
|
}>): Promise<ToolResult>;
|
|
26
26
|
export declare function listDirectory(workspaceRoot: string, dirPath: string, maxDepth?: number): Promise<ToolResult>;
|
|
27
27
|
export declare function runCommand(workspaceRoot: string, command: string, abortSignal?: AbortSignal): Promise<ToolResult>;
|
|
28
|
-
export declare function grepSearch(workspaceRoot: string, pattern: string, fileGlob?: string): Promise<ToolResult>;
|
|
28
|
+
export declare function grepSearch(workspaceRoot: string, pattern: string, fileGlob?: string, abortSignal?: AbortSignal): Promise<ToolResult>;
|
|
29
29
|
export declare function traceDependencies(workspaceRoot: string, filePath: string, direction?: string, maxDepth?: number): Promise<ToolResult>;
|
|
30
30
|
export declare function findRecentChanges(workspaceRoot: string, dirPath?: string, minutes?: number, maxDepth?: number): Promise<ToolResult>;
|
|
31
31
|
export declare function runDebugScript(workspaceRoot: string, language: string, code: string, abortSignal?: AbortSignal): Promise<ToolResult>;
|
|
@@ -541,7 +541,7 @@ export async function runCommand(workspaceRoot, command, abortSignal) {
|
|
|
541
541
|
return { output: '', error: `Command failed: ${truncatedMsg}` };
|
|
542
542
|
}
|
|
543
543
|
}
|
|
544
|
-
export async function grepSearch(workspaceRoot, pattern, fileGlob) {
|
|
544
|
+
export async function grepSearch(workspaceRoot, pattern, fileGlob, abortSignal) {
|
|
545
545
|
try {
|
|
546
546
|
const { ignoredDirs } = await getIgnoredPaths(workspaceRoot);
|
|
547
547
|
const excludeDirArgs = Array.from(ignoredDirs)
|
|
@@ -559,6 +559,7 @@ export async function grepSearch(workspaceRoot, pattern, fileGlob) {
|
|
|
559
559
|
cwd: workspaceRoot,
|
|
560
560
|
timeout: 15_000,
|
|
561
561
|
maxBuffer: 1024 * 1024 * 2,
|
|
562
|
+
signal: abortSignal,
|
|
562
563
|
});
|
|
563
564
|
// Limit output to 50 results
|
|
564
565
|
const lines = stdout.trim().split('\n');
|
|
@@ -570,11 +571,12 @@ export async function grepSearch(workspaceRoot, pattern, fileGlob) {
|
|
|
570
571
|
return { output: wrappedResult };
|
|
571
572
|
}
|
|
572
573
|
catch (err) {
|
|
573
|
-
// grep returns exit code 1 when no matches are found.
|
|
574
|
-
// Node's execException returns the code as a number.
|
|
575
574
|
if (err instanceof Error && 'code' in err && err.code === 1) {
|
|
576
575
|
return { output: `No matches found for "${pattern}".` };
|
|
577
576
|
}
|
|
577
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
578
|
+
return { output: '', error: 'Grep search aborted by user.' };
|
|
579
|
+
}
|
|
578
580
|
const message = err instanceof Error ? err.message : String(err);
|
|
579
581
|
// Also handle stringified exit codes just in case
|
|
580
582
|
if (message.includes('Command failed') &&
|
|
@@ -768,7 +770,7 @@ export async function executeTool(workspaceRoot, toolName, args, abortSignal) {
|
|
|
768
770
|
case 'run_debug_script':
|
|
769
771
|
return runDebugScript(workspaceRoot, args.language, args.code, abortSignal);
|
|
770
772
|
case 'grep_search':
|
|
771
|
-
return grepSearch(workspaceRoot, args.pattern, args.fileGlob);
|
|
773
|
+
return grepSearch(workspaceRoot, args.pattern, args.fileGlob, abortSignal);
|
|
772
774
|
case 'find_dependencies':
|
|
773
775
|
return traceDependencies(workspaceRoot, args.filePath, args.direction, args.maxDepth);
|
|
774
776
|
case 'find_recent_changes':
|
package/dist/services/agent.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ import * as p from '@clack/prompts';
|
|
|
25
25
|
*
|
|
26
26
|
* 1. **Pause execution** at any time by pressing a key.
|
|
27
27
|
* 2. **Queue feedback** ("chained messages") without waiting for the entire run to finish.
|
|
28
|
-
* 3. **Force abort** the current model or tool run by entering "stop"
|
|
28
|
+
* 3. **Force abort** the current model or tool run by entering "stop".
|
|
29
29
|
*
|
|
30
30
|
* ### Raw Mode & Terminal States
|
|
31
31
|
* In normal CLI execution, Node.js waits for a line feed (Enter) before emitting input.
|
package/dist/services/agent.js
CHANGED
|
@@ -32,7 +32,7 @@ const execAsync = promisify(exec);
|
|
|
32
32
|
*
|
|
33
33
|
* 1. **Pause execution** at any time by pressing a key.
|
|
34
34
|
* 2. **Queue feedback** ("chained messages") without waiting for the entire run to finish.
|
|
35
|
-
* 3. **Force abort** the current model or tool run by entering "stop"
|
|
35
|
+
* 3. **Force abort** the current model or tool run by entering "stop".
|
|
36
36
|
*
|
|
37
37
|
* ### Raw Mode & Terminal States
|
|
38
38
|
* In normal CLI execution, Node.js waits for a line feed (Enter) before emitting input.
|
|
@@ -111,8 +111,9 @@ export class AsyncInputHandler {
|
|
|
111
111
|
}
|
|
112
112
|
// Hide the background spinner before prompt output to avoid corrupting terminal lines
|
|
113
113
|
if (this.spinner) {
|
|
114
|
-
this.spinner.stop(
|
|
114
|
+
this.spinner.stop();
|
|
115
115
|
}
|
|
116
|
+
p.log.step(pc.cyan('Paused to receive input'));
|
|
116
117
|
// Capture the user feedback. The character typed to trigger this event is passed
|
|
117
118
|
// as the initial value of the prompt to avoid losing the first keystroke.
|
|
118
119
|
const userInput = await p.text({
|
|
@@ -120,12 +121,14 @@ export class AsyncInputHandler {
|
|
|
120
121
|
placeholder: '(Leave blank and press Enter to cancel)',
|
|
121
122
|
initialValue: char,
|
|
122
123
|
});
|
|
124
|
+
let wasAborted = false;
|
|
123
125
|
if (!p.isCancel(userInput) && userInput.trim()) {
|
|
124
126
|
const text = userInput.trim();
|
|
125
|
-
if (text.toLowerCase() === 'stop'
|
|
127
|
+
if (text.toLowerCase() === 'stop') {
|
|
126
128
|
if (this.ac) {
|
|
127
129
|
this.ac.abort();
|
|
128
130
|
p.log.warn(pc.yellow(`Generation aborted by user.`));
|
|
131
|
+
wasAborted = true;
|
|
129
132
|
}
|
|
130
133
|
}
|
|
131
134
|
else {
|
|
@@ -133,8 +136,9 @@ export class AsyncInputHandler {
|
|
|
133
136
|
p.log.info(pc.cyan(`📥 Queued message: "${text}"`));
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
|
-
if (this.spinner) {
|
|
137
|
-
this.spinner.
|
|
139
|
+
if (this.spinner && !wasAborted) {
|
|
140
|
+
const resumeMsg = this.spinner._lastMessage || 'Resuming execution...';
|
|
141
|
+
this.spinner.start(resumeMsg);
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
144
|
catch (err) {
|
|
@@ -145,6 +149,7 @@ export class AsyncInputHandler {
|
|
|
145
149
|
if (process.stdin.isTTY) {
|
|
146
150
|
process.stdin.setRawMode(true);
|
|
147
151
|
}
|
|
152
|
+
process.stdin.resume();
|
|
148
153
|
// Small delay before reattaching listener to avoid capturing duplicate keypress frames
|
|
149
154
|
setTimeout(() => {
|
|
150
155
|
if (!this.stopped) {
|
|
@@ -168,6 +173,26 @@ export class AsyncInputHandler {
|
|
|
168
173
|
this.stopped = false;
|
|
169
174
|
if (spinner) {
|
|
170
175
|
this.spinner = spinner;
|
|
176
|
+
// Monkey-patch to track the latest message for un-pausing
|
|
177
|
+
if (!spinner._isPatched) {
|
|
178
|
+
;
|
|
179
|
+
spinner._isPatched = true;
|
|
180
|
+
spinner._lastMessage = 'Executing...';
|
|
181
|
+
const originalMessage = spinner.message.bind(spinner);
|
|
182
|
+
spinner.message = (msg) => {
|
|
183
|
+
if (msg) {
|
|
184
|
+
spinner._lastMessage = msg;
|
|
185
|
+
}
|
|
186
|
+
originalMessage(msg);
|
|
187
|
+
};
|
|
188
|
+
const originalStart = spinner.start.bind(spinner);
|
|
189
|
+
spinner.start = (msg) => {
|
|
190
|
+
if (msg) {
|
|
191
|
+
spinner._lastMessage = msg;
|
|
192
|
+
}
|
|
193
|
+
originalStart(msg);
|
|
194
|
+
};
|
|
195
|
+
}
|
|
171
196
|
}
|
|
172
197
|
if (process.stdin.isTTY) {
|
|
173
198
|
this.originalRawMode = process.stdin.isRaw;
|
|
@@ -447,6 +472,7 @@ async function processResponse(chat, result, workspaceRoot, inputHandler, agentS
|
|
|
447
472
|
// Process each tool call requested by the model
|
|
448
473
|
const toolResponses = [];
|
|
449
474
|
for (const fc of functionCalls) {
|
|
475
|
+
await inputHandler.waitForPrompt();
|
|
450
476
|
if (abortSignal.aborted) {
|
|
451
477
|
p.log.warn(pc.yellow('Execution aborted by user.'));
|
|
452
478
|
return '[Generation stopped by user]';
|
|
@@ -476,6 +502,7 @@ async function processResponse(chat, result, workspaceRoot, inputHandler, agentS
|
|
|
476
502
|
}
|
|
477
503
|
// Execute the underlying filesystem, shell or search tool logic
|
|
478
504
|
const toolResult = await executeTool(workspaceRoot, toolName, toolArgs, abortSignal);
|
|
505
|
+
await inputHandler.waitForPrompt();
|
|
479
506
|
if (toolResult.error) {
|
|
480
507
|
debugLog(`Raw Tool Error for ${toolName}: ${toolResult.error}`);
|
|
481
508
|
// Do not spam the user with expected chunking warnings
|
|
@@ -526,6 +553,7 @@ async function processResponse(chat, result, workspaceRoot, inputHandler, agentS
|
|
|
526
553
|
}
|
|
527
554
|
throw e;
|
|
528
555
|
}
|
|
556
|
+
await inputHandler.waitForPrompt();
|
|
529
557
|
const grounding = followUp.response.groundingMetadata?.();
|
|
530
558
|
if (grounding?.webSearchQueries && grounding.webSearchQueries.length > 0) {
|
|
531
559
|
p.log.info(`${pc.blue('🌐')} ${pc.dim('Google Search Queries:')} ${grounding.webSearchQueries.map((q) => pc.cyan(`"${q}"`)).join(', ')}`);
|
|
@@ -632,9 +660,8 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
632
660
|
}
|
|
633
661
|
else {
|
|
634
662
|
lines.push(line);
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
}
|
|
663
|
+
// Unconditionally log the user's message so it is always visible
|
|
664
|
+
p.log.step(pc.cyan(line));
|
|
638
665
|
break;
|
|
639
666
|
}
|
|
640
667
|
}
|
|
@@ -708,15 +735,26 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
708
735
|
continue;
|
|
709
736
|
}
|
|
710
737
|
if (userInput.toLowerCase() === '/models') {
|
|
738
|
+
const currentModel = chat.getModel();
|
|
711
739
|
const selectedModel = await p.select({
|
|
712
|
-
message:
|
|
740
|
+
message: `Select AI Model (Current: ${pc.cyan(currentModel)})`,
|
|
741
|
+
initialValue: currentModel,
|
|
713
742
|
options: [
|
|
714
|
-
{
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
743
|
+
{
|
|
744
|
+
value: 'gemini-3.1-pro-preview',
|
|
745
|
+
label: 'Gemini 3.1 Pro',
|
|
746
|
+
hint: 'The newest Pro model for complex logic',
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
value: 'gemini-3.5-flash',
|
|
750
|
+
label: 'Gemini 3.5 Flash',
|
|
751
|
+
hint: 'Balanced performance',
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
value: 'gemini-3.1-flash-lite',
|
|
755
|
+
label: 'Gemini 3.1 Flash-Lite',
|
|
756
|
+
hint: 'Ultra-fast and cost-effective',
|
|
757
|
+
},
|
|
720
758
|
],
|
|
721
759
|
});
|
|
722
760
|
if (!p.isCancel(selectedModel)) {
|
|
@@ -786,7 +824,7 @@ ${diffOut}
|
|
|
786
824
|
</diff>`;
|
|
787
825
|
// Use flash-lite for extreme speed and low cost for this simple task
|
|
788
826
|
const commitSystemPrompt = 'You are an expert developer. Output only the git commit message, no markdown formatting, no explanations. Follow Conventional Commits format (feat:, fix:, chore:, refactor:, etc.) for the first line, keeping it under 70 characters and using the imperative mood. Then, add a blank line followed by a more descriptive bulleted list explaining the "what" and "why" of the changes based on the diff.';
|
|
789
|
-
const commitAgent = new ProxyChatSession('gemini-
|
|
827
|
+
const commitAgent = new ProxyChatSession('gemini-3.1-flash-lite', commitSystemPrompt, [], {});
|
|
790
828
|
const commitResult = await commitAgent.sendMessage(prompt);
|
|
791
829
|
const commitMsg = commitResult.response.text().trim();
|
|
792
830
|
commitSpinner.message('Committing...');
|
|
@@ -905,14 +943,26 @@ ${diffOut}
|
|
|
905
943
|
break;
|
|
906
944
|
finalText = await processResponse(chat, result, workspaceRoot, inputHandler, agentState, ac.signal);
|
|
907
945
|
debugLog(`processResponse returned finalText (length ${finalText.length}): "${finalText.substring(0, 10)}..."`);
|
|
946
|
+
if (finalText === '[Generation stopped by user]') {
|
|
947
|
+
break;
|
|
948
|
+
}
|
|
908
949
|
if (finalText.startsWith('[The AI repeatedly returned empty responses')) {
|
|
909
950
|
break;
|
|
910
951
|
}
|
|
911
952
|
const currentChanges = changeLogger.getCurrentChangeSet()?.changes || [];
|
|
912
953
|
// If the correction cycle is active but no new changes were registered, the model gave up
|
|
913
954
|
if (correctionAttempts > 0 && currentChanges.length <= previousChangeCount) {
|
|
914
|
-
|
|
915
|
-
|
|
955
|
+
if (correctionAttempts >= MAX_CORRECTIONS) {
|
|
956
|
+
p.log.warn('Max self-correction attempts reached. Leaving remaining errors for manual review.');
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
debugLog('AI failed to modify any files during the correction attempt. Retrying...');
|
|
960
|
+
const forcePrompt = `AUTOMATED SYSTEM CHECK: You did not modify any files. You MUST use your file modification tools to apply a fix for the previously mentioned errors. Do not just explain the issue.`;
|
|
961
|
+
spinner.start('Thinking (Correction)...');
|
|
962
|
+
result = await chat.sendMessage(forcePrompt);
|
|
963
|
+
spinner.stop('');
|
|
964
|
+
correctionAttempts++;
|
|
965
|
+
continue;
|
|
916
966
|
}
|
|
917
967
|
previousChangeCount = currentChanges.length;
|
|
918
968
|
const changedFiles = currentChanges
|
|
@@ -922,7 +972,11 @@ ${diffOut}
|
|
|
922
972
|
break;
|
|
923
973
|
// Stage 3: Static code analysis / verification
|
|
924
974
|
p.log.step('Verifying modified files...');
|
|
925
|
-
const verificationErrors = await verifyChangedFiles(workspaceRoot, changedFiles);
|
|
975
|
+
const verificationErrors = await verifyChangedFiles(workspaceRoot, changedFiles, ac.signal);
|
|
976
|
+
if (verificationErrors === '[Verification Aborted]') {
|
|
977
|
+
p.log.warn(pc.yellow('Verification aborted by user.'));
|
|
978
|
+
break;
|
|
979
|
+
}
|
|
926
980
|
if (!verificationErrors) {
|
|
927
981
|
p.log.success('Verification passed.');
|
|
928
982
|
break;
|
package/dist/services/ai.d.ts
CHANGED
|
@@ -53,13 +53,13 @@ export declare function getPlanExecutionConfig(): {
|
|
|
53
53
|
}[];
|
|
54
54
|
};
|
|
55
55
|
/**
|
|
56
|
-
* Compresses a large string of text using gemini-
|
|
56
|
+
* Compresses a large string of text using gemini-3.1-flash-lite.
|
|
57
57
|
* Used for shrinking context payloads to prevent OOM/choking.
|
|
58
58
|
*/
|
|
59
59
|
export declare function compressTextUsingFlashLite(text: string, instruction?: string): Promise<string>;
|
|
60
60
|
export declare const CONTEXT_AGENT_MODEL: "gemini-3.5-flash";
|
|
61
61
|
export declare function createContextAgentSession(): any;
|
|
62
|
-
export declare const INTENT_ROUTER_MODEL: "gemini-
|
|
62
|
+
export declare const INTENT_ROUTER_MODEL: "gemini-3.1-flash-lite";
|
|
63
63
|
export declare function createIntentRouterSession(): any;
|
|
64
64
|
export declare const WEB_SEARCH_AGENT_MODEL: "gemini-3.5-flash";
|
|
65
65
|
export declare function createWebSearchAgentSession(): any;
|
package/dist/services/ai.js
CHANGED
|
@@ -62,7 +62,7 @@ export class ProxyChatSession {
|
|
|
62
62
|
let formattedHistory = '';
|
|
63
63
|
for (const item of recentItems) {
|
|
64
64
|
const role = item.role === 'user' ? 'User' : 'Assistant';
|
|
65
|
-
const textParts = item.parts.map(p => p.text).filter(Boolean);
|
|
65
|
+
const textParts = item.parts.map((p) => p.text).filter(Boolean);
|
|
66
66
|
if (textParts.length > 0) {
|
|
67
67
|
formattedHistory += `[${role}]: ${textParts.join(' ')}\n`;
|
|
68
68
|
}
|
|
@@ -125,7 +125,9 @@ export class ProxyChatSession {
|
|
|
125
125
|
// Use the exact parts provided by the API (preserves thought_signature)
|
|
126
126
|
modelParts = [...result.parts];
|
|
127
127
|
for (const fc of allFunctionCalls) {
|
|
128
|
-
const existsInParts = modelParts.some(p => p.functionCall &&
|
|
128
|
+
const existsInParts = modelParts.some((p) => p.functionCall &&
|
|
129
|
+
p.functionCall.name === fc.name &&
|
|
130
|
+
JSON.stringify(p.functionCall.args) === JSON.stringify(fc.args));
|
|
129
131
|
if (!existsInParts) {
|
|
130
132
|
modelParts.push({ functionCall: fc });
|
|
131
133
|
}
|
|
@@ -182,7 +184,7 @@ export function getPlanExecutionConfig() {
|
|
|
182
184
|
};
|
|
183
185
|
}
|
|
184
186
|
/**
|
|
185
|
-
* Compresses a large string of text using gemini-
|
|
187
|
+
* Compresses a large string of text using gemini-3.1-flash-lite.
|
|
186
188
|
* Used for shrinking context payloads to prevent OOM/choking.
|
|
187
189
|
*/
|
|
188
190
|
export async function compressTextUsingFlashLite(text, instruction = 'Summarize the following text concisely. Preserve the most critical technical details, function names, and architecture logic. Keep it under 1500 characters.') {
|
|
@@ -193,7 +195,7 @@ export async function compressTextUsingFlashLite(text, instruction = 'Summarize
|
|
|
193
195
|
if (!idToken)
|
|
194
196
|
return text;
|
|
195
197
|
const contents = [{ role: 'user', parts: [{ text }] }];
|
|
196
|
-
const result = await proxyClient.generateFunctionCallViaProxy(idToken, 'gemini-
|
|
198
|
+
const result = await proxyClient.generateFunctionCallViaProxy(idToken, 'gemini-3.1-flash-lite', contents, [], // no tools
|
|
197
199
|
undefined, instruction, { temperature: 0.2 });
|
|
198
200
|
const summary = result.thought || result.parts?.find((p) => p.text)?.text || '';
|
|
199
201
|
return summary ? summary : text;
|
|
@@ -345,7 +347,7 @@ export function createContextAgentSession() {
|
|
|
345
347
|
});
|
|
346
348
|
}
|
|
347
349
|
// ─── Intent Router Service ───────────────────────────────────────────
|
|
348
|
-
export const INTENT_ROUTER_MODEL = GEMINI_MODELS.
|
|
350
|
+
export const INTENT_ROUTER_MODEL = GEMINI_MODELS.FLASH_LITE_3_1;
|
|
349
351
|
export function createIntentRouterSession() {
|
|
350
352
|
return new ProxyChatSession(INTENT_ROUTER_MODEL, INTENT_ROUTER_SYSTEM_INSTRUCTION, [], // no tools
|
|
351
353
|
{ temperature: 0, responseMimeType: 'application/json' });
|
|
@@ -220,6 +220,9 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
220
220
|
let isFinished = false;
|
|
221
221
|
const functionResponses = [];
|
|
222
222
|
for (const call of functionCalls) {
|
|
223
|
+
await inputHandler.waitForPrompt();
|
|
224
|
+
if (abortSignal.aborted)
|
|
225
|
+
break;
|
|
223
226
|
const args = call.args;
|
|
224
227
|
let logMsg = ` [Context Agent] Executing ${call.name}`;
|
|
225
228
|
if (call.name === 'finish_investigation') {
|
|
@@ -5,6 +5,6 @@ export interface VerificationResult {
|
|
|
5
5
|
errors: string[];
|
|
6
6
|
}
|
|
7
7
|
export declare function detectVerificationCommand(workspaceRoot: string): Promise<string | null>;
|
|
8
|
-
export declare function runVerification(workspaceRoot: string): Promise<VerificationResult | null>;
|
|
8
|
+
export declare function runVerification(workspaceRoot: string, abortSignal?: AbortSignal): Promise<VerificationResult | null>;
|
|
9
9
|
export declare function formatVerificationForModel(result: VerificationResult): string;
|
|
10
|
-
export declare function verifyChangedFiles(workspaceRoot: string, filePaths: string[]): Promise<string | null>;
|
|
10
|
+
export declare function verifyChangedFiles(workspaceRoot: string, filePaths: string[], abortSignal?: AbortSignal): Promise<string | null>;
|
|
@@ -35,7 +35,7 @@ export async function detectVerificationCommand(workspaceRoot) {
|
|
|
35
35
|
catch { }
|
|
36
36
|
return null;
|
|
37
37
|
}
|
|
38
|
-
export async function runVerification(workspaceRoot) {
|
|
38
|
+
export async function runVerification(workspaceRoot, abortSignal) {
|
|
39
39
|
const command = await detectVerificationCommand(workspaceRoot);
|
|
40
40
|
if (!command)
|
|
41
41
|
return null;
|
|
@@ -46,6 +46,7 @@ export async function runVerification(workspaceRoot) {
|
|
|
46
46
|
cwd: workspaceRoot,
|
|
47
47
|
timeout: 90_000, // 90s (1 min & 30 secs) - builds can take a while (e.g., Next.js)
|
|
48
48
|
maxBuffer: 1024 * 1024, // 1 MB buffer
|
|
49
|
+
signal: abortSignal,
|
|
49
50
|
});
|
|
50
51
|
if (stdout.trim())
|
|
51
52
|
debugLog(`[Verification Output - ${command}]\n${stdout.trim()}`);
|
|
@@ -59,6 +60,10 @@ export async function runVerification(workspaceRoot) {
|
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
62
|
catch (err) {
|
|
63
|
+
if (err.name === 'AbortError' || err.message?.includes('abort')) {
|
|
64
|
+
debugLog(`[Verification Aborted - ${command}]`);
|
|
65
|
+
return { success: false, command, output: 'Aborted', errors: [], aborted: true };
|
|
66
|
+
}
|
|
62
67
|
const rawStdout = (err.stdout || '').substring(0, MAX_VERIFY_OUTPUT);
|
|
63
68
|
const rawStderr = (err.stderr || '').substring(0, MAX_VERIFY_OUTPUT);
|
|
64
69
|
const output = rawStdout + '\n' + rawStderr;
|
|
@@ -100,13 +105,16 @@ ${result.errors.join('\n')}
|
|
|
100
105
|
|
|
101
106
|
Please fix these errors using the modify_file tool.`;
|
|
102
107
|
}
|
|
103
|
-
export async function verifyChangedFiles(workspaceRoot, filePaths) {
|
|
108
|
+
export async function verifyChangedFiles(workspaceRoot, filePaths, abortSignal) {
|
|
104
109
|
if (filePaths.length === 0)
|
|
105
110
|
return null;
|
|
106
111
|
const errors = [];
|
|
107
112
|
// Project-level build check (e.g., npm run build)
|
|
108
|
-
const buildResult = await runVerification(workspaceRoot);
|
|
113
|
+
const buildResult = await runVerification(workspaceRoot, abortSignal);
|
|
109
114
|
if (buildResult && !buildResult.success) {
|
|
115
|
+
if (buildResult.aborted) {
|
|
116
|
+
return '[Verification Aborted]';
|
|
117
|
+
}
|
|
110
118
|
const buildErrorMsg = `[Build Error: ${buildResult.command}]\n${buildResult.errors.join('\n').substring(0, 5000)}`;
|
|
111
119
|
errors.push(buildErrorMsg);
|
|
112
120
|
}
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -6,8 +6,7 @@ export declare const GITHUB_CLIENT_ID = "Ov23linFYFfjO3JILG7r";
|
|
|
6
6
|
export declare const GEMINI_MODELS: {
|
|
7
7
|
readonly PRO_3_1: "gemini-3.1-pro-preview";
|
|
8
8
|
readonly FLASH_3_5: "gemini-3.5-flash";
|
|
9
|
-
readonly
|
|
10
|
-
readonly FLASH_LATEST: "gemini-2.5-flash";
|
|
9
|
+
readonly FLASH_LITE_3_1: "gemini-3.1-flash-lite";
|
|
11
10
|
};
|
|
12
11
|
export declare const CLAUDE_MODELS: {
|
|
13
12
|
readonly OPUS: "claude-opus-4-6";
|
|
@@ -16,4 +15,4 @@ export declare const CLAUDE_MODELS: {
|
|
|
16
15
|
/** Default Gemini model for the coding agent. */
|
|
17
16
|
export declare const DEFAULT_MODEL: "gemini-3.5-flash";
|
|
18
17
|
/** Maximum tokens the model can output per response. */
|
|
19
|
-
export declare const MAX_OUTPUT_TOKENS =
|
|
18
|
+
export declare const MAX_OUTPUT_TOKENS = 60000;
|
package/dist/utils/config.js
CHANGED
|
@@ -6,8 +6,7 @@ export const GITHUB_CLIENT_ID = 'Ov23linFYFfjO3JILG7r';
|
|
|
6
6
|
export const GEMINI_MODELS = {
|
|
7
7
|
PRO_3_1: 'gemini-3.1-pro-preview',
|
|
8
8
|
FLASH_3_5: 'gemini-3.5-flash',
|
|
9
|
-
|
|
10
|
-
FLASH_LATEST: 'gemini-2.5-flash',
|
|
9
|
+
FLASH_LITE_3_1: 'gemini-3.1-flash-lite',
|
|
11
10
|
};
|
|
12
11
|
export const CLAUDE_MODELS = {
|
|
13
12
|
OPUS: 'claude-opus-4-6',
|
|
@@ -16,4 +15,4 @@ export const CLAUDE_MODELS = {
|
|
|
16
15
|
/** Default Gemini model for the coding agent. */
|
|
17
16
|
export const DEFAULT_MODEL = GEMINI_MODELS.FLASH_3_5;
|
|
18
17
|
/** Maximum tokens the model can output per response. */
|
|
19
|
-
export const MAX_OUTPUT_TOKENS =
|
|
18
|
+
export const MAX_OUTPUT_TOKENS = 60_000;
|
package/oclif.manifest.json
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
"chat": {
|
|
4
4
|
"aliases": [],
|
|
5
5
|
"args": {},
|
|
6
|
-
"description": "Start an interactive AI coding agent session powered by Vertex AI
|
|
6
|
+
"description": "Start an interactive AI coding agent session powered by Vertex AI.\n\nInside the chat session, you can use the following commands in the slash menu:\n /models - Select the active model\n /paste - Enter multi-line paste mode for long snippets\n /clear - Clear conversation history\n /debug - Debug tests or command execution in a sandbox loop\n /auto-approve - Toggle automatic approval of tool/command runs\n /revert - Revert the last file modification made by the agent\n /commit - Commit current workspace changes to Git\n\nChat Controls:\n - Multi-line Input: End a line with \\ to continue on the next line\n - Stop/Abort: Type \"stop\" to immediately interrupt agent generation\n - Exit Session: Type \"exit\" or \"quit\" to end the agent session",
|
|
7
7
|
"examples": [
|
|
8
|
-
"<%= config.bin %>",
|
|
9
|
-
"<%= config.bin %> --help"
|
|
8
|
+
"<%= config.bin %> chat",
|
|
9
|
+
"<%= config.bin %> chat --help"
|
|
10
10
|
],
|
|
11
11
|
"flags": {},
|
|
12
12
|
"hasDynamicHelp": false,
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
]
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
|
-
"version": "1.
|
|
68
|
+
"version": "1.2.0"
|
|
69
69
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minovative-mind-cli",
|
|
3
3
|
"description": "An automated AI agent powered by Vertex AI that helps you write software",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"author": "Daniel Ward",
|
|
6
6
|
"bin": {
|
|
7
7
|
"minovative-mind-cli": "./bin/run.js"
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"dirname": "minovative-mind-cli",
|
|
64
64
|
"default": "chat",
|
|
65
65
|
"commands": "./dist/commands",
|
|
66
|
+
"helpClass": "./dist/help.js",
|
|
66
67
|
"plugins": [
|
|
67
68
|
"@oclif/plugin-help"
|
|
68
69
|
],
|