ispbills-icli 4.0.0 → 4.0.1

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 CHANGED
@@ -31,7 +31,7 @@ function printHelp() {
31
31
  process.stdout.write(boxTop('iCli · Help', P.border) + '\n');
32
32
 
33
33
  process.stdout.write(boxRow('', P.border) + '\n');
34
- process.stdout.write(boxRow(' ' + P.bold('Usage'), P.border) + '\n');
34
+ process.stdout.write(boxRow(P.bold('Usage'), P.border) + '\n');
35
35
  process.stdout.write(boxRow('', P.border) + '\n');
36
36
  [
37
37
  ['icli', 'Interactive REPL with slash commands'],
@@ -40,19 +40,19 @@ function printHelp() {
40
40
  ['icli whoami', 'Show current session info'],
41
41
  ['icli --help', 'Show this help'],
42
42
  ].forEach(([c, d]) =>
43
- process.stdout.write(boxRow(' ' + P.accent(c.padEnd(20)) + P.dim(d), P.border) + '\n')
43
+ process.stdout.write(boxRow(P.accent(c.padEnd(20)) + P.dim(d), P.border) + '\n')
44
44
  );
45
45
 
46
46
  process.stdout.write(boxMid(P.border) + '\n');
47
- process.stdout.write(boxRow(' ' + P.bold('Slash commands'), P.border) + '\n');
47
+ process.stdout.write(boxRow(P.bold('Slash commands'), P.border) + '\n');
48
48
  process.stdout.write(boxRow('', P.border) + '\n');
49
49
  for (const { cmd: c, hint, desc } of SLASH_COMMANDS) {
50
50
  const key = (c + (hint ? ' '+hint : '')).padEnd(22);
51
- process.stdout.write(boxRow(' ' + P.accent(key) + P.dim(desc), P.border) + '\n');
51
+ process.stdout.write(boxRow(P.accent(key) + P.dim(desc), P.border) + '\n');
52
52
  }
53
53
 
54
54
  process.stdout.write(boxMid(P.border) + '\n');
55
- process.stdout.write(boxRow(' ' + P.bold('Keyboard shortcuts'), P.border) + '\n');
55
+ process.stdout.write(boxRow(P.bold('Keyboard shortcuts'), P.border) + '\n');
56
56
  process.stdout.write(boxRow('', P.border) + '\n');
57
57
  [
58
58
  ['Tab', 'Autocomplete slash commands'],
@@ -61,7 +61,7 @@ function printHelp() {
61
61
  ['Ctrl+L', 'Clear screen'],
62
62
  ['Ctrl+C', 'Cancel / exit'],
63
63
  ].forEach(([k, d]) =>
64
- process.stdout.write(boxRow(' ' + P.warn(k.padEnd(14)) + P.dim(d), P.border) + '\n')
64
+ process.stdout.write(boxRow(P.warn(k.padEnd(14)) + P.dim(d), P.border) + '\n')
65
65
  );
66
66
  process.stdout.write(boxRow('', P.border) + '\n');
67
67
  process.stdout.write(boxBot(P.border) + '\n\n');
@@ -142,8 +142,8 @@ async function interactiveRepl(cfg) {
142
142
  } else {
143
143
  process.stdout.write('\n' + boxTop('History', P.border) + '\n');
144
144
  turns.forEach((m, i) =>
145
- process.stdout.write(boxRow(' ' + P.dim(String(i+1).padStart(2)+'.') + ' ' +
146
- chalk.white(m.content.slice(0, inner() - 8)), P.border) + '\n')
145
+ process.stdout.write(boxRow(P.dim(String(i+1).padStart(2)+'.') + ' ' +
146
+ chalk.white(m.content.slice(0, inner() - 5)), P.border) + '\n')
147
147
  );
148
148
  process.stdout.write(boxBot(P.border) + '\n\n');
149
149
  }
@@ -203,7 +203,7 @@ async function main() {
203
203
  [['url', cfg.url], ['name', cfg.user?.name], ['email', cfg.user?.email], ['role', cfg.user?.role]]
204
204
  .filter(([, v]) => v)
205
205
  .forEach(([k, v]) =>
206
- process.stdout.write(boxRow(' ' + P.dim(k.padEnd(7)) + chalk.white(v), P.border) + '\n')
206
+ process.stdout.write(boxRow(P.dim(k.padEnd(7)) + chalk.white(v), P.border) + '\n')
207
207
  );
208
208
  process.stdout.write(boxBot(P.border) + '\n\n');
209
209
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ispbills-icli",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "iCli — IspBills AI network engineer in your terminal",
5
5
  "keywords": [
6
6
  "ispbills",
package/src/chat.js CHANGED
@@ -69,7 +69,7 @@ export function printBanner(cfg, meta = {}) {
69
69
  process.stdout.write(boxTop('', P.border) + '\n');
70
70
 
71
71
  // Title row
72
- const title = P.bold(' iCopilot') + P.dim(' · IspBills AI Network Engineer');
72
+ const title = P.bold('iCopilot') + P.dim(' · IspBills AI Network Engineer');
73
73
  process.stdout.write(boxRow(title, P.border) + '\n');
74
74
 
75
75
  // Divider
@@ -81,19 +81,19 @@ export function printBanner(cfg, meta = {}) {
81
81
  if (cfg?.user?.name) row1parts.push(P.dim('user ') + P.white(cfg.user.name));
82
82
  if (cfg?.user?.role) row1parts.push(P.dim('role ') + P.white(cfg.user.role));
83
83
  if (row1parts.length) {
84
- process.stdout.write(boxRow(' ' + row1parts.join(P.dim(' · ')), P.border) + '\n');
84
+ process.stdout.write(boxRow(row1parts.join(P.dim(' · ')), P.border) + '\n');
85
85
  }
86
86
  if (cfg?.url) {
87
- const maxUrl = inner() - 8;
87
+ const maxUrl = inner() - 6;
88
88
  const url = cfg.url.length > maxUrl ? cfg.url.slice(0, maxUrl - 1) + '…' : cfg.url;
89
- process.stdout.write(boxRow(' ' + P.dim('url ') + P.accent(url), P.border) + '\n');
89
+ process.stdout.write(boxRow(P.dim('url ') + P.accent(url), P.border) + '\n');
90
90
  }
91
91
 
92
92
  // Divider
93
93
  process.stdout.write(boxMid(P.border) + '\n');
94
94
 
95
95
  // Hints row
96
- const hints = ' ' + [
96
+ const hints = [
97
97
  P.accentB('/') + P.muted(' commands'),
98
98
  P.accentB('Tab') + P.muted(' autocomplete'),
99
99
  P.accentB('↑↓') + P.muted(' history'),
@@ -124,7 +124,7 @@ export function printSlashMenu() {
124
124
  process.stdout.write(boxTop('Commands', P.border) + '\n');
125
125
  for (const { cmd: c, hint, desc } of SLASH_COMMANDS) {
126
126
  const key = P.accentB(c) + (hint ? P.dim(' ' + hint) : '');
127
- const line = ' ' + padRight(key, 22) + ' ' + P.dim(desc);
127
+ const line = padRight(key, 22) + ' ' + P.dim(desc);
128
128
  process.stdout.write(boxRow(line, P.border) + '\n');
129
129
  }
130
130
  process.stdout.write(boxBot(P.border) + '\n\n');
@@ -143,18 +143,18 @@ export function printUserBox(question) {
143
143
  process.stdout.write('\n');
144
144
  process.stdout.write(boxTop('You', P.accentB) + '\n');
145
145
  // Word-wrap the question to fit inside the box
146
- const maxW = inner() - 2;
146
+ const maxW = inner();
147
147
  const words = question.split(' ');
148
148
  let line = '';
149
149
  for (const word of words) {
150
150
  if (line.length + word.length + 1 > maxW && line) {
151
- process.stdout.write(boxRow(' ' + P.white(line), P.accentB) + '\n');
151
+ process.stdout.write(boxRow(P.white(line), P.accentB) + '\n');
152
152
  line = word;
153
153
  } else {
154
154
  line = line ? line + ' ' + word : word;
155
155
  }
156
156
  }
157
- if (line) process.stdout.write(boxRow(' ' + P.white(line), P.accentB) + '\n');
157
+ if (line) process.stdout.write(boxRow(P.white(line), P.accentB) + '\n');
158
158
  process.stdout.write(boxBot(P.accentB) + '\n');
159
159
  }
160
160
 
@@ -216,15 +216,15 @@ export async function streamChat(cfg, messages, context = {}, opts = {}) {
216
216
  stopSpinner();
217
217
  reasoningOpen = true;
218
218
  process.stdout.write(boxLine('', P.border) + '\n');
219
- process.stdout.write(boxLine(P.dim(' ╌ Reasoning ') + P.border('╌'.repeat(Math.max(0, inner() - 16))), P.border) + '\n');
220
- process.stdout.write(boxLine(P.border(' │ '), P.border));
219
+ process.stdout.write(boxLine(P.dim('╌ Reasoning ') + P.border('╌'.repeat(Math.max(0, inner() - 14))), P.border) + '\n');
220
+ process.stdout.write(boxLine(P.border('│ '), P.border));
221
221
  }
222
222
 
223
223
  function writeReasonDelta(delta) {
224
224
  if (!reasoningOpen) return;
225
225
  const parts = delta.split('\n');
226
226
  for (let i = 0; i < parts.length; i++) {
227
- if (i > 0) process.stdout.write('\n' + boxLine(P.border(' │ '), P.border));
227
+ if (i > 0) process.stdout.write('\n' + boxLine(P.border('│ '), P.border));
228
228
  if (parts[i]) process.stdout.write(P.reasoning(parts[i]));
229
229
  }
230
230
  }
@@ -241,7 +241,7 @@ export async function streamChat(cfg, messages, context = {}, opts = {}) {
241
241
  stopSpinner();
242
242
  const icon = statusIcon(line);
243
243
  const text = line.trimStart();
244
- const indent = /^[\s·•⌁]/.test(line) ? ' ' : ' ';
244
+ const indent = /^[\s·•⌁]/.test(line) ? ' ' : '';
245
245
  process.stdout.write(boxLine(indent + icon + ' ' + P.dim(text), P.border) + '\n');
246
246
  if (showThinking) spinner.start();
247
247
  }
@@ -304,38 +304,37 @@ export async function streamChat(cfg, messages, context = {}, opts = {}) {
304
304
  if (replyType === 'message') {
305
305
  const text = reply.text || answerBuf || '(no response)';
306
306
  const rendered = renderMarkdown(text);
307
- // Render each markdown line with │ prefix
308
307
  for (const l of rendered.split('\n')) {
309
- process.stdout.write(boxLine(' ' + l, P.border) + '\n');
308
+ process.stdout.write(boxLine(l, P.border) + '\n');
310
309
  }
311
310
 
312
311
  } else if (replyType === 'plan') {
313
- process.stdout.write(boxLine(' ' + P.bold('Plan'), P.border) + '\n');
314
- if (reply.summary) process.stdout.write(boxLine(' ' + P.dim(reply.summary), P.border) + '\n');
312
+ process.stdout.write(boxLine(P.bold('Plan'), P.border) + '\n');
313
+ if (reply.summary) process.stdout.write(boxLine(P.dim(reply.summary), P.border) + '\n');
315
314
  process.stdout.write(boxLine('', P.border) + '\n');
316
315
  (reply.steps || []).forEach((s, i) => {
317
316
  const risk = s.risk === 'high' ? P.err(' ⚠ high risk') :
318
317
  s.risk === 'medium' ? P.warn(' ⚡') : '';
319
- process.stdout.write(boxLine(` ${P.dim(String(i+1)+'.')} ${P.warn(s.cmd || '')}${risk}`, P.border) + '\n');
320
- if (s.purpose) process.stdout.write(boxLine(' ' + P.muted(s.purpose), P.border) + '\n');
318
+ process.stdout.write(boxLine(`${P.dim(String(i+1)+'.')} ${P.warn(s.cmd || '')}${risk}`, P.border) + '\n');
319
+ if (s.purpose) process.stdout.write(boxLine(' ' + P.muted(s.purpose), P.border) + '\n');
321
320
  });
322
321
  process.stdout.write(boxLine('', P.border) + '\n');
323
- process.stdout.write(boxLine(' ' + P.dim('Type ') + P.white('apply fix') + P.dim(' to confirm.'), P.border) + '\n');
322
+ process.stdout.write(boxLine(P.dim('Type ') + P.white('apply fix') + P.dim(' to confirm.'), P.border) + '\n');
324
323
 
325
324
  } else if (replyType === 'connect') {
326
325
  const dev = reply.device ?? {};
327
326
  process.stdout.write(
328
- boxLine(' ' + P.ok('🔌 ') + P.bold(`${dev.kind ?? '?'}#${dev.id ?? '?'}`) +
327
+ boxLine(P.ok('🔌 ') + P.bold(`${dev.kind ?? '?'}#${dev.id ?? '?'}`) +
329
328
  ' ' + P.white(dev.name ?? '') + ' ' + P.dim(`(${dev.host ?? ''})`), P.border) + '\n'
330
329
  );
331
- if (reply.note) process.stdout.write(boxLine(' ' + P.dim(reply.note), P.border) + '\n');
330
+ if (reply.note) process.stdout.write(boxLine(P.dim(reply.note), P.border) + '\n');
332
331
  }
333
332
 
334
333
  // Blank + footer line inside box
335
334
  process.stdout.write(boxLine('', P.border) + '\n');
336
335
  const foot = [meta.model ? shortModel(meta.model) : null, meta.user, meta.role].filter(Boolean);
337
336
  if (foot.length) {
338
- process.stdout.write(boxLine(' ' + P.muted(foot.join(' · ')), P.border) + '\n');
337
+ process.stdout.write(boxLine(P.muted(foot.join(' · ')), P.border) + '\n');
339
338
  }
340
339
 
341
340
  // Close the box
package/src/layout.js CHANGED
@@ -23,12 +23,17 @@ export const padRight = (s, n) => s + ' '.repeat(Math.max(0, n - visLen(s)));
23
23
  // draw(color, char, n) — repeat char n times with color
24
24
  const rep = (ch, n) => ch.repeat(Math.max(0, n));
25
25
 
26
- /** Full top border: ╭─ [label] ──────────────╮ */
26
+ /** Full top border: ╭─ [label] ──────────────╮
27
+ * ╭ (1) + ─ (1) + lbl (lbl.len) + dash + ╮ (1) = w
28
+ * → dash = w - 3 - lbl.length */
27
29
  export function boxTop(label, borderFn) {
28
- const w = cols();
29
- const lbl = label ? ` ${label} ` : '';
30
- const dash = rep('─', Math.max(0, w - 2 - lbl.length));
31
- return borderFn(`╭${lbl.length ? '─' + lbl : ''}${dash}╮`);
30
+ const w = cols();
31
+ const lbl = label ? ` ${label} ` : '';
32
+ if (lbl.length) {
33
+ const dash = rep('─', Math.max(0, w - 3 - lbl.length));
34
+ return borderFn(`╭─${lbl}${dash}╮`);
35
+ }
36
+ return borderFn('╭' + rep('─', w - 2) + '╮');
32
37
  }
33
38
 
34
39
  /** Divider inside box: ├────────────────────────┤ */
@@ -42,11 +47,11 @@ export function boxBot(borderFn) {
42
47
  }
43
48
 
44
49
  /** Open top border (no right cap — for streaming sections):
45
- * ╭─ [label] ────────── */
50
+ * ╭─ [label] ────────────────────────────── */
46
51
  export function boxTopOpen(label, borderFn) {
47
52
  const w = cols();
48
53
  const lbl = label ? ` ${label} ` : '';
49
- const dash = rep('─', Math.max(0, w - 1 - lbl.length));
54
+ const dash = rep('─', Math.max(0, w - 2 - lbl.length));
50
55
  return borderFn(`╭─${lbl}${dash}`);
51
56
  }
52
57
 
@@ -56,11 +61,12 @@ export function boxBotOpen(borderFn) {
56
61
  }
57
62
 
58
63
  /** One content row with left border and padded right border:
59
- * │ [content] */
64
+ * │ [content]
65
+ * 1 + 2 + visLen(content) + pad + 1 = cols() → pad = cols()-4-visLen(content) */
60
66
  export function boxRow(content, borderFn) {
61
- const w = cols();
62
- const pad = Math.max(0, w - 4 - visLen(content));
63
- return borderFn('│') + ' ' + content + ' '.repeat(pad) + ' ' + borderFn('│');
67
+ const w = cols();
68
+ const pad = Math.max(1, w - 4 - visLen(content));
69
+ return borderFn('│') + ' ' + content + ' '.repeat(pad) + borderFn('│');
64
70
  }
65
71
 
66
72
  /** Left-border only line (for streaming): │ [content] */