ispbills-icli 8.5.3 → 8.5.4
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 +2 -1
- package/package.json +1 -1
- package/src/commands.js +38 -35
- package/src/tui/app.js +508 -200
- package/src/tui/components.js +99 -48
- package/src/tui/composer.js +236 -72
- package/src/tui/run.js +34 -7
package/src/tui/components.js
CHANGED
|
@@ -15,9 +15,45 @@ const LOGO = [
|
|
|
15
15
|
'╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ',
|
|
16
16
|
];
|
|
17
17
|
|
|
18
|
+
const HELP_ROWS = [
|
|
19
|
+
['Shift+Tab', 'cycle ask / plan / autopilot'],
|
|
20
|
+
['Ctrl+S', 'run while preserving input'],
|
|
21
|
+
['Ctrl+Enter / Ctrl+Q', 'queue follow-up while busy'],
|
|
22
|
+
['Ctrl+G', 'edit prompt in external editor'],
|
|
23
|
+
['Ctrl+F', 'search timeline'],
|
|
24
|
+
['Ctrl+O / Ctrl+E', 'expand recent / all timeline items'],
|
|
25
|
+
['Ctrl+T', 'toggle reasoning'],
|
|
26
|
+
['Ctrl+R', 'reverse-search input history'],
|
|
27
|
+
['Ctrl+V', 'paste attachment help'],
|
|
28
|
+
['Ctrl+Z', 'suspend to background'],
|
|
29
|
+
['!', 'shell command / shell mode'],
|
|
30
|
+
['?', 'quick help overlay'],
|
|
31
|
+
['@mention', 'insert ISP entities'],
|
|
32
|
+
['#123', 'reference customer / ticket id'],
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
function formatTs(ts) {
|
|
36
|
+
if (!ts) return '';
|
|
37
|
+
try {
|
|
38
|
+
return new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
39
|
+
} catch {
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function SpeakerLine({ label, color, cols = 80, ts, rightLabel = '' }) {
|
|
45
|
+
const stamp = ts ? `[${formatTs(ts)}]` : '';
|
|
46
|
+
return html`
|
|
47
|
+
<${Box} paddingX=${2} marginTop=${1} width=${cols}>
|
|
48
|
+
<${Text} color=${color} bold>${label}<//>
|
|
49
|
+
<${Box} flexGrow=${1} />
|
|
50
|
+
${rightLabel ? html`<${Text} color=${C.muted}>${rightLabel}<//>` : null}
|
|
51
|
+
${rightLabel && stamp ? html`<${Text} color=${C.muted}> <//>` : null}
|
|
52
|
+
${stamp ? html`<${Text} color=${C.muted}>${stamp}<//>` : null}
|
|
53
|
+
<//>`;
|
|
54
|
+
}
|
|
55
|
+
|
|
18
56
|
// ── Tab Bar ─────────────────────────────────────────────────────────────────
|
|
19
|
-
// Matches the June 2026 Copilot CLI GA redesign: deep-blue track, active tab
|
|
20
|
-
// wrapped in [brackets], inactive tabs dimmed. Adapted for ISP NOC context.
|
|
21
57
|
const TABS = [
|
|
22
58
|
{ id: 'session', label: 'Session' },
|
|
23
59
|
{ id: 'devices', label: 'Devices' },
|
|
@@ -26,11 +62,8 @@ const TABS = [
|
|
|
26
62
|
];
|
|
27
63
|
|
|
28
64
|
export function TabBar({ active = 'session', cols = 80, mode = 'ask' }) {
|
|
29
|
-
// Deep-blue background track per spec (mirrors \x1b[44m ANSI blue).
|
|
30
|
-
// Active tab wrapped in [brackets] bold/white; inactive dimmed.
|
|
31
|
-
// Right side shows the current mode badge.
|
|
32
65
|
const modeBadge = mode !== 'ask'
|
|
33
|
-
? ` ${mode === 'autopilot' ? '⚡ AUTO' : '📋 PLAN'}`
|
|
66
|
+
? ` ${mode === 'autopilot' ? '⚡ AUTO' : mode === 'shell' ? '⌘ SHELL' : '📋 PLAN'}`
|
|
34
67
|
: '';
|
|
35
68
|
return html`
|
|
36
69
|
<${Box} width=${cols} backgroundColor="blue">
|
|
@@ -50,9 +83,6 @@ export function TabBar({ active = 'session', cols = 80, mode = 'ask' }) {
|
|
|
50
83
|
<//>`;
|
|
51
84
|
}
|
|
52
85
|
|
|
53
|
-
// ── Followup Chips ────────────────────────────────────────────────────────────
|
|
54
|
-
// Up to 4 suggested followup actions shown above the input after a response.
|
|
55
|
-
// Ctrl+N/P cycles; ↵ on empty input submits active; Esc clears.
|
|
56
86
|
export function FollowupChips({ chips = [], active = 0 }) {
|
|
57
87
|
if (!chips.length) return null;
|
|
58
88
|
return html`
|
|
@@ -70,12 +100,11 @@ export function FollowupChips({ chips = [], active = 0 }) {
|
|
|
70
100
|
<//>`;
|
|
71
101
|
}
|
|
72
102
|
|
|
73
|
-
|
|
74
|
-
export function Banner({ cfg = {}, model, width = 80, compact = false }) {
|
|
103
|
+
export function Banner({ cfg = {}, model, width = 80, compact = false, streamerMode = false }) {
|
|
75
104
|
const m = shortModel(model || cfg._model || 'IspBills AI');
|
|
76
105
|
const wide = !compact && width >= 60;
|
|
77
106
|
const meta = [
|
|
78
|
-
['model', m], ['user', cfg.user?.name], ['role', cfg.user?.role],
|
|
107
|
+
['model', streamerMode ? 'hidden' : m], ['user', cfg.user?.name], ['role', cfg.user?.role],
|
|
79
108
|
].filter(([, v]) => v);
|
|
80
109
|
|
|
81
110
|
if (compact) {
|
|
@@ -110,61 +139,64 @@ export function Banner({ cfg = {}, model, width = 80, compact = false }) {
|
|
|
110
139
|
<//>`;
|
|
111
140
|
}
|
|
112
141
|
|
|
113
|
-
/** Split text into spans, colouring @mentions. */
|
|
114
142
|
function withMentions(text, base = C.white) {
|
|
115
|
-
const parts = String(text).split(/(@[\w:.\-/]+)/g);
|
|
143
|
+
const parts = String(text).split(/(@[\w:.\-/]+|#\d+)/g);
|
|
116
144
|
return parts.map((p, i) =>
|
|
117
145
|
/^@[\w:.\-/]+$/.test(p)
|
|
118
146
|
? html`<${Text} key=${'mn' + i} color=${C.accent} bold>${p}<//>`
|
|
147
|
+
: /^#\d+$/.test(p)
|
|
148
|
+
? html`<${Text} key=${'mn' + i} color=${C.slash} bold>${p}<//>`
|
|
119
149
|
: html`<${Text} key=${'mn' + i} color=${base}>${p}<//>`,
|
|
120
150
|
);
|
|
121
151
|
}
|
|
122
152
|
|
|
123
|
-
// Full-width separator line using - (ASCII dash) per spec §6: "Full-width `-` characters in #30363D".
|
|
124
153
|
function Sep({ cols = 80 }) {
|
|
125
|
-
return html`<${Text} color=${C.separator}>${'-'.repeat(Math.max(1, cols))}<//>`;
|
|
154
|
+
return html`<${Text} color=${C.separator}>${'-'.repeat(Math.max(1, cols))}<//>`;
|
|
155
|
+
}
|
|
126
156
|
|
|
127
|
-
|
|
128
|
-
export function UserMessage({ text, cols = 80 }) {
|
|
157
|
+
export function UserMessage({ text, cols = 80, ts }) {
|
|
129
158
|
return html`
|
|
130
159
|
<${Box} flexDirection="column" marginTop=${1}>
|
|
131
160
|
<${Sep} cols=${cols} />
|
|
132
|
-
<${
|
|
133
|
-
<${Text} color=${C.brand} bold>You<//>
|
|
134
|
-
<//>
|
|
161
|
+
<${SpeakerLine} label="You" color=${C.brand} cols=${cols} ts=${ts} />
|
|
135
162
|
<${Box} paddingX=${2}>
|
|
136
163
|
<${Text}>${withMentions(text)}<//>
|
|
137
164
|
<//>
|
|
138
165
|
<//>`;
|
|
139
166
|
}
|
|
140
167
|
|
|
141
|
-
|
|
142
|
-
export function AssistantMessage({ text, cols = 80 }) {
|
|
143
|
-
const dot = glyphs.copilotDot;
|
|
168
|
+
export function AssistantMessage({ text, cols = 80, ts, label = `${glyphs.copilotDot} iCopilot` }) {
|
|
144
169
|
return html`
|
|
145
170
|
<${Box} flexDirection="column" marginTop=${1}>
|
|
146
171
|
<${Sep} cols=${cols} />
|
|
147
|
-
<${
|
|
148
|
-
<${Text} color=${C.accent} bold>${dot} iCopilot<//>
|
|
149
|
-
<//>
|
|
172
|
+
<${SpeakerLine} label=${label} color=${C.accent} cols=${cols} ts=${ts} />
|
|
150
173
|
<${Box} paddingX=${2}>
|
|
151
174
|
<${Markdown} content=${text} />
|
|
152
175
|
<//>
|
|
153
176
|
<//>`;
|
|
154
177
|
}
|
|
155
178
|
|
|
156
|
-
|
|
157
|
-
|
|
179
|
+
export function ShellOutput({ command, output, cols = 80, ts, code = 0 }) {
|
|
180
|
+
return html`
|
|
181
|
+
<${Box} flexDirection="column" marginTop=${1}>
|
|
182
|
+
<${Sep} cols=${cols} />
|
|
183
|
+
<${SpeakerLine}
|
|
184
|
+
label="⌘ Shell"
|
|
185
|
+
color=${code === 0 ? C.warning : C.error}
|
|
186
|
+
cols=${cols}
|
|
187
|
+
ts=${ts}
|
|
188
|
+
rightLabel=${code === 0 ? '' : `exit ${code}`}
|
|
189
|
+
/>
|
|
190
|
+
<${Box} paddingX=${2} flexDirection="column">
|
|
191
|
+
<${Text} color=${C.warning}>$ ${command}<//>
|
|
192
|
+
<${Text}>${output || '(no output)'}<//>
|
|
193
|
+
<//>
|
|
194
|
+
<//>`;
|
|
195
|
+
}
|
|
158
196
|
|
|
159
|
-
|
|
197
|
+
const SUBAGENT = /^\s*[✓✗x]?\s*\[(\d+)\/(\d+)\]\s*(.+?)\s*[::]\s*(.+?)\s*$/;
|
|
160
198
|
const LEAD_EMOJI = /^\s*([\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}\u{2190}-\u{21FF}\u{FE0F}\u{2049}\u{203C}]+)\s*/u;
|
|
161
199
|
|
|
162
|
-
/**
|
|
163
|
-
* Live server activity — tool calls and sub-agents, rendered Copilot-CLI style:
|
|
164
|
-
* each activity gets a status marker (spinner while it's the in-flight step,
|
|
165
|
-
* green check once done), the tool's purpose label is kept, and sub-agent
|
|
166
|
-
* progress is nested underneath its parent as a tree.
|
|
167
|
-
*/
|
|
168
200
|
export function StatusLines({ lines = [], busy = false }) {
|
|
169
201
|
if (!lines.length) return null;
|
|
170
202
|
const subLines = lines.filter((l) => SUBAGENT.test(l));
|
|
@@ -192,7 +224,6 @@ export function StatusLines({ lines = [], busy = false }) {
|
|
|
192
224
|
<//>`;
|
|
193
225
|
}
|
|
194
226
|
|
|
195
|
-
// Tool / activity line. The last line while busy is the in-flight step.
|
|
196
227
|
const running = busy && i === lastIdx;
|
|
197
228
|
const em = String(line).match(LEAD_EMOJI);
|
|
198
229
|
const body = em ? String(line).slice(em[0].length) : String(line).replace(/^[\s◈◆●○◎⇢→>*✓✗×!⚠·-]+/u, '');
|
|
@@ -213,10 +244,8 @@ export function StatusLines({ lines = [], busy = false }) {
|
|
|
213
244
|
<//>`;
|
|
214
245
|
}
|
|
215
246
|
|
|
216
|
-
/** Reasoning trace — collapsed to a single dim line; Ctrl+T to expand. */
|
|
217
247
|
export function Reasoning({ text, show = true }) {
|
|
218
248
|
if (!text) return null;
|
|
219
|
-
// Estimate token count (~4 chars per token as rough heuristic)
|
|
220
249
|
const tokenEst = Math.round(text.length / 4);
|
|
221
250
|
if (!show) {
|
|
222
251
|
return html`
|
|
@@ -231,7 +260,6 @@ export function Reasoning({ text, show = true }) {
|
|
|
231
260
|
<//>`;
|
|
232
261
|
}
|
|
233
262
|
|
|
234
|
-
/** A proposed remediation plan. */
|
|
235
263
|
export function Plan({ reply = {}, live = false }) {
|
|
236
264
|
const steps = reply.steps || [];
|
|
237
265
|
const dot3 = '…';
|
|
@@ -265,7 +293,6 @@ export function Plan({ reply = {}, live = false }) {
|
|
|
265
293
|
<//>`;
|
|
266
294
|
}
|
|
267
295
|
|
|
268
|
-
/** A device-connect card. */
|
|
269
296
|
export function Connect({ reply = {} }) {
|
|
270
297
|
const d = reply.device ?? reply ?? {};
|
|
271
298
|
return html`
|
|
@@ -279,12 +306,41 @@ export function Connect({ reply = {} }) {
|
|
|
279
306
|
<//>`;
|
|
280
307
|
}
|
|
281
308
|
|
|
282
|
-
/** A local notice (help hint, errors, etc.). */
|
|
283
309
|
export function Notice({ text, color = C.gray }) {
|
|
284
310
|
return html`<${Box} marginTop=${1}><${Text} color=${color}>${text}<//><//>`;
|
|
285
311
|
}
|
|
286
312
|
|
|
287
|
-
|
|
313
|
+
export function HelpOverlay({ cols = 80, ts }) {
|
|
314
|
+
return html`
|
|
315
|
+
<${Box} flexDirection="column" marginTop=${1} borderStyle=${borderStyle()} borderColor=${C.accent} paddingX=${1} width=${cols}>
|
|
316
|
+
<${SpeakerLine} label="? Help" color=${C.accent} cols=${Math.max(10, cols - 2)} ts=${ts} />
|
|
317
|
+
${HELP_ROWS.map(([key, desc]) => html`
|
|
318
|
+
<${Box} key=${key} paddingX=${1}>
|
|
319
|
+
<${Text} color=${C.success} bold>${key.padEnd(22)}<//>
|
|
320
|
+
<${Text} color=${C.muted}>${desc}<//>
|
|
321
|
+
<//>`)}
|
|
322
|
+
<//>`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function SearchOverlay({ query = '', matches = [], active = 0, cols = 80 }) {
|
|
326
|
+
const snippetWidth = Math.max(12, cols - 20);
|
|
327
|
+
return html`
|
|
328
|
+
<${Box} flexDirection="column" marginTop=${1} borderStyle=${borderStyle()} borderColor=${C.accent} paddingX=${1} width=${cols}>
|
|
329
|
+
<${Text} color=${C.accent} bold>Search timeline<//>
|
|
330
|
+
<${Text} color=${C.muted}>query: <//><${Text} color=${C.white}>${query || 'type to search'}<//>
|
|
331
|
+
<${Text} color=${C.muted}>matches: ${matches.length}${matches.length ? ` active ${active + 1}/${matches.length}` : ''}<//>
|
|
332
|
+
${matches.slice(0, 5).map((m, i) => {
|
|
333
|
+
const on = i === active;
|
|
334
|
+
return html`
|
|
335
|
+
<${Box} key=${m.id || i}>
|
|
336
|
+
<${Text} color=${on ? C.success : C.muted}>${on ? glyphs.prompt : ' '} <//>
|
|
337
|
+
<${Text} color=${on ? C.white : C.muted}>${String(m.preview || '').slice(0, snippetWidth)}<//>
|
|
338
|
+
<//>`;
|
|
339
|
+
})}
|
|
340
|
+
<${Text} color=${C.muted}>Enter expand Tab/↑/↓ cycle Esc close<//>
|
|
341
|
+
<//>`;
|
|
342
|
+
}
|
|
343
|
+
|
|
288
344
|
export function Thinking({ label = 'Working', startedAt }) {
|
|
289
345
|
const [, tick] = useState(0);
|
|
290
346
|
useEffect(() => {
|
|
@@ -300,11 +356,6 @@ export function Thinking({ label = 'Working', startedAt }) {
|
|
|
300
356
|
<//>`;
|
|
301
357
|
}
|
|
302
358
|
|
|
303
|
-
/**
|
|
304
|
-
* An interactive prompt — pick an option with ↑/↓ + Enter, or (when
|
|
305
|
-
* `allowText`) type a free-form answer. Mirrors GitHub Copilot CLI prompts.
|
|
306
|
-
* Presentational only: the parent owns `sel`/`textValue` and key handling.
|
|
307
|
-
*/
|
|
308
359
|
export function Choice({ title, note, options = [], sel = 0, allowText = false, text = '', focusText = false }) {
|
|
309
360
|
return html`
|
|
310
361
|
<${Box} flexDirection="column" marginTop=${1} borderStyle=${borderStyle()} borderColor=${C.accent} paddingX=${1}>
|