agent-mp 0.1.0 → 0.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/dist/commands/repl.d.ts +29 -1
- package/dist/commands/repl.js +520 -191
- package/dist/commands/setup.js +83 -26
- package/dist/core/engine.d.ts +3 -1
- package/dist/core/engine.js +191 -44
- package/dist/index.js +143 -15
- package/dist/types.d.ts +7 -6
- package/dist/ui/input.d.ts +24 -0
- package/dist/ui/input.js +244 -0
- package/dist/ui/theme.d.ts +99 -0
- package/dist/ui/theme.js +307 -0
- package/dist/utils/config.d.ts +9 -0
- package/dist/utils/config.js +7 -6
- package/dist/utils/qwen-auth.d.ts +3 -0
- package/dist/utils/qwen-auth.js +30 -12
- package/dist/utils/sessions.d.ts +17 -0
- package/dist/utils/sessions.js +46 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,32 +3,93 @@ import { Command } from 'commander';
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import * as os from 'os';
|
|
5
5
|
import * as fs from 'fs/promises';
|
|
6
|
-
import
|
|
6
|
+
import * as readline from 'readline';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { AGENT_HOME } from './utils/config.js';
|
|
9
|
+
import { runRepl, runRole } from './commands/repl.js';
|
|
10
|
+
import { setupCommand } from './commands/setup.js';
|
|
11
|
+
import { getLastSessionForDir, listSessions, loadSession } from './utils/sessions.js';
|
|
7
12
|
const program = new Command();
|
|
8
13
|
program
|
|
9
14
|
.name('agent-mp')
|
|
10
15
|
.description('Deterministic multi-agent CLI orchestrator')
|
|
11
|
-
.version('0.1.0')
|
|
12
|
-
|
|
13
|
-
.option('--
|
|
14
|
-
.option('--
|
|
15
|
-
|
|
16
|
+
.version('0.1.0')
|
|
17
|
+
.argument('[task...]', 'Task description or arguments')
|
|
18
|
+
.option('--continue', 'Resume the last session in the current directory')
|
|
19
|
+
.option('--resume [id]', 'Resume any saved session by ID (omit ID to pick from list)')
|
|
20
|
+
.option('--rol <role>', 'Run a specific role directly: orchestrator | implementor | reviewer | coordinator')
|
|
21
|
+
.option('--reset-coordinator', 'Clear coordinator selection (re-pick on next run)')
|
|
22
|
+
.option('--reset-auth', 'Wipe all auth credentials and start fresh')
|
|
23
|
+
.addHelpText('after', `
|
|
24
|
+
Setup subcommands:
|
|
25
|
+
agent-mp setup init Full interactive wizard (login + roles + project)
|
|
26
|
+
agent-mp setup config-multi Reconfigure all roles at once
|
|
27
|
+
agent-mp setup orch Reconfigure orchestrator only
|
|
28
|
+
agent-mp setup impl Reconfigure implementor only
|
|
29
|
+
agent-mp setup rev Reconfigure reviewer only
|
|
30
|
+
agent-mp setup explorer Reconfigure explorer only
|
|
31
|
+
agent-mp setup proposer Reconfigure proposer (deliberation)
|
|
32
|
+
agent-mp setup critic Reconfigure critic (deliberation)
|
|
33
|
+
agent-mp setup status Show current configuration
|
|
34
|
+
|
|
35
|
+
REPL commands (type inside the session):
|
|
36
|
+
/help Show all REPL commands
|
|
37
|
+
/status Show roles, auth, and tasks
|
|
38
|
+
/auth-status Show login status
|
|
39
|
+
/setup orch|impl|rev|explorer Reconfigure a role without leaving the REPL
|
|
40
|
+
/config-multi Reconfigure all roles at once
|
|
41
|
+
/run <task> Full cycle: orchestrator → implementor → reviewer
|
|
42
|
+
/run orch <task> Run only orchestrator
|
|
43
|
+
/run impl <id> Run only implementor
|
|
44
|
+
/run rev <id> Run only reviewer
|
|
45
|
+
/login Login (OAuth)
|
|
46
|
+
/logout Logout and clear credentials
|
|
47
|
+
/models [cli] List available models
|
|
48
|
+
/tasks List all tasks
|
|
49
|
+
/clear Clear screen
|
|
50
|
+
/exit Exit
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
agent-mp Start interactive REPL
|
|
54
|
+
agent-mp --continue Resume last session in this directory
|
|
55
|
+
agent-mp --resume Pick a saved session from a list
|
|
56
|
+
agent-mp --resume 2026-04-07_14-30-00 Resume a specific session
|
|
57
|
+
agent-mp --rol orchestrator "build login system"
|
|
58
|
+
agent-mp setup orch Change the orchestrator CLI/model
|
|
59
|
+
`);
|
|
60
|
+
// ── Role-specific binary detection ───────────────────────────────────────────
|
|
61
|
+
const ROLE_BINS = {
|
|
62
|
+
'agent-orch': 'orchestrator',
|
|
63
|
+
'agent-impl': 'implementor',
|
|
64
|
+
'agent-rev': 'reviewer',
|
|
65
|
+
'agent-explorer': 'explorer',
|
|
66
|
+
};
|
|
67
|
+
const binName = path.basename(process.argv[1]).replace(/\.(js|ts|mjs)$/, '');
|
|
68
|
+
const nativeRole = ROLE_BINS[binName];
|
|
69
|
+
if (nativeRole) {
|
|
70
|
+
// Role-specific CLI: accept task as positional arg or prompt for it
|
|
71
|
+
const taskArg = process.argv.slice(2).filter(a => !a.startsWith('-')).join(' ').trim();
|
|
72
|
+
if (!taskArg) {
|
|
73
|
+
console.error(chalk.red(` Usage: ${binName} "<task description or task-id>"`));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
await runRole(nativeRole, taskArg);
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
80
|
+
program.action(async (task, options) => {
|
|
16
81
|
if (options.resetCoordinator || options.resetAuth) {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const qwenAgentHome = path.join(os.homedir(), '.qwen-agent');
|
|
20
|
-
const corporateCreds = path.join(qwenAgentHome, 'oauth_creds.json');
|
|
82
|
+
const authFile = path.join(AGENT_HOME, 'auth.json');
|
|
83
|
+
const qwenCreds = path.join(AGENT_HOME, 'oauth_creds.json');
|
|
21
84
|
try {
|
|
22
85
|
if (options.resetCoordinator) {
|
|
23
|
-
// Remove auth file completely to force coordinator re-selection
|
|
24
86
|
await fs.unlink(authFile).catch(() => { });
|
|
25
|
-
|
|
26
|
-
await fs.unlink(corporateCreds).catch(() => { });
|
|
87
|
+
await fs.unlink(qwenCreds).catch(() => { });
|
|
27
88
|
console.log('✓ Coordinator reset. Run agent-mp to select a coordinator.');
|
|
28
89
|
}
|
|
29
90
|
if (options.resetAuth) {
|
|
30
|
-
await fs.rm(
|
|
31
|
-
await fs.rm(
|
|
91
|
+
await fs.rm(AGENT_HOME, { recursive: true, force: true });
|
|
92
|
+
await fs.rm(path.join(os.homedir(), '.qwen-agent'), { recursive: true, force: true }).catch(() => { });
|
|
32
93
|
console.log('✓ All authentication data cleared. Run agent-mp to start fresh.');
|
|
33
94
|
}
|
|
34
95
|
}
|
|
@@ -38,6 +99,73 @@ program.action(async (options) => {
|
|
|
38
99
|
}
|
|
39
100
|
process.exit(0);
|
|
40
101
|
}
|
|
102
|
+
// --continue: resume last session for current dir
|
|
103
|
+
if (options.continue) {
|
|
104
|
+
const session = await getLastSessionForDir(process.cwd());
|
|
105
|
+
if (!session) {
|
|
106
|
+
console.log(chalk.yellow(' No previous session found for this directory. Starting fresh.'));
|
|
107
|
+
runRepl();
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const date = new Date(session.updatedAt).toLocaleString();
|
|
111
|
+
console.log(chalk.dim(` Resuming last session: ${session.id} (${date}, ${session.messages.length} messages)`));
|
|
112
|
+
runRepl(session);
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// --resume [id]: pick or load a specific session
|
|
117
|
+
if (options.resume !== undefined) {
|
|
118
|
+
const id = typeof options.resume === 'string' ? options.resume : '';
|
|
119
|
+
if (id) {
|
|
120
|
+
const session = await loadSession(id);
|
|
121
|
+
if (!session) {
|
|
122
|
+
console.log(chalk.red(` Session not found: ${id}`));
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
runRepl(session);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// Show picker
|
|
129
|
+
const sessions = await listSessions();
|
|
130
|
+
if (sessions.length === 0) {
|
|
131
|
+
console.log(chalk.yellow(' No sessions saved yet. Starting fresh.'));
|
|
132
|
+
runRepl();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
console.log(chalk.bold.cyan('\n Saved Sessions\n'));
|
|
136
|
+
sessions.slice(0, 20).forEach((s, i) => {
|
|
137
|
+
const date = new Date(s.updatedAt).toLocaleString();
|
|
138
|
+
const msgs = s.messages.length;
|
|
139
|
+
const dir = s.dir.replace(os.homedir(), '~');
|
|
140
|
+
console.log(chalk.dim(` ${String(i + 1).padStart(2)}. [${date}] ${dir} — ${msgs} messages (${s.id})`));
|
|
141
|
+
});
|
|
142
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
143
|
+
const answer = await new Promise((resolve) => rl.question(chalk.bold('\n Pick session #: '), resolve));
|
|
144
|
+
rl.close();
|
|
145
|
+
const num = parseInt(answer.trim());
|
|
146
|
+
if (isNaN(num) || num < 1 || num > sessions.length) {
|
|
147
|
+
console.log(chalk.red(' Invalid selection. Starting fresh.'));
|
|
148
|
+
runRepl();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const chosen = sessions[num - 1];
|
|
152
|
+
console.log(chalk.dim(` Resuming: ${chosen.id}`));
|
|
153
|
+
runRepl(chosen);
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// --rol flag: run specific role directly
|
|
158
|
+
if (options.rol) {
|
|
159
|
+
if (!task || task.length === 0) {
|
|
160
|
+
console.error('Error: --rol requires an argument (task description or task ID)');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
const arg = task.join(' ');
|
|
164
|
+
await runRole(options.rol, arg);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// Default: start fresh REPL
|
|
41
168
|
runRepl();
|
|
42
169
|
});
|
|
170
|
+
setupCommand(program);
|
|
43
171
|
program.parse();
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export interface CliRole {
|
|
2
|
+
cli: string;
|
|
3
|
+
model: string;
|
|
4
|
+
cmd: string;
|
|
5
|
+
fallback?: CliRole;
|
|
6
|
+
}
|
|
1
7
|
export interface AgentConfig {
|
|
2
8
|
project: string;
|
|
3
9
|
description: string;
|
|
@@ -15,17 +21,12 @@ export interface AgentConfig {
|
|
|
15
21
|
stop_on: string;
|
|
16
22
|
output_dir: string;
|
|
17
23
|
};
|
|
18
|
-
|
|
24
|
+
fallback_global?: CliRole;
|
|
19
25
|
structure: {
|
|
20
26
|
approved_dirs: string[];
|
|
21
27
|
forbidden_dirs: string[];
|
|
22
28
|
};
|
|
23
29
|
}
|
|
24
|
-
export interface CliRole {
|
|
25
|
-
cli: string;
|
|
26
|
-
model: string;
|
|
27
|
-
cmd: string;
|
|
28
|
-
}
|
|
29
30
|
export interface TaskPlan {
|
|
30
31
|
task_id: string;
|
|
31
32
|
description: string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare class FixedInput {
|
|
2
|
+
private buf;
|
|
3
|
+
private history;
|
|
4
|
+
private histIdx;
|
|
5
|
+
private origLog;
|
|
6
|
+
private get rows();
|
|
7
|
+
get cols(): number;
|
|
8
|
+
private get scrollBottom();
|
|
9
|
+
private _contentRows;
|
|
10
|
+
setup(): void;
|
|
11
|
+
teardown(): void;
|
|
12
|
+
redrawBox(): void;
|
|
13
|
+
suspend(): () => void;
|
|
14
|
+
readLine(): Promise<string>;
|
|
15
|
+
println(text: string): void;
|
|
16
|
+
printSeparator(): void;
|
|
17
|
+
/** Set DECSTBM once — only called in setup() and on resize, never during typing. */
|
|
18
|
+
private _setScrollRegion;
|
|
19
|
+
/** Blank every row in the reserved area. */
|
|
20
|
+
private _clearReserved;
|
|
21
|
+
private _drawBox;
|
|
22
|
+
/** Split text into visual lines: split on \n, then wrap each segment. */
|
|
23
|
+
private _wrapText;
|
|
24
|
+
}
|
package/dist/ui/input.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
// ─── Midas brand colors ──────────────────────────────────────────────────────
|
|
3
|
+
const T = (s) => chalk.rgb(0, 185, 180)(s); // teal — borders
|
|
4
|
+
const B = (s) => chalk.rgb(30, 110, 185)(s); // blue — prompt arrow
|
|
5
|
+
const PREFIX = T('│') + B(' > ');
|
|
6
|
+
const PREFIX_CONT = T('│') + B(' '); // continuation lines
|
|
7
|
+
const PREFIX_COLS = 4; // visual width of "│ > " and "│ "
|
|
8
|
+
// Maximum content rows the box can grow to (Shift+Enter / word-wrap).
|
|
9
|
+
// The reserved area at the bottom is MAX_CONTENT_ROWS + 2 (borders).
|
|
10
|
+
const MAX_CONTENT_ROWS = 4;
|
|
11
|
+
const RESERVED_ROWS = MAX_CONTENT_ROWS + 2; // 6
|
|
12
|
+
// ─── FixedInput ──────────────────────────────────────────────────────────────
|
|
13
|
+
// Keeps an input box pinned to the physical bottom of the terminal.
|
|
14
|
+
// The box starts as 3 rows (border + 1 content + border) and grows up to
|
|
15
|
+
// RESERVED_ROWS when the user types multiline text (Shift+Enter) or the
|
|
16
|
+
// text wraps. The scroll region is set ONCE at setup (and on resize) to
|
|
17
|
+
// [1 .. rows-RESERVED_ROWS] so DECSTBM never fires during normal typing.
|
|
18
|
+
export class FixedInput {
|
|
19
|
+
buf = '';
|
|
20
|
+
history = [];
|
|
21
|
+
histIdx = -1;
|
|
22
|
+
origLog;
|
|
23
|
+
get rows() { return process.stdout.rows || 24; }
|
|
24
|
+
get cols() { return process.stdout.columns || 80; }
|
|
25
|
+
// The scroll region always ends here — everything below is reserved for the box.
|
|
26
|
+
get scrollBottom() { return this.rows - RESERVED_ROWS; }
|
|
27
|
+
// How many content rows the current buffer needs (1 .. MAX_CONTENT_ROWS).
|
|
28
|
+
_contentRows() {
|
|
29
|
+
const w = this.cols - PREFIX_COLS - 2;
|
|
30
|
+
if (w <= 0)
|
|
31
|
+
return 1;
|
|
32
|
+
if (!this.buf)
|
|
33
|
+
return 1;
|
|
34
|
+
let n = 0;
|
|
35
|
+
for (const seg of this.buf.split('\n'))
|
|
36
|
+
n += Math.max(1, Math.ceil((seg.length || 1) / w));
|
|
37
|
+
return Math.min(n, MAX_CONTENT_ROWS);
|
|
38
|
+
}
|
|
39
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
40
|
+
setup() {
|
|
41
|
+
this.origLog = console.log;
|
|
42
|
+
console.log = (...args) => {
|
|
43
|
+
const text = args.map(a => (typeof a === 'string' ? a : String(a))).join(' ');
|
|
44
|
+
this.println(text);
|
|
45
|
+
};
|
|
46
|
+
this._setScrollRegion();
|
|
47
|
+
process.stdout.write(`\x1b[${this.scrollBottom};1H`);
|
|
48
|
+
this._clearReserved();
|
|
49
|
+
this._drawBox();
|
|
50
|
+
process.stdout.on('resize', () => {
|
|
51
|
+
this._setScrollRegion();
|
|
52
|
+
this._clearReserved();
|
|
53
|
+
this._drawBox();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
teardown() {
|
|
57
|
+
console.log = this.origLog;
|
|
58
|
+
process.stdout.write('\x1b[r'); // reset scroll region
|
|
59
|
+
process.stdout.write('\x1b[?25h'); // show cursor
|
|
60
|
+
process.stdout.write(`\x1b[${this.rows};1H\n`);
|
|
61
|
+
}
|
|
62
|
+
redrawBox() { this._drawBox(); }
|
|
63
|
+
suspend() {
|
|
64
|
+
console.log = this.origLog;
|
|
65
|
+
process.stdout.write('\x1b[r');
|
|
66
|
+
this._clearReserved();
|
|
67
|
+
process.stdout.write(`\x1b[${this.scrollBottom};1H`);
|
|
68
|
+
return () => {
|
|
69
|
+
console.log = (...args) => {
|
|
70
|
+
const text = args.map(a => (typeof a === 'string' ? a : String(a))).join(' ');
|
|
71
|
+
this.println(text);
|
|
72
|
+
};
|
|
73
|
+
this._setScrollRegion();
|
|
74
|
+
this._clearReserved();
|
|
75
|
+
this._drawBox();
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// ── Input ──────────────────────────────────────────────────────────────────
|
|
79
|
+
readLine() {
|
|
80
|
+
this.buf = '';
|
|
81
|
+
this.histIdx = -1;
|
|
82
|
+
this._drawBox();
|
|
83
|
+
return new Promise((resolve) => {
|
|
84
|
+
process.stdin.setRawMode(true);
|
|
85
|
+
process.stdin.resume();
|
|
86
|
+
const done = (line) => {
|
|
87
|
+
process.stdin.removeListener('data', onData);
|
|
88
|
+
if (process.stdin.isTTY)
|
|
89
|
+
process.stdin.setRawMode(false);
|
|
90
|
+
this.buf = '';
|
|
91
|
+
this._drawBox();
|
|
92
|
+
resolve(line);
|
|
93
|
+
};
|
|
94
|
+
const onData = (data) => {
|
|
95
|
+
const hex = data.toString('hex');
|
|
96
|
+
const key = data.toString();
|
|
97
|
+
// ── Shift+Enter → insert newline into buffer ──────────────────
|
|
98
|
+
// Different terminals send different sequences:
|
|
99
|
+
if (hex === '5c0d' || // \\\r (GNOME Terminal, ThinkPad, many Linux)
|
|
100
|
+
key === '\x0a' || // LF (Ctrl+J, some terminals)
|
|
101
|
+
hex === '1b5b31333b327e' || // \x1b[13;2~ xterm
|
|
102
|
+
hex === '1b5b31333b3275' || // \x1b[13;2u kitty
|
|
103
|
+
hex === '1b4f4d' // \x1bOM DECNKP
|
|
104
|
+
) {
|
|
105
|
+
this.buf += '\n';
|
|
106
|
+
this._drawBox();
|
|
107
|
+
// ── Enter → submit ────────────────────────────────────────────
|
|
108
|
+
}
|
|
109
|
+
else if (key === '\r') {
|
|
110
|
+
const line = this.buf;
|
|
111
|
+
if (line.trim()) {
|
|
112
|
+
this.history.unshift(line);
|
|
113
|
+
if (this.history.length > 200)
|
|
114
|
+
this.history.pop();
|
|
115
|
+
}
|
|
116
|
+
done(line);
|
|
117
|
+
}
|
|
118
|
+
else if (key === '\x7f' || key === '\x08') { // Backspace
|
|
119
|
+
if (this.buf.length > 0) {
|
|
120
|
+
this.buf = this.buf.slice(0, -1);
|
|
121
|
+
this._drawBox();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else if (key === '\x03') { // Ctrl+C
|
|
125
|
+
this.teardown();
|
|
126
|
+
process.exit(0);
|
|
127
|
+
}
|
|
128
|
+
else if (key === '\x04') { // Ctrl+D
|
|
129
|
+
done('/exit');
|
|
130
|
+
}
|
|
131
|
+
else if (key === '\x15') { // Ctrl+U
|
|
132
|
+
this.buf = '';
|
|
133
|
+
this._drawBox();
|
|
134
|
+
}
|
|
135
|
+
else if (hex === '1b5b41') { // Arrow ↑
|
|
136
|
+
if (this.histIdx + 1 < this.history.length) {
|
|
137
|
+
this.histIdx++;
|
|
138
|
+
this.buf = this.history[this.histIdx];
|
|
139
|
+
this._drawBox();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if (hex === '1b5b42') { // Arrow ↓
|
|
143
|
+
if (this.histIdx > 0) {
|
|
144
|
+
this.histIdx--;
|
|
145
|
+
this.buf = this.history[this.histIdx];
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
this.histIdx = -1;
|
|
149
|
+
this.buf = '';
|
|
150
|
+
}
|
|
151
|
+
this._drawBox();
|
|
152
|
+
}
|
|
153
|
+
else if (key.length >= 1 && key.charCodeAt(0) >= 32 && !key.startsWith('\x1b')) {
|
|
154
|
+
this.buf += key;
|
|
155
|
+
this._drawBox();
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
process.stdin.on('data', onData);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
// ── Output helpers ─────────────────────────────────────────────────────────
|
|
162
|
+
println(text) {
|
|
163
|
+
process.stdout.write(`\x1b[${this.scrollBottom};1H`);
|
|
164
|
+
process.stdout.write(text + '\n');
|
|
165
|
+
this._drawBox();
|
|
166
|
+
}
|
|
167
|
+
printSeparator() {
|
|
168
|
+
this.println(chalk.rgb(0, 120, 116)('─'.repeat(this.cols - 1)));
|
|
169
|
+
}
|
|
170
|
+
// ── Private drawing ────────────────────────────────────────────────────────
|
|
171
|
+
/** Set DECSTBM once — only called in setup() and on resize, never during typing. */
|
|
172
|
+
_setScrollRegion() {
|
|
173
|
+
const sb = this.scrollBottom;
|
|
174
|
+
if (sb >= 1)
|
|
175
|
+
process.stdout.write(`\x1b[1;${sb}r`);
|
|
176
|
+
}
|
|
177
|
+
/** Blank every row in the reserved area. */
|
|
178
|
+
_clearReserved() {
|
|
179
|
+
for (let r = this.scrollBottom + 1; r <= this.rows; r++)
|
|
180
|
+
process.stdout.write(`\x1b[${r};1H\x1b[2K`);
|
|
181
|
+
}
|
|
182
|
+
_drawBox() {
|
|
183
|
+
const cols = this.cols;
|
|
184
|
+
const cRows = this._contentRows();
|
|
185
|
+
const cWidth = cols - PREFIX_COLS - 2;
|
|
186
|
+
// The box occupies the bottom of the terminal:
|
|
187
|
+
// topBorder = rows - cRows - 1
|
|
188
|
+
// content rows = rows - cRows ... rows - 1
|
|
189
|
+
// bottomBorder = rows
|
|
190
|
+
const topBorder = this.rows - cRows - 1;
|
|
191
|
+
// Hide cursor while repainting
|
|
192
|
+
process.stdout.write('\x1b[?25l');
|
|
193
|
+
// Clear entire reserved area (removes stale content from previous draws)
|
|
194
|
+
this._clearReserved();
|
|
195
|
+
// ── Top border ───────────────────────────────────────────────
|
|
196
|
+
process.stdout.write(`\x1b[${topBorder};1H`);
|
|
197
|
+
process.stdout.write(T('╭') + T('─'.repeat(cols - 2)));
|
|
198
|
+
process.stdout.write(`\x1b[${cols}G` + T('╮'));
|
|
199
|
+
// ── Content rows ─────────────────────────────────────────────
|
|
200
|
+
const wrapped = this._wrapText(this.buf, cWidth);
|
|
201
|
+
const showStart = Math.max(0, wrapped.length - cRows);
|
|
202
|
+
const visible = wrapped.slice(showStart);
|
|
203
|
+
for (let i = 0; i < cRows; i++) {
|
|
204
|
+
const row = topBorder + 1 + i;
|
|
205
|
+
let line = visible[i] ?? '';
|
|
206
|
+
// Show overflow indicator when content is clipped above
|
|
207
|
+
if (i === 0 && showStart > 0)
|
|
208
|
+
line = '… ' + line.slice(0, Math.max(0, cWidth - 2));
|
|
209
|
+
else
|
|
210
|
+
line = line.slice(0, cWidth);
|
|
211
|
+
const pfx = (i === 0) ? PREFIX : PREFIX_CONT;
|
|
212
|
+
process.stdout.write(`\x1b[${row};1H`);
|
|
213
|
+
process.stdout.write(pfx + line);
|
|
214
|
+
process.stdout.write(`\x1b[${cols}G` + T('│'));
|
|
215
|
+
}
|
|
216
|
+
// ── Bottom border ────────────────────────────────────────────
|
|
217
|
+
process.stdout.write(`\x1b[${this.rows};1H`);
|
|
218
|
+
process.stdout.write(T('╰') + T('─'.repeat(cols - 2)));
|
|
219
|
+
process.stdout.write(`\x1b[${cols}G` + T('╯'));
|
|
220
|
+
// ── Position cursor at end of last visible line ──────────────
|
|
221
|
+
const lastLine = visible[visible.length - 1] ?? '';
|
|
222
|
+
const cursorRow = topBorder + cRows; // last content row
|
|
223
|
+
const cursorCol = PREFIX_COLS + 1 + lastLine.length;
|
|
224
|
+
process.stdout.write(`\x1b[${cursorRow};${cursorCol}H`);
|
|
225
|
+
process.stdout.write('\x1b[?25h');
|
|
226
|
+
}
|
|
227
|
+
/** Split text into visual lines: split on \n, then wrap each segment. */
|
|
228
|
+
_wrapText(text, maxWidth) {
|
|
229
|
+
if (!text)
|
|
230
|
+
return [''];
|
|
231
|
+
if (maxWidth <= 0)
|
|
232
|
+
return [''];
|
|
233
|
+
const result = [];
|
|
234
|
+
for (const seg of text.split('\n')) {
|
|
235
|
+
if (seg.length === 0) {
|
|
236
|
+
result.push('');
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
for (let i = 0; i < seg.length; i += maxWidth)
|
|
240
|
+
result.push(seg.slice(i, i + maxWidth));
|
|
241
|
+
}
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export declare const theme: {
|
|
2
|
+
heading: import("chalk").ChalkInstance;
|
|
3
|
+
emphasis: import("chalk").ChalkInstance;
|
|
4
|
+
strong: import("chalk").ChalkInstance;
|
|
5
|
+
inlineCode: import("chalk").ChalkInstance;
|
|
6
|
+
link: import("chalk").ChalkInstance;
|
|
7
|
+
quote: import("chalk").ChalkInstance;
|
|
8
|
+
banner: import("chalk").ChalkInstance;
|
|
9
|
+
bannerAccent: import("chalk").ChalkInstance;
|
|
10
|
+
spinnerActive: import("chalk").ChalkInstance;
|
|
11
|
+
spinnerDone: import("chalk").ChalkInstance;
|
|
12
|
+
spinnerFailed: import("chalk").ChalkInstance;
|
|
13
|
+
toolCallBorder: import("chalk").ChalkInstance;
|
|
14
|
+
toolCallName: import("chalk").ChalkInstance;
|
|
15
|
+
toolCallSuccess: import("chalk").ChalkInstance;
|
|
16
|
+
toolCallError: import("chalk").ChalkInstance;
|
|
17
|
+
dim: import("chalk").ChalkInstance;
|
|
18
|
+
section: import("chalk").ChalkInstance;
|
|
19
|
+
success: import("chalk").ChalkInstance;
|
|
20
|
+
error: import("chalk").ChalkInstance;
|
|
21
|
+
warning: import("chalk").ChalkInstance;
|
|
22
|
+
info: import("chalk").ChalkInstance;
|
|
23
|
+
codeBlockBg: string;
|
|
24
|
+
codeBlockBorder: import("chalk").ChalkInstance;
|
|
25
|
+
diffAdd: import("chalk").ChalkInstance;
|
|
26
|
+
diffRemove: import("chalk").ChalkInstance;
|
|
27
|
+
teal: import("chalk").ChalkInstance;
|
|
28
|
+
tealDim: import("chalk").ChalkInstance;
|
|
29
|
+
midasBlue: import("chalk").ChalkInstance;
|
|
30
|
+
};
|
|
31
|
+
export declare function renderBanner(): string;
|
|
32
|
+
export interface WelcomePanelOpts {
|
|
33
|
+
version: string;
|
|
34
|
+
cliName: string;
|
|
35
|
+
activeModel: string;
|
|
36
|
+
user: string;
|
|
37
|
+
project: string;
|
|
38
|
+
stack: string;
|
|
39
|
+
roles?: {
|
|
40
|
+
orchestrator?: string;
|
|
41
|
+
implementor?: string;
|
|
42
|
+
reviewer?: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export declare function renderWelcomePanel(opts: WelcomePanelOpts): string;
|
|
46
|
+
export declare function renderSeparator(): string;
|
|
47
|
+
export declare function renderInputBoxTop(): string;
|
|
48
|
+
export declare function renderInputBoxBottom(): string;
|
|
49
|
+
/** The prompt string that sits on the │ > line */
|
|
50
|
+
export declare function inputPromptStr(): string;
|
|
51
|
+
export declare function renderHelpHint(): string;
|
|
52
|
+
export interface SectionRow {
|
|
53
|
+
key: string;
|
|
54
|
+
value: string;
|
|
55
|
+
keyColor?: (s: string) => string;
|
|
56
|
+
valueColor?: (s: string) => string;
|
|
57
|
+
}
|
|
58
|
+
export declare function renderSectionBox(title: string, rows: SectionRow[]): string;
|
|
59
|
+
export declare function renderMultiSectionBox(sections: Array<{
|
|
60
|
+
title: string;
|
|
61
|
+
rows: SectionRow[];
|
|
62
|
+
}>): string;
|
|
63
|
+
export declare class Spinner {
|
|
64
|
+
private frame;
|
|
65
|
+
private label;
|
|
66
|
+
private interval;
|
|
67
|
+
private started;
|
|
68
|
+
constructor(label: string);
|
|
69
|
+
start(label?: string): void;
|
|
70
|
+
private tick;
|
|
71
|
+
private render;
|
|
72
|
+
succeed(label?: string): void;
|
|
73
|
+
fail(label?: string): void;
|
|
74
|
+
private stop;
|
|
75
|
+
}
|
|
76
|
+
export declare function boxTop(label: string): string;
|
|
77
|
+
export declare function boxLine(content: string): string;
|
|
78
|
+
export declare function boxBottom(): string;
|
|
79
|
+
export declare function box(label: string, content: string): string;
|
|
80
|
+
export declare function statusReport(section: string, entries: Array<[string, string]>): string;
|
|
81
|
+
export declare function multiReport(sections: Array<[string, Array<[string, string]>]>): string;
|
|
82
|
+
export declare const emoji: {
|
|
83
|
+
thinking: string;
|
|
84
|
+
done: string;
|
|
85
|
+
failed: string;
|
|
86
|
+
read: string;
|
|
87
|
+
write: string;
|
|
88
|
+
edit: string;
|
|
89
|
+
search: string;
|
|
90
|
+
plan: string;
|
|
91
|
+
review: string;
|
|
92
|
+
run: string;
|
|
93
|
+
warning: string;
|
|
94
|
+
info: string;
|
|
95
|
+
success: string;
|
|
96
|
+
error: string;
|
|
97
|
+
login: string;
|
|
98
|
+
logout: string;
|
|
99
|
+
};
|