minovative-mind-cli 1.4.1 → 1.4.3
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 +5 -3
- package/dist/commands/login.d.ts +15 -0
- package/dist/commands/login.js +15 -0
- package/dist/services/agent/commandApproval.d.ts +16 -0
- package/dist/services/agent/commandApproval.js +128 -0
- package/dist/services/agent/inputHandler.d.ts +83 -0
- package/dist/services/agent/inputHandler.js +208 -0
- package/dist/services/agent/slashCommands.d.ts +6 -0
- package/dist/services/agent/slashCommands.js +203 -0
- package/dist/services/agent/toolLoop.d.ts +39 -0
- package/dist/services/agent/toolLoop.js +271 -0
- package/dist/services/agent/types.d.ts +17 -0
- package/dist/services/agent/types.js +1 -0
- package/dist/services/agent.d.ts +2 -97
- package/dist/services/agent.js +194 -951
- package/dist/services/ai.d.ts +2 -2
- package/dist/services/ai.js +2 -4
- package/dist/utils/paste.d.ts +1 -1
- package/dist/utils/paste.js +6 -2
- package/dist/utils/systemPrompts.d.ts +2 -2
- package/dist/utils/systemPrompts.js +4 -4
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ the build/performance metrics are green.
|
|
|
41
41
|
**3. Verify and self-correct**
|
|
42
42
|
|
|
43
43
|
- **Runs native builds & audits:** Automatically executes your actual build commands (`npm run build`, `cargo check`, etc.) and scans for performance flaws on modified files only.
|
|
44
|
-
- **Autonomously debugs:** Reads the exact compiler output and fixes its own errors across up to
|
|
44
|
+
- **Autonomously debugs:** Reads the exact compiler output and fixes its own errors across up to 5 hands-free correction cycles.
|
|
45
45
|
|
|
46
46
|
---
|
|
47
47
|
|
|
@@ -54,6 +54,8 @@ the build/performance metrics are green.
|
|
|
54
54
|
|
|
55
55
|
## Quick Start
|
|
56
56
|
|
|
57
|
+
**Prerequisite:** Ensure you have Node.js installed on your machine (v18.0.0 or higher).
|
|
58
|
+
|
|
57
59
|
```bash
|
|
58
60
|
npm install -g minovative-mind-cli
|
|
59
61
|
```
|
|
@@ -102,8 +104,8 @@ Hot-swap during a session with `/models`:
|
|
|
102
104
|
| `/models` | Hot-swap the active model |
|
|
103
105
|
| `/revert` | Instantly undo all changes from the last turn |
|
|
104
106
|
| `/commit` | Generate a conventional commit message from your diff |
|
|
105
|
-
| `/auto-approve` |
|
|
106
|
-
| `/paste` | Multi-line input mode
|
|
107
|
+
| `/auto-approve` | Toggle skipping confirmation prompts for commands |
|
|
108
|
+
| `/paste` | Multi-line input mode (cancel with Ctrl+C) |
|
|
107
109
|
| `/debug` | Expose internal agent diagnostics |
|
|
108
110
|
| `stop` | Abort generation immediately |
|
|
109
111
|
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
+
/**
|
|
3
|
+
* @class LoginCommand
|
|
4
|
+
* @extends Command
|
|
5
|
+
* @description Command to authenticate the user with their Minovative Mind account.
|
|
6
|
+
* Initiates the GitHub Device Flow and exchanges credentials for a Firebase custom token.
|
|
7
|
+
*/
|
|
2
8
|
export default class LoginCommand extends Command {
|
|
9
|
+
/**
|
|
10
|
+
* Description of the login command shown in the CLI help documentation.
|
|
11
|
+
*/
|
|
3
12
|
static description: string;
|
|
13
|
+
/**
|
|
14
|
+
* Executes the login workflow. Displays the login prompt intro, executes the auth login logic,
|
|
15
|
+
* and handles UI feedback for successful/failed authorization attempts.
|
|
16
|
+
*
|
|
17
|
+
* @returns {Promise<void>}
|
|
18
|
+
*/
|
|
4
19
|
run(): Promise<void>;
|
|
5
20
|
}
|
package/dist/commands/login.js
CHANGED
|
@@ -2,8 +2,23 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
import { login } from '../services/auth.js';
|
|
3
3
|
import * as p from '@clack/prompts';
|
|
4
4
|
import pc from 'picocolors';
|
|
5
|
+
/**
|
|
6
|
+
* @class LoginCommand
|
|
7
|
+
* @extends Command
|
|
8
|
+
* @description Command to authenticate the user with their Minovative Mind account.
|
|
9
|
+
* Initiates the GitHub Device Flow and exchanges credentials for a Firebase custom token.
|
|
10
|
+
*/
|
|
5
11
|
export default class LoginCommand extends Command {
|
|
12
|
+
/**
|
|
13
|
+
* Description of the login command shown in the CLI help documentation.
|
|
14
|
+
*/
|
|
6
15
|
static description = 'Sign in to your Minovative Mind account using GitHub';
|
|
16
|
+
/**
|
|
17
|
+
* Executes the login workflow. Displays the login prompt intro, executes the auth login logic,
|
|
18
|
+
* and handles UI feedback for successful/failed authorization attempts.
|
|
19
|
+
*
|
|
20
|
+
* @returns {Promise<void>}
|
|
21
|
+
*/
|
|
7
22
|
async run() {
|
|
8
23
|
p.intro(`${pc.bgCyan(pc.black(' Minovative Mind '))} - Login`);
|
|
9
24
|
const success = await login();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare function isCommandDestructive(command: string): boolean;
|
|
2
|
+
export declare function isCommandSafe(command: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Executes an interactive approval prompt via `@clack/prompts`.
|
|
5
|
+
*
|
|
6
|
+
* Supports pattern-based approvals and three user approval models:
|
|
7
|
+
* 1. **Ask**: Prompts the user for every shell execution command (except implicitly safe ones).
|
|
8
|
+
* 2. **Skip-Once**: Automatically grants permission to the current command, then resets.
|
|
9
|
+
* 3. **Skip-All / Auto-Approve**: Grants permission to all future terminal commands.
|
|
10
|
+
*
|
|
11
|
+
* Destructive commands will ALWAYS prompt the user, regardless of mode.
|
|
12
|
+
*
|
|
13
|
+
* @param command - The terminal string requested for execution.
|
|
14
|
+
* @returns A promise resolving to `true` if approved, or `false` if denied/cancelled.
|
|
15
|
+
*/
|
|
16
|
+
export declare function requestCommandApproval(command: string): Promise<boolean>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { consumeSkipOnce, getApprovalMode, setApprovalMode } from '../agent-tools.js';
|
|
4
|
+
export function isCommandDestructive(command) {
|
|
5
|
+
const destructivePatterns = [
|
|
6
|
+
// Unix deletion (catches rm -r, rm -rf, rm -fr, rm --recursive, rm --force, anywhere in args)
|
|
7
|
+
/\brm\s+.*?(?:-[a-zA-Z]*[rfR]|--(?:recursive|force))\b/i,
|
|
8
|
+
/\bmv\s+.*\/dev\/null\b/i, // Move to void
|
|
9
|
+
// Windows deletion (catches rd /s, del /f /s)
|
|
10
|
+
/\b(?:rd|rmdir)\s+.*\/[sq]\b/i,
|
|
11
|
+
/\bdel\s+.*\/[fs]\b/i,
|
|
12
|
+
// Remote script execution (catches curl/wget piped to any shell or runtime)
|
|
13
|
+
/\b(?:curl|wget)\b.*?\|\s*(?:sh|bash|zsh|dash|python|node|ruby|perl)\b/i,
|
|
14
|
+
// Git overwriting (catches hard resets, clean -f, force pushes, hard branch deletes)
|
|
15
|
+
/\bgit\s+(?:reset|clean)\s+.*(?:--hard|-f|--force)\b/i,
|
|
16
|
+
/\bgit\s+push\s+.*(?:-f|--force)\b/i,
|
|
17
|
+
/\bgit\s+branch\s+.*-D\b/i,
|
|
18
|
+
// DB & Cluster Drops (catches truncates, deletes, drop db/table, docker volume/system prunes, kubectl wipes)
|
|
19
|
+
/\b(?:drop|truncate|delete)\s+(?:database|table|schema)\b/i,
|
|
20
|
+
/\bdocker\s+system\s+prune\b/i,
|
|
21
|
+
/\bdocker\s+volume\s+(?:rm|prune)\b/i,
|
|
22
|
+
/\bkubectl\s+delete\s+(?:namespace|all|--all)\b/i,
|
|
23
|
+
// Disk & System destruction (format, fdisk, dd, overwriting block devices, shutdown, fork bombs)
|
|
24
|
+
/\b(?:mkfs|fdisk|parted|mkswap)\b/i,
|
|
25
|
+
/\bformat\s+[A-Z]:/i,
|
|
26
|
+
/\bdd\s+.*(?:if=|of=)\b/i,
|
|
27
|
+
/>\s*\/dev\/(?:sda|disk|hda|nvme)\b/i,
|
|
28
|
+
/\b(?:shutdown|reboot|halt|poweroff)\b/i,
|
|
29
|
+
/:\(\)\{\s*:\|:&\s*\};:/, // Fork bomb
|
|
30
|
+
// User data/config erasure
|
|
31
|
+
/\bcrontab\s+-r\b/i,
|
|
32
|
+
/\bhistory\s+-c\b/i,
|
|
33
|
+
// Massive permission shifts (chmod 777, recursive chowns)
|
|
34
|
+
/\bchmod\s+(?:-[R\w]*\s+)?777\b/i,
|
|
35
|
+
/\bchown\s+-[R\w]*\b/i,
|
|
36
|
+
];
|
|
37
|
+
return destructivePatterns.some((pattern) => pattern.test(command));
|
|
38
|
+
}
|
|
39
|
+
export function isCommandSafe(command) {
|
|
40
|
+
// Do not auto-approve chained, redirected, or sudo commands
|
|
41
|
+
if (/[;&|>]/.test(command) || /\bsudo\b/.test(command)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const safePatterns = [
|
|
45
|
+
/^\s*ls\b/i,
|
|
46
|
+
/^\s*pwd\b/i,
|
|
47
|
+
/^\s*whoami\b/i,
|
|
48
|
+
/^\s*cat\b/i,
|
|
49
|
+
/^\s*echo\b/i,
|
|
50
|
+
/^\s*grep\b/i,
|
|
51
|
+
/^\s*find\b/i,
|
|
52
|
+
/^\s*npm\s+(install|i|ci|run\b)/i,
|
|
53
|
+
/^\s*yarn\s+(install|add|build|lint|test|run\b)/i,
|
|
54
|
+
/^\s*pnpm\s+(install|i|add|build|lint|test|run\b)/i,
|
|
55
|
+
/^\s*bun\s+(install|i|add|run\b)/i,
|
|
56
|
+
/^\s*cargo\s+(build|check|add|test|run\b)/i,
|
|
57
|
+
/^\s*go\s+(mod|get|build|test|run\b)/i,
|
|
58
|
+
/^\s*pip\s+(install|list|show)\b/i,
|
|
59
|
+
/^\s*uv\s+(add|pip|sync|run\b)/i,
|
|
60
|
+
/^\s*rustc\b/i,
|
|
61
|
+
/^\s*git\s+(status|log|diff|show|branch)\b/i,
|
|
62
|
+
/^\s*(node|python|ruby|java|go|rustc)\s+(--version|-v)\b/i,
|
|
63
|
+
/^\s*tsc\b/i,
|
|
64
|
+
/^\s*eslint\b/i,
|
|
65
|
+
/^\s*prettier\b/i,
|
|
66
|
+
];
|
|
67
|
+
return safePatterns.some((pattern) => pattern.test(command));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Executes an interactive approval prompt via `@clack/prompts`.
|
|
71
|
+
*
|
|
72
|
+
* Supports pattern-based approvals and three user approval models:
|
|
73
|
+
* 1. **Ask**: Prompts the user for every shell execution command (except implicitly safe ones).
|
|
74
|
+
* 2. **Skip-Once**: Automatically grants permission to the current command, then resets.
|
|
75
|
+
* 3. **Skip-All / Auto-Approve**: Grants permission to all future terminal commands.
|
|
76
|
+
*
|
|
77
|
+
* Destructive commands will ALWAYS prompt the user, regardless of mode.
|
|
78
|
+
*
|
|
79
|
+
* @param command - The terminal string requested for execution.
|
|
80
|
+
* @returns A promise resolving to `true` if approved, or `false` if denied/cancelled.
|
|
81
|
+
*/
|
|
82
|
+
export async function requestCommandApproval(command) {
|
|
83
|
+
const mode = getApprovalMode();
|
|
84
|
+
const isDestructive = isCommandDestructive(command);
|
|
85
|
+
const isSafe = isCommandSafe(command);
|
|
86
|
+
// Force prompt for destructive commands
|
|
87
|
+
if (isDestructive) {
|
|
88
|
+
p.log.warn(`${pc.bgRed(pc.white(' WARNING '))} Destructive command detected. Explicit approval required.`);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
if (mode === 'skip-all') {
|
|
92
|
+
p.log.info(`${pc.dim('Auto-approved (skip-all):')} ${pc.yellow(command)}`);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
if (mode === 'skip-once') {
|
|
96
|
+
p.log.info(`${pc.dim('Auto-approved (skip-once):')} ${pc.yellow(command)}`);
|
|
97
|
+
consumeSkipOnce();
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
if (isSafe) {
|
|
101
|
+
p.log.info(`${pc.dim('Auto-approved (safe command):')} ${pc.yellow(command)}`);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// mode === 'ask' or isDestructive
|
|
106
|
+
const result = await p['select']({
|
|
107
|
+
message: `Approve command: ${pc.yellow(command)}`,
|
|
108
|
+
options: [
|
|
109
|
+
{ value: 'approve', label: 'Yes, run this command' },
|
|
110
|
+
{ value: 'skip-once', label: 'Yes, and skip approval for the next command too' },
|
|
111
|
+
{
|
|
112
|
+
value: 'skip-all',
|
|
113
|
+
label: 'Yes, auto-approve all future commands (Note: this lasts until you restart the CLI)',
|
|
114
|
+
},
|
|
115
|
+
{ value: 'deny', label: 'No, deny this command' },
|
|
116
|
+
],
|
|
117
|
+
});
|
|
118
|
+
if (p.isCancel(result) || result === 'deny') {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (result === 'skip-once') {
|
|
122
|
+
setApprovalMode('skip-once');
|
|
123
|
+
}
|
|
124
|
+
else if (result === 'skip-all') {
|
|
125
|
+
setApprovalMode('skip-all');
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
/**
|
|
3
|
+
* Asynchronous Input Handler (AsyncInputHandler)
|
|
4
|
+
*
|
|
5
|
+
* This class intercepts user keyboard input from standard input (stdin) while
|
|
6
|
+
* background processes (e.g., Gemini model generations or long shell tool runs)
|
|
7
|
+
* are executing. It provides seamless execution management by letting users:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Pause execution** at any time by pressing a key.
|
|
10
|
+
* 2. **Queue feedback** ("chained messages") without waiting for the entire run to finish.
|
|
11
|
+
* 3. **Force abort** the current model or tool run by entering "stop".
|
|
12
|
+
*
|
|
13
|
+
* ### Raw Mode & Terminal States
|
|
14
|
+
* In normal CLI execution, Node.js waits for a line feed (Enter) before emitting input.
|
|
15
|
+
* To intercept immediate keystrokes, we put `process.stdin` into *raw mode*.
|
|
16
|
+
* While in raw mode, we listen for direct data buffers.
|
|
17
|
+
* To avoid visual conflicts with our logging output and Clack's CLI spinners,
|
|
18
|
+
* we dynamically pause spinners, detach listeners, disable raw mode, open standard
|
|
19
|
+
* interactive text-prompt forms, and resume raw mode and spinners upon completion.
|
|
20
|
+
*/
|
|
21
|
+
export declare class AsyncInputHandler {
|
|
22
|
+
/** Queue of pending user feedback/instructions typed during active background execution */
|
|
23
|
+
private queue;
|
|
24
|
+
/** Guard flag preventing multiple simultaneous input prompt overlays */
|
|
25
|
+
private isPrompting;
|
|
26
|
+
/** Reference to the Clack CLI spinner which must be paused/restarted during prompts */
|
|
27
|
+
private spinner;
|
|
28
|
+
/** Stores the terminal's raw mode configuration state before handler activation */
|
|
29
|
+
private originalRawMode;
|
|
30
|
+
/** Indicates whether the input handler is currently inactive/stopped */
|
|
31
|
+
private stopped;
|
|
32
|
+
/** Reference to the AbortController controlling the active AI request to trigger cancellations */
|
|
33
|
+
private ac;
|
|
34
|
+
/**
|
|
35
|
+
* Registers the active AbortController for the current AI request.
|
|
36
|
+
* This is triggered when the user commands a process cancel (e.g., typing "stop").
|
|
37
|
+
*
|
|
38
|
+
* @param ac - The AbortController controlling the current generation.
|
|
39
|
+
*/
|
|
40
|
+
setAbortController(ac: AbortController): void;
|
|
41
|
+
/**
|
|
42
|
+
* Determines if a user-prompt dialog is actively running.
|
|
43
|
+
* Useful for coordinating other console logging output to avoid UI overlap.
|
|
44
|
+
*
|
|
45
|
+
* @returns True if a text prompt is currently displayed, false otherwise.
|
|
46
|
+
*/
|
|
47
|
+
isCurrentlyPrompting(): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Blocks and waits until any active prompting action is completed.
|
|
50
|
+
* Guarantees terminal stdout is clean before resuming logs.
|
|
51
|
+
*/
|
|
52
|
+
waitForPrompt(): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Core stdin event listener. Detects pressed keys, pauses background visual elements,
|
|
55
|
+
* handles exit interrupts, and opens a Clack text dialog for input.
|
|
56
|
+
*
|
|
57
|
+
* Handles recovery of the standard input stream and raw mode states even if
|
|
58
|
+
* errors occur during prompt initialization.
|
|
59
|
+
*
|
|
60
|
+
* @param chunk - The raw terminal buffer containing keypress data.
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
private onData;
|
|
64
|
+
/**
|
|
65
|
+
* Starts intercepting keystrokes and enables raw terminal processing.
|
|
66
|
+
* Saves the original raw mode configuration to ensure a clean restoration later.
|
|
67
|
+
*
|
|
68
|
+
* @param spinner - The current active Clack spinner UI reference, if any.
|
|
69
|
+
*/
|
|
70
|
+
start(spinner?: ReturnType<typeof p.spinner>): void;
|
|
71
|
+
/**
|
|
72
|
+
* Disables raw mode, stops intercepting keystrokes, and restores
|
|
73
|
+
* the terminal stdin stream to its original raw/cooked state.
|
|
74
|
+
*/
|
|
75
|
+
stop(): void;
|
|
76
|
+
/**
|
|
77
|
+
* Retrieves and flushes all user feedback messages accumulated during execution.
|
|
78
|
+
*
|
|
79
|
+
* @returns A concatenated string of all queued user messages separated by newlines,
|
|
80
|
+
* or an empty string if nothing was queued.
|
|
81
|
+
*/
|
|
82
|
+
getAndClear(): string;
|
|
83
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
/**
|
|
4
|
+
* Asynchronous Input Handler (AsyncInputHandler)
|
|
5
|
+
*
|
|
6
|
+
* This class intercepts user keyboard input from standard input (stdin) while
|
|
7
|
+
* background processes (e.g., Gemini model generations or long shell tool runs)
|
|
8
|
+
* are executing. It provides seamless execution management by letting users:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Pause execution** at any time by pressing a key.
|
|
11
|
+
* 2. **Queue feedback** ("chained messages") without waiting for the entire run to finish.
|
|
12
|
+
* 3. **Force abort** the current model or tool run by entering "stop".
|
|
13
|
+
*
|
|
14
|
+
* ### Raw Mode & Terminal States
|
|
15
|
+
* In normal CLI execution, Node.js waits for a line feed (Enter) before emitting input.
|
|
16
|
+
* To intercept immediate keystrokes, we put `process.stdin` into *raw mode*.
|
|
17
|
+
* While in raw mode, we listen for direct data buffers.
|
|
18
|
+
* To avoid visual conflicts with our logging output and Clack's CLI spinners,
|
|
19
|
+
* we dynamically pause spinners, detach listeners, disable raw mode, open standard
|
|
20
|
+
* interactive text-prompt forms, and resume raw mode and spinners upon completion.
|
|
21
|
+
*/
|
|
22
|
+
export class AsyncInputHandler {
|
|
23
|
+
/** Queue of pending user feedback/instructions typed during active background execution */
|
|
24
|
+
queue = [];
|
|
25
|
+
/** Guard flag preventing multiple simultaneous input prompt overlays */
|
|
26
|
+
isPrompting = false;
|
|
27
|
+
/** Reference to the Clack CLI spinner which must be paused/restarted during prompts */
|
|
28
|
+
spinner = null;
|
|
29
|
+
/** Stores the terminal's raw mode configuration state before handler activation */
|
|
30
|
+
originalRawMode = false;
|
|
31
|
+
/** Indicates whether the input handler is currently inactive/stopped */
|
|
32
|
+
stopped = true;
|
|
33
|
+
/** Reference to the AbortController controlling the active AI request to trigger cancellations */
|
|
34
|
+
ac = null;
|
|
35
|
+
/**
|
|
36
|
+
* Registers the active AbortController for the current AI request.
|
|
37
|
+
* This is triggered when the user commands a process cancel (e.g., typing "stop").
|
|
38
|
+
*
|
|
39
|
+
* @param ac - The AbortController controlling the current generation.
|
|
40
|
+
*/
|
|
41
|
+
setAbortController(ac) {
|
|
42
|
+
this.ac = ac;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Determines if a user-prompt dialog is actively running.
|
|
46
|
+
* Useful for coordinating other console logging output to avoid UI overlap.
|
|
47
|
+
*
|
|
48
|
+
* @returns True if a text prompt is currently displayed, false otherwise.
|
|
49
|
+
*/
|
|
50
|
+
isCurrentlyPrompting() {
|
|
51
|
+
return this.isPrompting;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Blocks and waits until any active prompting action is completed.
|
|
55
|
+
* Guarantees terminal stdout is clean before resuming logs.
|
|
56
|
+
*/
|
|
57
|
+
async waitForPrompt() {
|
|
58
|
+
while (this.isPrompting) {
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Core stdin event listener. Detects pressed keys, pauses background visual elements,
|
|
64
|
+
* handles exit interrupts, and opens a Clack text dialog for input.
|
|
65
|
+
*
|
|
66
|
+
* Handles recovery of the standard input stream and raw mode states even if
|
|
67
|
+
* errors occur during prompt initialization.
|
|
68
|
+
*
|
|
69
|
+
* @param chunk - The raw terminal buffer containing keypress data.
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
72
|
+
onData = async (chunk) => {
|
|
73
|
+
try {
|
|
74
|
+
if (this.isPrompting)
|
|
75
|
+
return;
|
|
76
|
+
const char = chunk.toString();
|
|
77
|
+
// Handle Ctrl+C (End of Text ASCII 0x03) immediately
|
|
78
|
+
if (char === '\u0003') {
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
// Ignore standard non-printable control keys, escape sequences, etc.
|
|
82
|
+
if (char.charCodeAt(0) < 32 || char === '\u007f')
|
|
83
|
+
return;
|
|
84
|
+
this.isPrompting = true;
|
|
85
|
+
process.stdin.removeListener('data', this.onData);
|
|
86
|
+
if (process.stdin.isTTY) {
|
|
87
|
+
process.stdin.setRawMode(false);
|
|
88
|
+
}
|
|
89
|
+
// Hide the background spinner before prompt output to avoid corrupting terminal lines
|
|
90
|
+
if (this.spinner) {
|
|
91
|
+
this.spinner.stop();
|
|
92
|
+
}
|
|
93
|
+
p.log.step(pc.cyan('Paused to receive input'));
|
|
94
|
+
// Capture the user feedback. The character typed to trigger this event is passed
|
|
95
|
+
// as the initial value of the prompt to avoid losing the first keystroke.
|
|
96
|
+
const userInput = await p.text({
|
|
97
|
+
message: 'Add chained message:',
|
|
98
|
+
placeholder: '(Leave blank and press Enter to cancel)',
|
|
99
|
+
initialValue: char,
|
|
100
|
+
});
|
|
101
|
+
let wasAborted = false;
|
|
102
|
+
if (!p.isCancel(userInput) && userInput.trim()) {
|
|
103
|
+
const text = userInput.trim();
|
|
104
|
+
if (text.toLowerCase() === 'stop') {
|
|
105
|
+
if (this.ac) {
|
|
106
|
+
this.ac.abort();
|
|
107
|
+
p.log.warn(pc.yellow(`Generation aborted by user.`));
|
|
108
|
+
wasAborted = true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
this.queue.push(text);
|
|
113
|
+
p.log.info(pc.cyan(`📥 Queued message: "${text}"`));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (this.spinner && !wasAborted) {
|
|
117
|
+
const resumeMsg = this.spinner._lastMessage || 'Resuming execution...';
|
|
118
|
+
this.spinner.start(resumeMsg);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
// Absorb and suppress errors safely during raw stream intercepts
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
if (!this.stopped) {
|
|
126
|
+
if (process.stdin.isTTY) {
|
|
127
|
+
process.stdin.setRawMode(true);
|
|
128
|
+
}
|
|
129
|
+
process.stdin.resume();
|
|
130
|
+
// Small delay before reattaching listener to avoid capturing duplicate keypress frames
|
|
131
|
+
setTimeout(() => {
|
|
132
|
+
if (!this.stopped) {
|
|
133
|
+
process.stdin.on('data', this.onData);
|
|
134
|
+
}
|
|
135
|
+
this.isPrompting = false;
|
|
136
|
+
}, 50);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
this.isPrompting = false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Starts intercepting keystrokes and enables raw terminal processing.
|
|
145
|
+
* Saves the original raw mode configuration to ensure a clean restoration later.
|
|
146
|
+
*
|
|
147
|
+
* @param spinner - The current active Clack spinner UI reference, if any.
|
|
148
|
+
*/
|
|
149
|
+
start(spinner) {
|
|
150
|
+
this.stopped = false;
|
|
151
|
+
if (spinner) {
|
|
152
|
+
this.spinner = spinner;
|
|
153
|
+
// Monkey-patch to track the latest message for un-pausing
|
|
154
|
+
if (!spinner._isPatched) {
|
|
155
|
+
;
|
|
156
|
+
spinner._isPatched = true;
|
|
157
|
+
spinner._lastMessage = 'Executing...';
|
|
158
|
+
const originalMessage = spinner.message.bind(spinner);
|
|
159
|
+
spinner.message = (msg) => {
|
|
160
|
+
if (msg) {
|
|
161
|
+
;
|
|
162
|
+
spinner._lastMessage = msg;
|
|
163
|
+
}
|
|
164
|
+
originalMessage(msg);
|
|
165
|
+
};
|
|
166
|
+
const originalStart = spinner.start.bind(spinner);
|
|
167
|
+
spinner.start = (msg) => {
|
|
168
|
+
if (msg) {
|
|
169
|
+
;
|
|
170
|
+
spinner._lastMessage = msg;
|
|
171
|
+
}
|
|
172
|
+
originalStart(msg);
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (process.stdin.isTTY) {
|
|
177
|
+
this.originalRawMode = process.stdin.isRaw;
|
|
178
|
+
process.stdin.setRawMode(true);
|
|
179
|
+
}
|
|
180
|
+
process.stdin.resume();
|
|
181
|
+
process.stdin.on('data', this.onData);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Disables raw mode, stops intercepting keystrokes, and restores
|
|
185
|
+
* the terminal stdin stream to its original raw/cooked state.
|
|
186
|
+
*/
|
|
187
|
+
stop() {
|
|
188
|
+
this.stopped = true;
|
|
189
|
+
process.stdin.removeListener('data', this.onData);
|
|
190
|
+
if (process.stdin.isTTY) {
|
|
191
|
+
process.stdin.setRawMode(this.originalRawMode);
|
|
192
|
+
}
|
|
193
|
+
this.spinner = null;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Retrieves and flushes all user feedback messages accumulated during execution.
|
|
197
|
+
*
|
|
198
|
+
* @returns A concatenated string of all queued user messages separated by newlines,
|
|
199
|
+
* or an empty string if nothing was queued.
|
|
200
|
+
*/
|
|
201
|
+
getAndClear() {
|
|
202
|
+
if (this.queue.length === 0)
|
|
203
|
+
return '';
|
|
204
|
+
const messages = this.queue.join('\n');
|
|
205
|
+
this.queue = [];
|
|
206
|
+
return messages;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SlashCommandContext, SlashCommandResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Handles all slash command operations (/paste, /clear, /models, /debug, /auto-approve, /revert, /commit).
|
|
4
|
+
* Returns control state to the caller loop (such as whether to continue/skip, or if a text-override occurred).
|
|
5
|
+
*/
|
|
6
|
+
export declare function handleSlashCommand(command: string, context: SlashCommandContext): Promise<SlashCommandResult>;
|