ispbills-icli 4.0.3 → 4.0.5
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/bin/icli.js +6 -6
- package/package.json +1 -1
- package/src/chat.js +16 -32
- package/src/layout.js +86 -7
package/bin/icli.js
CHANGED
|
@@ -4,18 +4,18 @@ import { stdin as input, stdout as output, argv, exit } from 'process';
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { loadConfig, configPath } from '../src/config.js';
|
|
6
6
|
import { loginFlow } from '../src/auth.js';
|
|
7
|
-
import {
|
|
7
|
+
import { inner, onResize, boxTop, boxBot, boxMid, boxRow } from '../src/layout.js';
|
|
8
8
|
import {
|
|
9
9
|
streamChat, printBanner, printSlashMenu, printUserBox,
|
|
10
10
|
SLASH_COMMANDS, slashCompleter,
|
|
11
11
|
} from '../src/chat.js';
|
|
12
12
|
|
|
13
13
|
const P = {
|
|
14
|
-
border: chalk.hex('#
|
|
15
|
-
dim: chalk.hex('#
|
|
16
|
-
muted: chalk.hex('#
|
|
17
|
-
accent: chalk.hex('#
|
|
18
|
-
accentD: chalk.hex('#
|
|
14
|
+
border: chalk.hex('#303134'),
|
|
15
|
+
dim: chalk.hex('#9AA0A6'),
|
|
16
|
+
muted: chalk.hex('#80868B'),
|
|
17
|
+
accent: chalk.hex('#8AB4F8').bold,
|
|
18
|
+
accentD: chalk.hex('#8AB4F8'),
|
|
19
19
|
bold: chalk.bold.white,
|
|
20
20
|
ok: chalk.green,
|
|
21
21
|
warn: chalk.yellow,
|
package/package.json
CHANGED
package/src/chat.js
CHANGED
|
@@ -7,13 +7,13 @@ import { cols, inner, visLen, padRight, stripAnsi,
|
|
|
7
7
|
boxTop, boxMid, boxBot, boxTopOpen, boxBotOpen,
|
|
8
8
|
boxRow, boxLine } from './layout.js';
|
|
9
9
|
|
|
10
|
-
// ──
|
|
10
|
+
// ── Gemini-style palette ───────────────────────────────────────────────────────
|
|
11
11
|
const P = {
|
|
12
|
-
border: chalk.hex('#
|
|
13
|
-
dim: chalk.hex('#
|
|
14
|
-
muted: chalk.hex('#
|
|
15
|
-
accent: chalk.hex('#
|
|
16
|
-
accentB: chalk.hex('#
|
|
12
|
+
border: chalk.hex('#303134'),
|
|
13
|
+
dim: chalk.hex('#9AA0A6'),
|
|
14
|
+
muted: chalk.hex('#80868B'),
|
|
15
|
+
accent: chalk.hex('#8AB4F8'),
|
|
16
|
+
accentB: chalk.hex('#8AB4F8').bold,
|
|
17
17
|
white: chalk.white,
|
|
18
18
|
bold: chalk.bold.white,
|
|
19
19
|
ok: chalk.green,
|
|
@@ -70,21 +70,12 @@ const shortModel = (m) =>
|
|
|
70
70
|
// ── Banner ────────────────────────────────────────────────────────────────────
|
|
71
71
|
export function printBanner(cfg, meta = {}) {
|
|
72
72
|
const model = meta.model ? shortModel(meta.model) : (cfg?._model ?? null);
|
|
73
|
-
const w = cols();
|
|
74
73
|
|
|
75
74
|
process.stdout.write('\n');
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
process.stdout.write(boxTop('', P.border) + '\n');
|
|
79
|
-
|
|
80
|
-
// Title row
|
|
81
|
-
const title = P.bold('iCopilot') + P.dim(' · IspBills AI Network Engineer');
|
|
82
|
-
process.stdout.write(boxRow(title, P.border) + '\n');
|
|
83
|
-
|
|
84
|
-
// Divider
|
|
75
|
+
process.stdout.write(boxTop('iCopilot', P.border) + '\n');
|
|
76
|
+
process.stdout.write(boxRow(P.dim('Gemini-style terminal mode'), P.border) + '\n');
|
|
85
77
|
process.stdout.write(boxMid(P.border) + '\n');
|
|
86
78
|
|
|
87
|
-
// Session info rows
|
|
88
79
|
const row1parts = [];
|
|
89
80
|
if (model) row1parts.push(P.dim('model ') + P.white(model));
|
|
90
81
|
if (cfg?.user?.name) row1parts.push(P.dim('user ') + P.white(cfg.user.name));
|
|
@@ -98,10 +89,8 @@ export function printBanner(cfg, meta = {}) {
|
|
|
98
89
|
process.stdout.write(boxRow(P.dim('url ') + P.accent(url), P.border) + '\n');
|
|
99
90
|
}
|
|
100
91
|
|
|
101
|
-
// Divider
|
|
102
92
|
process.stdout.write(boxMid(P.border) + '\n');
|
|
103
93
|
|
|
104
|
-
// Hints row
|
|
105
94
|
const hints = [
|
|
106
95
|
P.accentB('/') + P.muted(' commands'),
|
|
107
96
|
P.accentB('Tab') + P.muted(' autocomplete'),
|
|
@@ -109,8 +98,6 @@ export function printBanner(cfg, meta = {}) {
|
|
|
109
98
|
P.accentB('Ctrl+C') + P.muted(' exit'),
|
|
110
99
|
].join(P.muted(' '));
|
|
111
100
|
process.stdout.write(boxRow(hints, P.border) + '\n');
|
|
112
|
-
|
|
113
|
-
// Bottom border
|
|
114
101
|
process.stdout.write(boxBot(P.border) + '\n\n');
|
|
115
102
|
}
|
|
116
103
|
|
|
@@ -130,7 +117,7 @@ export const SLASH_COMMANDS = [
|
|
|
130
117
|
|
|
131
118
|
export function printSlashMenu() {
|
|
132
119
|
process.stdout.write('\n');
|
|
133
|
-
process.stdout.write(boxTop('
|
|
120
|
+
process.stdout.write(boxTop('Slash commands', P.border) + '\n');
|
|
134
121
|
for (const { cmd: c, hint, desc } of SLASH_COMMANDS) {
|
|
135
122
|
const key = P.accentB(c) + (hint ? P.dim(' ' + hint) : '');
|
|
136
123
|
const line = padRight(key, 22) + ' ' + P.dim(desc);
|
|
@@ -150,21 +137,21 @@ export function slashCompleter(line) {
|
|
|
150
137
|
// ── User message box ───────────────────────────────────────────────────────────
|
|
151
138
|
export function printUserBox(question) {
|
|
152
139
|
process.stdout.write('\n');
|
|
153
|
-
process.stdout.write(boxTop('You', P.
|
|
140
|
+
process.stdout.write(boxTop('You', P.border) + '\n');
|
|
154
141
|
// Word-wrap the question to fit inside the box
|
|
155
142
|
const maxW = inner();
|
|
156
143
|
const words = question.split(' ');
|
|
157
144
|
let line = '';
|
|
158
145
|
for (const word of words) {
|
|
159
146
|
if (line.length + word.length + 1 > maxW && line) {
|
|
160
|
-
process.stdout.write(boxRow(P.white(line), P.
|
|
147
|
+
process.stdout.write(boxRow(P.white(line), P.border) + '\n');
|
|
161
148
|
line = word;
|
|
162
149
|
} else {
|
|
163
150
|
line = line ? line + ' ' + word : word;
|
|
164
151
|
}
|
|
165
152
|
}
|
|
166
|
-
if (line) process.stdout.write(boxRow(P.white(line), P.
|
|
167
|
-
process.stdout.write(boxBot(P.
|
|
153
|
+
if (line) process.stdout.write(boxRow(P.white(line), P.border) + '\n');
|
|
154
|
+
process.stdout.write(boxBot(P.border) + '\n');
|
|
168
155
|
}
|
|
169
156
|
|
|
170
157
|
// ── streamChat ────────────────────────────────────────────────────────────────
|
|
@@ -203,19 +190,17 @@ export async function streamChat(cfg, messages, context = {}, opts = {}) {
|
|
|
203
190
|
let rawAnswer = '';
|
|
204
191
|
let finalResult = null;
|
|
205
192
|
|
|
206
|
-
// Open the iCopilot response box
|
|
207
193
|
function ensureBoxOpen() {
|
|
208
194
|
if (boxOpen) return;
|
|
209
195
|
boxOpen = true;
|
|
210
196
|
process.stdout.write('\n' + boxTopOpen('iCopilot', P.border) + '\n');
|
|
211
197
|
}
|
|
212
198
|
|
|
213
|
-
// Ora spinner — sits on a │ prefixed line
|
|
214
199
|
const spinner = ora({
|
|
215
200
|
text: P.dim('Thinking…'),
|
|
216
201
|
color: 'blue',
|
|
217
202
|
spinner: 'dots',
|
|
218
|
-
prefixText: P.border('│') + '
|
|
203
|
+
prefixText: P.border('│') + ' ',
|
|
219
204
|
});
|
|
220
205
|
|
|
221
206
|
const stopSpinner = () => { if (spinner.isSpinning) spinner.stop(); };
|
|
@@ -224,8 +209,7 @@ export async function streamChat(cfg, messages, context = {}, opts = {}) {
|
|
|
224
209
|
if (reasoningOpen) return;
|
|
225
210
|
stopSpinner();
|
|
226
211
|
reasoningOpen = true;
|
|
227
|
-
process.stdout.write(boxLine('', P.border) + '\n');
|
|
228
|
-
process.stdout.write(boxLine(P.dim('╌ Reasoning ') + P.border('╌'.repeat(Math.max(0, inner() - 14))), P.border) + '\n');
|
|
212
|
+
process.stdout.write(boxLine(P.dim('reasoning'), P.border) + '\n');
|
|
229
213
|
process.stdout.write(boxLine(P.border('│ '), P.border));
|
|
230
214
|
}
|
|
231
215
|
|
|
@@ -240,7 +224,7 @@ export async function streamChat(cfg, messages, context = {}, opts = {}) {
|
|
|
240
224
|
|
|
241
225
|
function closeReasoning() {
|
|
242
226
|
if (!reasoningOpen) return;
|
|
243
|
-
process.stdout.write('\n'
|
|
227
|
+
process.stdout.write('\n');
|
|
244
228
|
reasoningOpen = false;
|
|
245
229
|
}
|
|
246
230
|
|
package/src/layout.js
CHANGED
|
@@ -1,16 +1,95 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
|
|
1
3
|
// ── Terminal width ─────────────────────────────────────────────────────────────
|
|
4
|
+
let cachedCols = 0;
|
|
5
|
+
let lastProbeAt = 0;
|
|
6
|
+
let fallbackCols = 0;
|
|
7
|
+
|
|
8
|
+
function toPosInt(v) {
|
|
9
|
+
const n = Number.parseInt(String(v ?? ''), 10);
|
|
10
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function streamCols(stream) {
|
|
14
|
+
const direct = toPosInt(stream?.columns);
|
|
15
|
+
if (direct) return direct;
|
|
16
|
+
|
|
17
|
+
if (typeof stream?.getWindowSize === 'function') {
|
|
18
|
+
try {
|
|
19
|
+
const win = stream.getWindowSize();
|
|
20
|
+
const fromWin = Array.isArray(win) ? toPosInt(win[0]) : toPosInt(win?.columns);
|
|
21
|
+
if (fromWin) return fromWin;
|
|
22
|
+
} catch {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function probeFallbackCols() {
|
|
29
|
+
if (fallbackCols > 0) return fallbackCols;
|
|
30
|
+
|
|
31
|
+
const candidates = [];
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
candidates.push(toPosInt(execSync('tput cols', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()));
|
|
35
|
+
} catch {}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const stty = execSync('stty size 2>/dev/null', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
|
|
39
|
+
const parts = stty.split(/\s+/);
|
|
40
|
+
candidates.push(toPosInt(parts[1]));
|
|
41
|
+
} catch {}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const mode = execSync('mode con', { stdio: ['ignore', 'pipe', 'ignore'] }).toString();
|
|
45
|
+
const m = mode.match(/Columns:\s*(\d+)/i);
|
|
46
|
+
candidates.push(toPosInt(m?.[1]));
|
|
47
|
+
} catch {}
|
|
48
|
+
|
|
49
|
+
fallbackCols = Math.max(0, ...candidates.filter(Boolean));
|
|
50
|
+
return fallbackCols;
|
|
51
|
+
}
|
|
52
|
+
|
|
2
53
|
export function termCols() {
|
|
3
|
-
|
|
4
|
-
if (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
if (cachedCols > 0 && now - lastProbeAt < 250) return cachedCols;
|
|
56
|
+
|
|
57
|
+
const envCols = toPosInt(process.env.COLUMNS || process.env.TERM_WIDTH);
|
|
58
|
+
if (envCols) {
|
|
59
|
+
cachedCols = envCols;
|
|
60
|
+
lastProbeAt = now;
|
|
61
|
+
return envCols;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const stdoutCols = streamCols(process.stdout);
|
|
65
|
+
if (stdoutCols) {
|
|
66
|
+
cachedCols = stdoutCols;
|
|
67
|
+
lastProbeAt = now;
|
|
68
|
+
return stdoutCols;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const stderrCols = streamCols(process.stderr);
|
|
72
|
+
if (stderrCols) {
|
|
73
|
+
cachedCols = stderrCols;
|
|
74
|
+
lastProbeAt = now;
|
|
75
|
+
return stderrCols;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const probed = probeFallbackCols();
|
|
79
|
+
cachedCols = probed || 120;
|
|
80
|
+
lastProbeAt = now;
|
|
81
|
+
return cachedCols;
|
|
8
82
|
}
|
|
9
83
|
export const cols = () => Math.max(40, termCols());
|
|
10
84
|
export const inner = () => cols() - 4; // usable width inside │ … │
|
|
11
85
|
export const onResize = (cb) => {
|
|
12
|
-
|
|
13
|
-
|
|
86
|
+
const wrapped = () => {
|
|
87
|
+
cachedCols = 0;
|
|
88
|
+
fallbackCols = 0;
|
|
89
|
+
cb();
|
|
90
|
+
};
|
|
91
|
+
process.stdout.on('resize', wrapped);
|
|
92
|
+
process.stderr.on('resize', wrapped);
|
|
14
93
|
};
|
|
15
94
|
|
|
16
95
|
// ── ANSI-aware string helpers ─────────────────────────────────────────────────
|