agent-rev 0.1.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/api/qwen.d.ts +12 -0
- package/dist/api/qwen.js +150 -0
- package/dist/commands/auth.d.ts +31 -0
- package/dist/commands/auth.js +255 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +42 -0
- package/dist/commands/models.d.ts +2 -0
- package/dist/commands/models.js +27 -0
- package/dist/commands/repl.d.ts +29 -0
- package/dist/commands/repl.js +1167 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +52 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +353 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +46 -0
- package/dist/core/engine.d.ts +36 -0
- package/dist/core/engine.js +905 -0
- package/dist/core/prompts.d.ts +11 -0
- package/dist/core/prompts.js +126 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +171 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +120 -0
- package/dist/types.d.ts +73 -0
- package/dist/types.js +58 -0
- 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 +38 -0
- package/dist/utils/config.js +40 -0
- package/dist/utils/fs.d.ts +7 -0
- package/dist/utils/fs.js +37 -0
- package/dist/utils/logger.d.ts +13 -0
- package/dist/utils/logger.js +46 -0
- package/dist/utils/qwen-auth.d.ts +12 -0
- package/dist/utils/qwen-auth.js +250 -0
- package/dist/utils/sessions.d.ts +17 -0
- package/dist/utils/sessions.js +46 -0
- package/package.json +44 -0
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
|
+
};
|
package/dist/ui/theme.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
// ═══════════════════════════════════════════════════════════
|
|
3
|
+
// Color Theme
|
|
4
|
+
// ═══════════════════════════════════════════════════════════
|
|
5
|
+
export const theme = {
|
|
6
|
+
// Markdown/content colors
|
|
7
|
+
heading: chalk.cyan,
|
|
8
|
+
emphasis: chalk.magenta,
|
|
9
|
+
strong: chalk.yellow,
|
|
10
|
+
inlineCode: chalk.green,
|
|
11
|
+
link: chalk.blue.underline,
|
|
12
|
+
quote: chalk.gray,
|
|
13
|
+
// UI element colors
|
|
14
|
+
banner: chalk.blueBright,
|
|
15
|
+
bannerAccent: chalk.rgb(255, 165, 0),
|
|
16
|
+
spinnerActive: chalk.blue,
|
|
17
|
+
spinnerDone: chalk.green,
|
|
18
|
+
spinnerFailed: chalk.red,
|
|
19
|
+
toolCallBorder: chalk.rgb(100, 100, 120),
|
|
20
|
+
toolCallName: chalk.cyanBright,
|
|
21
|
+
toolCallSuccess: chalk.greenBright,
|
|
22
|
+
toolCallError: chalk.redBright,
|
|
23
|
+
dim: chalk.gray,
|
|
24
|
+
section: chalk.yellow,
|
|
25
|
+
success: chalk.green,
|
|
26
|
+
error: chalk.red,
|
|
27
|
+
warning: chalk.yellow,
|
|
28
|
+
info: chalk.blue,
|
|
29
|
+
// Code block
|
|
30
|
+
codeBlockBg: "#2b303b",
|
|
31
|
+
codeBlockBorder: chalk.rgb(100, 110, 120),
|
|
32
|
+
// Diff colors
|
|
33
|
+
diffAdd: chalk.rgb(100, 200, 100),
|
|
34
|
+
diffRemove: chalk.rgb(200, 100, 100),
|
|
35
|
+
// Midas brand colors
|
|
36
|
+
teal: chalk.rgb(0, 185, 180),
|
|
37
|
+
tealDim: chalk.rgb(0, 120, 116),
|
|
38
|
+
midasBlue: chalk.rgb(30, 110, 185),
|
|
39
|
+
};
|
|
40
|
+
// Strip ANSI escape codes to get visual length
|
|
41
|
+
function stripAnsi(s) {
|
|
42
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
43
|
+
}
|
|
44
|
+
function padEnd(s, width) {
|
|
45
|
+
const vis = stripAnsi(s).length;
|
|
46
|
+
const diff = width - vis;
|
|
47
|
+
return diff > 0 ? s + " ".repeat(diff) : s;
|
|
48
|
+
}
|
|
49
|
+
// ═══════════════════════════════════════════════════════════
|
|
50
|
+
// ASCII Banner — MIDAS (used inside the welcome panel)
|
|
51
|
+
// ═══════════════════════════════════════════════════════════
|
|
52
|
+
const BANNER_LINES = [
|
|
53
|
+
" ██║ ███╗ ███╗██╗ █╗█████╗ ██╗ ██╗███╗",
|
|
54
|
+
"█╔═█╗█╔══╝ █══╝███╗ █║╚═█╔═╝ ██║ ██║█╔█║",
|
|
55
|
+
"████║█║ ██╗██╗ █╔██╗█║ █║ ██╗█╔█ █╔█║███║",
|
|
56
|
+
"█╔═█║█║ █║█╔╝ █║╚███║ █║ ╚═╝█║╚█╔╝█║█╔═╝",
|
|
57
|
+
"█║ █║╚███╔╝███╗█║ ╚██║ █║ █║ ╚╝ █║█║ ",
|
|
58
|
+
"╚╝ ╚╝ ╚══╝ ╚══╝╚╝ ╚═╝ ╚╝ ╚╝ ╚╝╚╝ ",
|
|
59
|
+
];
|
|
60
|
+
export function renderBanner() {
|
|
61
|
+
return BANNER_LINES.map((line) => chalk.rgb(30, 110, 185)(line)).join("\n");
|
|
62
|
+
}
|
|
63
|
+
export function renderWelcomePanel(opts) {
|
|
64
|
+
// MIDAS banner is 38 chars wide; left col = banner + 4 padding = 42 min
|
|
65
|
+
const cols = Math.min(Math.max(process.stdout.columns || 84, 84), 110);
|
|
66
|
+
const innerWidth = cols - 2;
|
|
67
|
+
const divAt = 46; // banner lines are 44 chars + 1 leading space = 45, +1 margin
|
|
68
|
+
const rightWidth = innerWidth - divAt - 1;
|
|
69
|
+
const bo = chalk.rgb(0, 185, 180); // Midas teal border
|
|
70
|
+
const accent = chalk.rgb(0, 185, 180);
|
|
71
|
+
const divLine = chalk.dim("│");
|
|
72
|
+
// ── Left column: MIDAS logo + compact info ───────────────
|
|
73
|
+
const midasLines = BANNER_LINES.map((l) => ` ${chalk.rgb(30, 110, 185)(l)}`);
|
|
74
|
+
const left = [
|
|
75
|
+
"",
|
|
76
|
+
...midasLines,
|
|
77
|
+
"",
|
|
78
|
+
` ${chalk.dim(opts.version + " · " + opts.activeModel)}`,
|
|
79
|
+
` ${chalk.dim(opts.user + " · " + opts.project + " · " + opts.stack)}`,
|
|
80
|
+
"",
|
|
81
|
+
];
|
|
82
|
+
// ── Right column ─────────────────────────────────────────
|
|
83
|
+
const rSep = chalk.rgb(0, 120, 116)("─".repeat(Math.max(rightWidth - 2, 4)));
|
|
84
|
+
const right = [
|
|
85
|
+
"",
|
|
86
|
+
` ${accent("Tips for getting started")}`,
|
|
87
|
+
` ${rSep}`,
|
|
88
|
+
` ${chalk.dim("Run /help for commands")}`,
|
|
89
|
+
` ${chalk.dim("Run /setup to configure agents")}`,
|
|
90
|
+
"",
|
|
91
|
+
];
|
|
92
|
+
if (opts.roles &&
|
|
93
|
+
(opts.roles.orchestrator || opts.roles.implementor || opts.roles.reviewer)) {
|
|
94
|
+
right.push(` ${accent("Current roles")}`);
|
|
95
|
+
right.push(` ${rSep}`);
|
|
96
|
+
if (opts.roles.orchestrator) {
|
|
97
|
+
right.push(` ${chalk.dim("Orch ")}${chalk.rgb(0, 185, 180)(opts.roles.orchestrator)}`);
|
|
98
|
+
}
|
|
99
|
+
if (opts.roles.implementor) {
|
|
100
|
+
right.push(` ${chalk.dim("Impl ")}${chalk.rgb(0, 185, 180)(opts.roles.implementor)}`);
|
|
101
|
+
}
|
|
102
|
+
if (opts.roles.reviewer) {
|
|
103
|
+
right.push(` ${chalk.dim("Rev ")}${chalk.rgb(0, 185, 180)(opts.roles.reviewer)}`);
|
|
104
|
+
}
|
|
105
|
+
right.push("");
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
right.push(` ${accent("Recent activity")}`);
|
|
109
|
+
right.push(` ${rSep}`);
|
|
110
|
+
right.push(` ${chalk.dim("No recent activity")}`);
|
|
111
|
+
right.push("");
|
|
112
|
+
}
|
|
113
|
+
// Equalize heights
|
|
114
|
+
const height = Math.max(left.length, right.length);
|
|
115
|
+
while (left.length < height)
|
|
116
|
+
left.push("");
|
|
117
|
+
while (right.length < height)
|
|
118
|
+
right.push("");
|
|
119
|
+
const rows = [];
|
|
120
|
+
// Top border
|
|
121
|
+
rows.push(bo("╭") + bo("─".repeat(innerWidth)) + bo("╮"));
|
|
122
|
+
for (let i = 0; i < height; i++) {
|
|
123
|
+
const lPad = padEnd(left[i] ?? "", divAt);
|
|
124
|
+
const rPad = padEnd(right[i] ?? "", rightWidth);
|
|
125
|
+
rows.push(bo("│") + lPad + divLine + rPad + bo("│"));
|
|
126
|
+
}
|
|
127
|
+
// Bottom border
|
|
128
|
+
rows.push(bo("╰") + bo("─".repeat(innerWidth)) + bo("╯"));
|
|
129
|
+
return rows.join("\n");
|
|
130
|
+
}
|
|
131
|
+
// ═══════════════════════════════════════════════════════════
|
|
132
|
+
// Conversation separator — thin line between turns
|
|
133
|
+
// ═══════════════════════════════════════════════════════════
|
|
134
|
+
export function renderSeparator() {
|
|
135
|
+
const cols = Math.min(process.stdout.columns || 80, 110);
|
|
136
|
+
return chalk.rgb(0, 120, 116)("─".repeat(cols));
|
|
137
|
+
}
|
|
138
|
+
// ═══════════════════════════════════════════════════════════
|
|
139
|
+
// Input box — orange-bordered prompt area
|
|
140
|
+
// ═══════════════════════════════════════════════════════════
|
|
141
|
+
function inputCols() {
|
|
142
|
+
return Math.min(process.stdout.columns || 80, 110);
|
|
143
|
+
}
|
|
144
|
+
export function renderInputBoxTop() {
|
|
145
|
+
const bo = chalk.rgb(0, 185, 180);
|
|
146
|
+
return bo("╭") + bo("─".repeat(inputCols() - 2)) + bo("╮");
|
|
147
|
+
}
|
|
148
|
+
export function renderInputBoxBottom() {
|
|
149
|
+
const bo = chalk.rgb(0, 185, 180);
|
|
150
|
+
return bo("╰") + bo("─".repeat(inputCols() - 2)) + bo("╯");
|
|
151
|
+
}
|
|
152
|
+
/** The prompt string that sits on the │ > line */
|
|
153
|
+
export function inputPromptStr() {
|
|
154
|
+
return chalk.rgb(0, 185, 180)("│") + chalk.rgb(30, 110, 185)(" > ");
|
|
155
|
+
}
|
|
156
|
+
// ═══════════════════════════════════════════════════════════
|
|
157
|
+
// Help hint — shown below the welcome panel
|
|
158
|
+
// ═══════════════════════════════════════════════════════════
|
|
159
|
+
export function renderHelpHint() {
|
|
160
|
+
return chalk.dim(" ? for shortcuts · /help for commands · /exit to quit");
|
|
161
|
+
}
|
|
162
|
+
export function renderSectionBox(title, rows) {
|
|
163
|
+
const cols = Math.min(process.stdout.columns || 80, 110);
|
|
164
|
+
const innerWidth = cols - 2;
|
|
165
|
+
const bo = chalk.rgb(0, 185, 180);
|
|
166
|
+
const teal = chalk.rgb(0, 185, 180);
|
|
167
|
+
const blue = chalk.rgb(30, 110, 185);
|
|
168
|
+
const maxKey = Math.max(...rows.map((r) => r.key.length), 0);
|
|
169
|
+
const lines = [];
|
|
170
|
+
// Title row
|
|
171
|
+
const titleStr = ` ${teal(title)} `;
|
|
172
|
+
const titleStripped = stripAnsi(titleStr);
|
|
173
|
+
const dashCount = Math.max(innerWidth - titleStripped.length, 0);
|
|
174
|
+
const leftDash = "─".repeat(Math.floor(dashCount / 2));
|
|
175
|
+
const rightDash = "─".repeat(Math.ceil(dashCount / 2));
|
|
176
|
+
lines.push(bo("╭") + bo(leftDash) + titleStr + bo(rightDash) + bo("╮"));
|
|
177
|
+
// Rows
|
|
178
|
+
for (const row of rows) {
|
|
179
|
+
const k = (row.keyColor ?? blue)(row.key.padEnd(maxKey));
|
|
180
|
+
const v = (row.valueColor ?? chalk.dim)(row.value);
|
|
181
|
+
const content = ` ${k} ${v}`;
|
|
182
|
+
const padded = padEnd(content, innerWidth);
|
|
183
|
+
lines.push(bo("│") + padded + bo("│"));
|
|
184
|
+
}
|
|
185
|
+
// Bottom
|
|
186
|
+
lines.push(bo("╰") + bo("─".repeat(innerWidth)) + bo("╯"));
|
|
187
|
+
return lines.join("\n");
|
|
188
|
+
}
|
|
189
|
+
export function renderMultiSectionBox(sections) {
|
|
190
|
+
return sections.map((s) => renderSectionBox(s.title, s.rows)).join("\n");
|
|
191
|
+
}
|
|
192
|
+
// ═══════════════════════════════════════════════════════════
|
|
193
|
+
// Braille Spinner
|
|
194
|
+
// ═══════════════════════════════════════════════════════════
|
|
195
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
196
|
+
export class Spinner {
|
|
197
|
+
frame = 0;
|
|
198
|
+
label;
|
|
199
|
+
interval = null;
|
|
200
|
+
started = false;
|
|
201
|
+
constructor(label) {
|
|
202
|
+
this.label = label;
|
|
203
|
+
}
|
|
204
|
+
start(label) {
|
|
205
|
+
if (label)
|
|
206
|
+
this.label = label;
|
|
207
|
+
if (this.started)
|
|
208
|
+
return;
|
|
209
|
+
this.started = true;
|
|
210
|
+
this.frame = 0;
|
|
211
|
+
this.interval = setInterval(() => this.tick(), 80);
|
|
212
|
+
this.render();
|
|
213
|
+
}
|
|
214
|
+
tick() {
|
|
215
|
+
this.frame = (this.frame + 1) % SPINNER_FRAMES.length;
|
|
216
|
+
this.render();
|
|
217
|
+
}
|
|
218
|
+
render() {
|
|
219
|
+
const frame = SPINNER_FRAMES[this.frame];
|
|
220
|
+
const text = theme.spinnerActive(`${frame} ${this.label}`);
|
|
221
|
+
process.stdout.write(`\r${text}`);
|
|
222
|
+
}
|
|
223
|
+
succeed(label) {
|
|
224
|
+
this.stop();
|
|
225
|
+
const msg = label || this.label;
|
|
226
|
+
process.stdout.write(`\r${theme.spinnerDone(`✔ ${msg}`)}\n`);
|
|
227
|
+
}
|
|
228
|
+
fail(label) {
|
|
229
|
+
this.stop();
|
|
230
|
+
const msg = label || this.label;
|
|
231
|
+
process.stdout.write(`\r${theme.spinnerFailed(`✘ ${msg}`)}\n`);
|
|
232
|
+
}
|
|
233
|
+
stop() {
|
|
234
|
+
if (this.interval) {
|
|
235
|
+
clearInterval(this.interval);
|
|
236
|
+
this.interval = null;
|
|
237
|
+
}
|
|
238
|
+
this.started = false;
|
|
239
|
+
process.stdout.write("\r\x1b[K");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// ═══════════════════════════════════════════════════════════
|
|
243
|
+
// Box Drawing (for tool calls, code blocks, etc.)
|
|
244
|
+
// ═══════════════════════════════════════════════════════════
|
|
245
|
+
export function boxTop(label) {
|
|
246
|
+
const border = theme.toolCallBorder(`╭─ ${label} ─`);
|
|
247
|
+
const fill = theme.toolCallBorder("─".repeat(Math.max(0, 40 - label.length - 4)));
|
|
248
|
+
return border + fill + theme.toolCallBorder("╮");
|
|
249
|
+
}
|
|
250
|
+
export function boxLine(content) {
|
|
251
|
+
return theme.toolCallBorder("│") + ` ${content}`;
|
|
252
|
+
}
|
|
253
|
+
export function boxBottom() {
|
|
254
|
+
return theme.toolCallBorder("╰" + "─".repeat(48) + "╯");
|
|
255
|
+
}
|
|
256
|
+
export function box(label, content) {
|
|
257
|
+
const lines = content.split("\n");
|
|
258
|
+
const maxLen = Math.max(label.length, ...lines.map((l) => l.length));
|
|
259
|
+
const width = Math.max(maxLen + 4, 40);
|
|
260
|
+
const top = theme.toolCallBorder(`╭─ ${label} `) +
|
|
261
|
+
theme.toolCallBorder("─".repeat(Math.max(0, width - label.length - 4)) + "╮");
|
|
262
|
+
const body = lines
|
|
263
|
+
.map((l) => theme.toolCallBorder("│") +
|
|
264
|
+
` ${l}` +
|
|
265
|
+
" ".repeat(Math.max(0, width - l.length - 2)) +
|
|
266
|
+
theme.toolCallBorder("│"))
|
|
267
|
+
.join("\n");
|
|
268
|
+
const bottom = theme.toolCallBorder("╰" + "─".repeat(width - 2) + "╯");
|
|
269
|
+
return `${top}\n${body}\n${bottom}`;
|
|
270
|
+
}
|
|
271
|
+
// ═══════════════════════════════════════════════════════════
|
|
272
|
+
// Status Report (aligned columns)
|
|
273
|
+
// ═══════════════════════════════════════════════════════════
|
|
274
|
+
export function statusReport(section, entries) {
|
|
275
|
+
const maxKey = Math.max(...entries.map(([k]) => k.length));
|
|
276
|
+
const lines = entries.map(([key, val]) => {
|
|
277
|
+
const k = theme.section(key.padEnd(maxKey));
|
|
278
|
+
return ` ${k} ${theme.dim(val)}`;
|
|
279
|
+
});
|
|
280
|
+
return `${theme.emphasis.bold(section)}\n${lines.join("\n")}`;
|
|
281
|
+
}
|
|
282
|
+
export function multiReport(sections) {
|
|
283
|
+
return sections
|
|
284
|
+
.map(([title, entries]) => statusReport(title, entries))
|
|
285
|
+
.join("\n\n");
|
|
286
|
+
}
|
|
287
|
+
// ═══════════════════════════════════════════════════════════
|
|
288
|
+
// Emoji Indicators
|
|
289
|
+
// ═══════════════════════════════════════════════════════════
|
|
290
|
+
export const emoji = {
|
|
291
|
+
thinking: "🦀",
|
|
292
|
+
done: "✨",
|
|
293
|
+
failed: "❌",
|
|
294
|
+
read: "📄",
|
|
295
|
+
write: "✏️",
|
|
296
|
+
edit: "📝",
|
|
297
|
+
search: "🔎",
|
|
298
|
+
plan: "📋",
|
|
299
|
+
review: "👁️",
|
|
300
|
+
run: "⚡",
|
|
301
|
+
warning: "⚠️",
|
|
302
|
+
info: "ℹ️",
|
|
303
|
+
success: "✅",
|
|
304
|
+
error: "🚫",
|
|
305
|
+
login: "🔑",
|
|
306
|
+
logout: "🔓",
|
|
307
|
+
};
|