ispbills-icli 8.7.7 → 8.7.8
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/package.json +1 -1
- package/src/banner.js +3 -3
- package/src/commands.js +3 -3
- package/src/tui/app.js +20 -10
- package/src/tui/components.js +27 -17
- package/src/tui/composer.js +1 -1
- package/src/tui/run.js +3 -3
- package/src/tui/theme.js +2 -0
package/package.json
CHANGED
package/src/banner.js
CHANGED
|
@@ -13,7 +13,7 @@ const LOGO = [
|
|
|
13
13
|
const LOGO_WIDTH = Math.max(...LOGO.map((l) => l.length));
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Full
|
|
16
|
+
* Full iPolot banner. On narrow terminals falls back to a compact header.
|
|
17
17
|
* A full-width rule frames the header so it uses the whole window.
|
|
18
18
|
*
|
|
19
19
|
* @param {object} cfg Loaded config ({ url, user, _model }).
|
|
@@ -26,9 +26,9 @@ export function printBanner(cfg = {}, opts = {}) {
|
|
|
26
26
|
console.log();
|
|
27
27
|
if (width >= LOGO_WIDTH) {
|
|
28
28
|
for (const line of LOGO) console.log(ACCENT + BOLD + line + RESET);
|
|
29
|
-
console.log(` ${DIM}
|
|
29
|
+
console.log(` ${DIM}iPolot — network engineer in your terminal${RESET}`);
|
|
30
30
|
} else {
|
|
31
|
-
console.log(` ${BOLD}
|
|
31
|
+
console.log(` ${BOLD}iPolot${RESET} ${DIM}— IspBills AI terminal${RESET}`);
|
|
32
32
|
}
|
|
33
33
|
console.log();
|
|
34
34
|
|
package/src/commands.js
CHANGED
|
@@ -88,7 +88,7 @@ export const SLASH_COMMANDS = [
|
|
|
88
88
|
{ cmd: '/list-dirs', hint: '', desc: 'Show all allowed directories', group: 'Permissions', local: true },
|
|
89
89
|
{ cmd: '/sandbox', hint: '[enable|disable]', desc: 'Enable/disable local sandboxing', group: 'Permissions', local: true },
|
|
90
90
|
{ cmd: '/experimental', hint: '[on|off|show]', desc: 'Toggle experimental features', group: 'Permissions', local: true },
|
|
91
|
-
{ cmd: '/init', hint: '', desc: 'Initialize
|
|
91
|
+
{ cmd: '/init', hint: '', desc: 'Initialize .github/copilot-instructions.md for this repo', group: 'Permissions', local: true },
|
|
92
92
|
|
|
93
93
|
// ── Agents & models ──
|
|
94
94
|
{ cmd: '/agent', hint: '', desc: 'Browse and select agents', group: 'Agents', local: true },
|
|
@@ -121,9 +121,9 @@ export const SLASH_COMMANDS = [
|
|
|
121
121
|
{ cmd: '/bug', hint: '', desc: 'Report a bug (alias for /feedback)', group: 'Help', local: true },
|
|
122
122
|
{ cmd: '/update', hint: '', desc: 'Update iCli to the latest version', group: 'Help', local: true },
|
|
123
123
|
{ cmd: '/downgrade', hint: 'VERSION', desc: 'Roll back to a specific CLI version', group: 'Help', local: true },
|
|
124
|
-
{ cmd: '/app', hint: '', desc: '
|
|
124
|
+
{ cmd: '/app', hint: '', desc: 'Show instructions to open the iPolot web app', group: 'Help', local: true },
|
|
125
125
|
{ cmd: '/user', hint: '', desc: 'Manage GitHub user list', group: 'Help', local: true },
|
|
126
|
-
{ cmd: '/login', hint: '', desc: 'Log in to
|
|
126
|
+
{ cmd: '/login', hint: '', desc: 'Log in to iPolot', group: 'Help', local: true },
|
|
127
127
|
{ cmd: '/whoami', hint: '', desc: 'Show current user info', group: 'Help', local: true },
|
|
128
128
|
{ cmd: '/clikit', hint: '[component]', desc: 'Preview CLI UI components', group: 'Help', local: true },
|
|
129
129
|
{ cmd: '/extensions', hint: '[manage|mode]', desc: 'Manage CLI extensions', group: 'Help', local: true },
|
package/src/tui/app.js
CHANGED
|
@@ -69,6 +69,8 @@ function approxTokens(messages) {
|
|
|
69
69
|
|
|
70
70
|
const TOKEN_LIMIT = 100000;
|
|
71
71
|
const SESSION_SORTS = ['relevance', 'created', 'name', 'last used'];
|
|
72
|
+
const TOOL_APPROVAL_ONCE = 'yes, proceed with this tool call';
|
|
73
|
+
const TOOL_APPROVAL_SESSION = '__session_allow__';
|
|
72
74
|
|
|
73
75
|
function sessionAutoName(messages = []) {
|
|
74
76
|
const firstUser = messages.find((m) => m?.role === 'user' && String(m.content || '').trim());
|
|
@@ -899,9 +901,9 @@ export function App({ cfg, display = {}, onExternal, onExit, initialQuestion, sh
|
|
|
899
901
|
const toolArgs = reply.toolCall?.args || reply.toolCall?.command || '';
|
|
900
902
|
const toolDesc = toolArgs ? `${toolName}: ${toolArgs}` : (reply.note || toolName);
|
|
901
903
|
choiceActionRef.current = (value) => {
|
|
902
|
-
if (value ===
|
|
904
|
+
if (value === TOOL_APPROVAL_SESSION) {
|
|
903
905
|
cfg.allowedTools = [...(cfg.allowedTools || []), toolName];
|
|
904
|
-
runTurn(
|
|
906
|
+
runTurn(TOOL_APPROVAL_ONCE);
|
|
905
907
|
return;
|
|
906
908
|
}
|
|
907
909
|
if (value != null) runTurn(String(value));
|
|
@@ -912,9 +914,9 @@ export function App({ cfg, display = {}, onExternal, onExit, initialQuestion, sh
|
|
|
912
914
|
sel: 0, text: '', focusText: false, allowText: true,
|
|
913
915
|
isToolApproval: true,
|
|
914
916
|
options: [
|
|
915
|
-
{ label: '1. Yes', value:
|
|
916
|
-
{ label:
|
|
917
|
-
{ label: '3. No, and tell
|
|
917
|
+
{ label: '1. Yes', value: TOOL_APPROVAL_ONCE },
|
|
918
|
+
{ label: `2. Yes, and approve ${toolName} for the rest of the session`, value: TOOL_APPROVAL_SESSION },
|
|
919
|
+
{ label: '3. No, and tell iPolot what to do differently (Esc)', value: null },
|
|
918
920
|
],
|
|
919
921
|
});
|
|
920
922
|
} else if (type === 'prompt' || type === 'choice') {
|
|
@@ -1449,7 +1451,7 @@ export function App({ cfg, display = {}, onExternal, onExit, initialQuestion, sh
|
|
|
1449
1451
|
if (q === '/init') {
|
|
1450
1452
|
const fp = resolve(process.cwd(), '.github/copilot-instructions.md');
|
|
1451
1453
|
if (existsSync(fp)) {
|
|
1452
|
-
pushNotice(
|
|
1454
|
+
pushNotice(`.github/copilot-instructions.md already exists at ${shortenPath(fp)}. Edit it directly.`);
|
|
1453
1455
|
} else {
|
|
1454
1456
|
runTurn('Create a .github/copilot-instructions.md file for this repository with sensible defaults for an ISP NOC tool.');
|
|
1455
1457
|
}
|
|
@@ -1683,7 +1685,7 @@ export function App({ cfg, display = {}, onExternal, onExit, initialQuestion, sh
|
|
|
1683
1685
|
return;
|
|
1684
1686
|
}
|
|
1685
1687
|
if (q === '/app') {
|
|
1686
|
-
pushNotice('
|
|
1688
|
+
pushNotice('iPolot web app — open your IspBills instance in the browser with /o or by typing the URL.', C.muted);
|
|
1687
1689
|
return;
|
|
1688
1690
|
}
|
|
1689
1691
|
if (q === '/user' || q.startsWith('/user ')) {
|
|
@@ -1774,7 +1776,7 @@ export function App({ cfg, display = {}, onExternal, onExit, initialQuestion, sh
|
|
|
1774
1776
|
if (value == null) {
|
|
1775
1777
|
if (currentChoice?.isToolApproval || /⚙ Tool request|tool request|approval|shell:/i.test(`${currentChoice?.title || ''} ${currentChoice?.note || ''}`)) {
|
|
1776
1778
|
setChoice({
|
|
1777
|
-
title: 'Tell
|
|
1779
|
+
title: 'Tell iPolot what to do differently:',
|
|
1778
1780
|
note: '',
|
|
1779
1781
|
sel: 0,
|
|
1780
1782
|
text: '',
|
|
@@ -1836,6 +1838,13 @@ export function App({ cfg, display = {}, onExternal, onExit, initialQuestion, sh
|
|
|
1836
1838
|
if (!choice) return;
|
|
1837
1839
|
if (sessionPicker || diffState) return;
|
|
1838
1840
|
if (key.escape || (key.ctrl && input === 'c')) { setChoice(null); return; }
|
|
1841
|
+
if (choice.isToolApproval && !choice.focusText) {
|
|
1842
|
+
const yesValue = choice.options.find((o) => o?.value === TOOL_APPROVAL_ONCE)?.value;
|
|
1843
|
+
const alwaysValue = choice.options.find((o) => o?.value === TOOL_APPROVAL_SESSION)?.value;
|
|
1844
|
+
if (input === 'y') { pickChoice(yesValue); return; }
|
|
1845
|
+
if (input === 'a') { pickChoice(alwaysValue); return; }
|
|
1846
|
+
if (input === 'n') { pickChoice(null); return; }
|
|
1847
|
+
}
|
|
1839
1848
|
if (key.tab && choice.allowText) { setChoice((c) => ({ ...c, focusText: !c.focusText })); return; }
|
|
1840
1849
|
if (key.upArrow) { setChoice((c) => ({ ...c, focusText: false, sel: Math.max(0, c.sel - 1) })); return; }
|
|
1841
1850
|
if (key.downArrow) {
|
|
@@ -2156,7 +2165,7 @@ export function App({ cfg, display = {}, onExternal, onExit, initialQuestion, sh
|
|
|
2156
2165
|
|
|
2157
2166
|
const liveArea = live
|
|
2158
2167
|
? html`<${Box} flexDirection="column">
|
|
2159
|
-
<${StatusLines} lines=${live.status} busy=${busy} />
|
|
2168
|
+
<${StatusLines} lines=${live.status} busy=${busy} cols=${cols} />
|
|
2160
2169
|
${live.reasoning ? html`<${Reasoning} text=${live.reasoning} show=${showReasoning} />` : null}
|
|
2161
2170
|
${live.text ? html`<${AssistantMessage} text=${live.text} cols=${cols} ts=${startedAt || Date.now()} streaming=${true} />` : null}
|
|
2162
2171
|
${live.reply?.steps ? html`<${Plan} reply=${live.reply} live=${true} />` : null}
|
|
@@ -2185,7 +2194,7 @@ export function App({ cfg, display = {}, onExternal, onExit, initialQuestion, sh
|
|
|
2185
2194
|
/>
|
|
2186
2195
|
${diffState.commentInput
|
|
2187
2196
|
? html`<${Choice}
|
|
2188
|
-
title=${`Tell
|
|
2197
|
+
title=${`Tell iPolot what stands out about ${diffState.commentInput.file}:${diffState.commentInput.lineNumber || '?'}`}
|
|
2189
2198
|
note=${diffState.commentInput.content}
|
|
2190
2199
|
sel=${0}
|
|
2191
2200
|
allowText=${true}
|
|
@@ -2258,6 +2267,7 @@ export function App({ cfg, display = {}, onExternal, onExit, initialQuestion, sh
|
|
|
2258
2267
|
clearToken=${composerClearToken}
|
|
2259
2268
|
/>`}
|
|
2260
2269
|
|
|
2270
|
+
<${Text} color=${C.separator}>${'─'.repeat(Math.max(1, cols))}<//>
|
|
2261
2271
|
<${Box} paddingX=${1} width=${cols}>
|
|
2262
2272
|
<${Box} flexGrow=${1}>
|
|
2263
2273
|
<${Text} color=${footerStatusColor} bold=${busy} wrap="truncate-end">${footerStatusLeft}<//>
|
package/src/tui/components.js
CHANGED
|
@@ -124,7 +124,7 @@ export function TabBar({ active = 'session', cols = 80 }) {
|
|
|
124
124
|
const label = ` ${i + 1} ${tab.label} `.padEnd(TAB_W);
|
|
125
125
|
return html`
|
|
126
126
|
<${Box} key=${tab.id} width=${TAB_W}>
|
|
127
|
-
<${Text} bold=${on} color=${on ?
|
|
127
|
+
<${Text} bold=${on} color=${on ? C.brand : C.muted} dimColor=${!on} inverse=${on}>
|
|
128
128
|
${label}
|
|
129
129
|
<//>
|
|
130
130
|
<//>`;
|
|
@@ -168,7 +168,7 @@ export function Banner({ cfg = {}, model, width = 80, compact = false, streamerM
|
|
|
168
168
|
if (compact) {
|
|
169
169
|
return html`
|
|
170
170
|
<${Box} marginBottom=${1}>
|
|
171
|
-
<${Text} color=${C.accent} bold>${glyphs.ring}
|
|
171
|
+
<${Text} color=${C.accent} bold>${glyphs.ring} iPolot <//>
|
|
172
172
|
<${Text} color=${C.muted}>${midDot()}<//>
|
|
173
173
|
<${Text} color=${C.muted}>${m}<//>
|
|
174
174
|
${cfg.user?.name ? html`<${Text} color=${C.muted}>${midDot()}<//>` : null}
|
|
@@ -183,7 +183,7 @@ export function Banner({ cfg = {}, model, width = 80, compact = false, streamerM
|
|
|
183
183
|
<//>
|
|
184
184
|
|
|
185
185
|
<${Box} marginTop=${1} justifyContent="center">
|
|
186
|
-
<${Text} color=${C.accent} bold>${glyphs.ring}
|
|
186
|
+
<${Text} color=${C.accent} bold>${glyphs.ring} iPolot<//>
|
|
187
187
|
<${Text} color=${C.muted}> ${dash()} IspBills network engineer in your terminal<//>
|
|
188
188
|
<//>
|
|
189
189
|
|
|
@@ -229,8 +229,9 @@ export function UserMessage({ text, cols = 80, ts }) {
|
|
|
229
229
|
const stamp = ts ? new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }) : '';
|
|
230
230
|
return html`
|
|
231
231
|
<${Box} flexDirection="column" marginBottom=${1}>
|
|
232
|
+
<${Sep} cols=${cols} />
|
|
232
233
|
<${Box} flexDirection="row" justifyContent="space-between" width=${cols} paddingX=${1}>
|
|
233
|
-
<${Text} bold color
|
|
234
|
+
<${Text} bold color=${C.brand}>You<//>
|
|
234
235
|
<${Text} color=${C.muted}>${stamp}<//>
|
|
235
236
|
<//>
|
|
236
237
|
<${Box} paddingX=${1} marginLeft=${2} flexWrap="wrap">
|
|
@@ -264,12 +265,14 @@ function StreamingText({ text = '' }) {
|
|
|
264
265
|
|
|
265
266
|
export function AssistantMessage({ text, cols = 80, ts, label, streaming = false }) {
|
|
266
267
|
const stamp = ts ? new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }) : '';
|
|
267
|
-
const speakerLabel = streaming ? 'iCopilot ●' : 'iCopilot';
|
|
268
268
|
const bodyWidth = Math.min(cols, 100);
|
|
269
269
|
return html`
|
|
270
270
|
<${Box} flexDirection="column" marginBottom=${1}>
|
|
271
|
+
<${Sep} cols=${cols} />
|
|
271
272
|
<${Box} flexDirection="row" justifyContent="space-between" width=${cols} paddingX=${1}>
|
|
272
|
-
<${
|
|
273
|
+
<${Box}>
|
|
274
|
+
<${Text} color=${C.accent} bold>${glyphs.copilotDot} <//><${Text} bold>iPolot${streaming ? ' ●' : ''}<//>
|
|
275
|
+
<//>
|
|
273
276
|
<${Text} color=${C.muted}>${stamp}<//>
|
|
274
277
|
<//>
|
|
275
278
|
<${Box} paddingX=${1} marginLeft=${2} flexDirection="column" width=${bodyWidth}>
|
|
@@ -279,18 +282,22 @@ export function AssistantMessage({ text, cols = 80, ts, label, streaming = false
|
|
|
279
282
|
}
|
|
280
283
|
|
|
281
284
|
export function ShellOutput({ command, output, cols = 80, ts, code = 0 }) {
|
|
285
|
+
const ok = code === 0;
|
|
286
|
+
const tone = ok ? C.success : C.error;
|
|
282
287
|
return html`
|
|
283
288
|
<${Box} flexDirection="column" marginTop=${1}>
|
|
289
|
+
<${Sep} cols=${cols} />
|
|
284
290
|
<${SpeakerLine}
|
|
285
291
|
label=${"$ Shell"}
|
|
286
|
-
color=${
|
|
292
|
+
color=${tone}
|
|
287
293
|
cols=${cols}
|
|
288
294
|
ts=${ts}
|
|
289
|
-
rightLabel=${
|
|
295
|
+
rightLabel=${ok ? '' : `exit ${code}`}
|
|
290
296
|
/>
|
|
291
297
|
<${Box} paddingX=${1} flexDirection="column">
|
|
292
298
|
<${Text} color=${C.warning}>$ ${command}<//>
|
|
293
|
-
<${Text}>${
|
|
299
|
+
<${Text} color=${tone}>${ok ? glyphs.check : `${glyphs.cross} exit ${code}` }<//>
|
|
300
|
+
<${Text} color=${ok ? C.white : tone}>${output || '(no output)'}<//>
|
|
294
301
|
<//>
|
|
295
302
|
<//>`;
|
|
296
303
|
}
|
|
@@ -386,6 +393,7 @@ export function StatusLines({ lines = [], busy = false, navigable = false }) {
|
|
|
386
393
|
if (!lines.length) return null;
|
|
387
394
|
const lastIdx = lines.length - 1;
|
|
388
395
|
|
|
396
|
+
|
|
389
397
|
return html`
|
|
390
398
|
<${Box} flexDirection="column" marginTop=${1}>
|
|
391
399
|
${lines.map((line, i) => {
|
|
@@ -654,15 +662,17 @@ export function Thinking({ label = 'Working', startedAt }) {
|
|
|
654
662
|
}
|
|
655
663
|
|
|
656
664
|
export function Choice({ title, note, options = [], sel = 0, allowText = false, text = '', focusText = false, isToolApproval = false, cols = 80 }) {
|
|
657
|
-
// §13: tool approval uses
|
|
665
|
+
// §13: tool approval uses separator-line style matching TUI.md spec exactly
|
|
658
666
|
if (isToolApproval) {
|
|
659
667
|
return html`
|
|
660
668
|
<${Box} flexDirection="column" marginTop=${1}>
|
|
661
|
-
<${
|
|
662
|
-
|
|
669
|
+
<${Sep} cols=${cols} />
|
|
670
|
+
<${Box} paddingX=${1} marginBottom=${1}>
|
|
671
|
+
<${Text} color=${C.accent} bold>${glyphs.settings} Tool request<//>
|
|
663
672
|
<//>
|
|
664
|
-
|
|
665
|
-
|
|
673
|
+
<${Sep} cols=${cols} />
|
|
674
|
+
${note ? html`<${Box} paddingX=${1} marginTop=${1} marginBottom=${1}><${Text} color=${C.white}>${note}<//><//>` : null}
|
|
675
|
+
<${Box} flexDirection="column" marginTop=${1} paddingX=${1}>
|
|
666
676
|
${options.map((o, i) => {
|
|
667
677
|
const on = !focusText && i === sel;
|
|
668
678
|
const label = o.label ?? o.value ?? String(o);
|
|
@@ -676,13 +686,13 @@ export function Choice({ title, note, options = [], sel = 0, allowText = false,
|
|
|
676
686
|
${allowText
|
|
677
687
|
? html`<${Box} key="freetext" marginTop=${1}>
|
|
678
688
|
<${Text} color=${focusText ? C.accent : C.muted}>${focusText ? glyphs.prompt + ' ' : ' '}<//>
|
|
679
|
-
<${Text} color=${C.muted}>Tell
|
|
689
|
+
<${Text} color=${C.muted}>Tell iPolot what to do differently: <//>
|
|
680
690
|
<${Text} color=${C.white}>${text}<//>${focusText ? html`<${Text} inverse> <//>` : null}
|
|
681
691
|
<//>`
|
|
682
692
|
: null}
|
|
683
693
|
<//>
|
|
684
|
-
<${Box} marginTop=${1} paddingX=${
|
|
685
|
-
<${Text} color=${C.muted}
|
|
694
|
+
<${Box} marginTop=${1} paddingX=${1}>
|
|
695
|
+
<${Text} color=${C.muted}>y / a / n ${midDot()} ${glyphs.up}${glyphs.down} move ${midDot()} Enter confirm ${midDot()} Esc cancel<//>
|
|
686
696
|
<//>
|
|
687
697
|
<//>`;
|
|
688
698
|
}
|
package/src/tui/composer.js
CHANGED
|
@@ -512,7 +512,7 @@ export function Composer({
|
|
|
512
512
|
<//>`
|
|
513
513
|
: null}
|
|
514
514
|
|
|
515
|
-
<${Text} color=${C.
|
|
515
|
+
<${Text} color=${C.accent}>${'_'.repeat(Math.max(1, width))}<//>
|
|
516
516
|
|
|
517
517
|
<${Box} width=${width} paddingX=${1} justifyContent="space-between">
|
|
518
518
|
<${Box} flexGrow=${1}>
|
package/src/tui/run.js
CHANGED
|
@@ -93,7 +93,7 @@ function exitAltScreen() {
|
|
|
93
93
|
altScreenActive = false;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
//
|
|
96
|
+
// iPolot wordmark in block letters
|
|
97
97
|
const LOGO = [
|
|
98
98
|
' ██╗ ██████╗ ██████╗ ██████╗ ██╗██╗ ██████╗ ████████╗',
|
|
99
99
|
' ██║██╔════╝██╔═══██╗██╔══██╗██║██║ ██╔═══██╗╚══██╔══╝',
|
|
@@ -145,8 +145,8 @@ async function animateSplash(cfg = {}) {
|
|
|
145
145
|
const R = '\x1b[0m';
|
|
146
146
|
|
|
147
147
|
const hr = `${SEP}${'─'.repeat(cols)}${R}`;
|
|
148
|
-
const subtitle = `${ACC}◈
|
|
149
|
-
const subtitleLen =
|
|
148
|
+
const subtitle = `${ACC}◈ iPolot${R} ${DIM}IspBills network engineer in your terminal${R}`;
|
|
149
|
+
const subtitleLen = subtitle.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
150
150
|
const subPad = ' '.repeat(Math.max(0, Math.floor((cols - subtitleLen) / 2)));
|
|
151
151
|
|
|
152
152
|
process.stdout.write(`\n${hr}\n`);
|
package/src/tui/theme.js
CHANGED
|
@@ -46,6 +46,7 @@ const ASCII_EXTRA = {
|
|
|
46
46
|
chip: '|', // chip separator
|
|
47
47
|
expand: '+', // expand indicator
|
|
48
48
|
collapse: '-', // collapse indicator
|
|
49
|
+
settings: '#', // tool-approval header (ASCII safe)
|
|
49
50
|
};
|
|
50
51
|
const UNICODE_EXTRA = {
|
|
51
52
|
copilotDot: '◈', // spec §6: ◈ Copilot speaker label (◈ in accent/purple)
|
|
@@ -56,6 +57,7 @@ const UNICODE_EXTRA = {
|
|
|
56
57
|
chip: '·',
|
|
57
58
|
expand: '▸',
|
|
58
59
|
collapse: '▾',
|
|
60
|
+
settings: '⚙', // spec §13: tool request header glyph
|
|
59
61
|
};
|
|
60
62
|
|
|
61
63
|
// Merged glyph proxy: own extras first, then ui.js base glyphs.
|