ispbills-icli 8.7.6 → 8.7.7
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/tui/components.js +101 -114
package/package.json
CHANGED
package/src/tui/components.js
CHANGED
|
@@ -299,8 +299,9 @@ const SUBAGENT = /^\s*[✓✗x]?\s*\[(\d+)\/(\d+)\]\s*(.+?)\s*[::]\s*(.+?)\s*$
|
|
|
299
299
|
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;
|
|
300
300
|
|
|
301
301
|
/**
|
|
302
|
-
* Classify a status line
|
|
303
|
-
*
|
|
302
|
+
* Classify a status line into op type, a dim type-tag, and display parts.
|
|
303
|
+
* Tag is metadata only (file ext, device protocol, service) — never colored.
|
|
304
|
+
* Op type drives color: read=green, edit=red, step=blue.
|
|
304
305
|
*/
|
|
305
306
|
function parseToolLine(lineStr) {
|
|
306
307
|
const s = String(lineStr).trim();
|
|
@@ -313,73 +314,72 @@ function parseToolLine(lineStr) {
|
|
|
313
314
|
const ci = body.indexOf(':');
|
|
314
315
|
const action = (ci > -1 ? body.slice(0, ci) : body).trim();
|
|
315
316
|
const subject = (ci > -1 ? body.slice(ci + 1) : '').trim();
|
|
316
|
-
const
|
|
317
|
+
const aL = action.toLowerCase();
|
|
318
|
+
const sL = subject.toLowerCase();
|
|
317
319
|
|
|
318
|
-
// ── Op type
|
|
320
|
+
// ── Op type + descriptive verb (from action keywords) ────────────────────
|
|
319
321
|
let opType = 'other';
|
|
320
322
|
let verb = '';
|
|
321
|
-
if
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// ── Type tag from subject
|
|
323
|
+
if (/running on\b/i.test(aL)) { opType = 'edit'; verb = 'Exec'; }
|
|
324
|
+
else if (/\b(save|saving|generat|rollback|write|creat|update|apply)\b/i.test(aL)) { opType = 'edit'; verb = aL.includes('save') || aL.includes('saving') ? 'Save' : aL.includes('generat') ? 'Generate' : aL.includes('rollback') ? 'Rollback' : 'Edit'; }
|
|
325
|
+
else if (/\b(search|searching)\b/i.test(aL)) { opType = 'read'; verb = 'Search'; }
|
|
326
|
+
else if (/\b(lookup|look.?up|looking)\b/i.test(aL)) { opType = 'read'; verb = 'Lookup'; }
|
|
327
|
+
else if (/\b(check|checking|analys|correlat|simulat|predict|retrieve)\b/i.test(aL)) { opType = 'read'; verb = 'Check'; }
|
|
328
|
+
else if (/\b(fetch|reading|read)\b/i.test(aL)) { opType = 'read'; verb = 'Read'; }
|
|
329
|
+
else if (/\b(assign|spawn|sub.?agent|delegat)\b/i.test(aL)) { opType = 'step'; verb = 'Spawn'; }
|
|
330
|
+
|
|
331
|
+
// ── Type tag — ONLY from explicit context; never inherit device from subject ──
|
|
332
|
+
// Tag identifies the protocol/format involved, not the device mentioned.
|
|
330
333
|
let typeTag = '';
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
else if (/\.
|
|
335
|
-
else if (/\.
|
|
336
|
-
else if (/\.
|
|
337
|
-
else if (/\.(
|
|
338
|
-
else if (/\.(
|
|
339
|
-
else if (
|
|
340
|
-
|
|
341
|
-
else if (/
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
else if (/
|
|
348
|
-
else if (/
|
|
349
|
-
else if (/
|
|
350
|
-
|
|
351
|
-
|
|
334
|
+
// File extensions in subject or action (most specific)
|
|
335
|
+
const allText = aL + ' ' + sL;
|
|
336
|
+
if (/\.(js|jsx|mjs|cjs)\b/.test(allText)) typeTag = 'JS';
|
|
337
|
+
else if (/\.(ts|tsx)\b/.test(allText)) typeTag = 'TS';
|
|
338
|
+
else if (/\.php\b/.test(allText)) typeTag = 'PHP';
|
|
339
|
+
else if (/\.py\b/.test(allText)) typeTag = 'PY';
|
|
340
|
+
else if (/\.(rb)\b/.test(allText)) typeTag = 'RB';
|
|
341
|
+
else if (/\.(go)\b/.test(allText)) typeTag = 'GO';
|
|
342
|
+
else if (/\.(sh|bash)\b/.test(allText)) typeTag = 'SH';
|
|
343
|
+
// Device protocol — ONLY when action is a device exec ("running on")
|
|
344
|
+
else if (/running on/i.test(aL)) {
|
|
345
|
+
if (/nas#|mikrotik|\/export|\/ip |\/ppp|\/interface|\/system|\/queue|\/routing/.test(allText)) typeTag = 'ROS';
|
|
346
|
+
else if (/olt#|gpon|epon|optical/.test(allText)) typeTag = 'OLT';
|
|
347
|
+
else typeTag = 'DEV';
|
|
348
|
+
}
|
|
349
|
+
// Action-based tags
|
|
350
|
+
else if (/github/i.test(aL)) typeTag = 'GIT';
|
|
351
|
+
else if (/web|url|page|http/i.test(aL)) typeTag = 'WEB';
|
|
352
|
+
else if (/customer|subscriber/i.test(aL)) typeTag = 'CRM';
|
|
353
|
+
else if (/firmware|version/i.test(aL)) typeTag = 'FW';
|
|
354
|
+
else if (/sub.?agent|specialist|assign/i.test(aL)) typeTag = 'AI';
|
|
355
|
+
else if (/shell|command/i.test(aL)) typeTag = 'SH';
|
|
356
|
+
else if (/checkpoint|backup/i.test(aL)) typeTag = 'CFG';
|
|
357
|
+
else if (/config/i.test(aL)) typeTag = 'CFG';
|
|
358
|
+
else if (/docs|documentation/i.test(aL)) typeTag = 'DOC';
|
|
359
|
+
else if (/signal|optical|dbm/i.test(aL)) typeTag = 'OPT';
|
|
360
|
+
else if (/device|router|nas|olt/i.test(aL)) typeTag = 'DEV';
|
|
361
|
+
|
|
362
|
+
// Display text: verb + detail
|
|
363
|
+
// When action has no colon split, subject is empty — show trimmed action as detail
|
|
364
|
+
const display = subject || action;
|
|
365
|
+
|
|
366
|
+
return { opType, typeTag, verb, action, subject, display, done, ok, full: s };
|
|
352
367
|
}
|
|
353
368
|
|
|
354
369
|
export function StatusLines({ lines = [], busy = false, navigable = false }) {
|
|
355
370
|
const [sel, setSel] = useState(-1);
|
|
356
371
|
const [expanded, setExpanded] = useState(new Set());
|
|
357
372
|
|
|
358
|
-
|
|
359
|
-
useEffect(() => {
|
|
360
|
-
setSel(-1);
|
|
361
|
-
setExpanded(new Set());
|
|
362
|
-
}, [lines.length]);
|
|
373
|
+
useEffect(() => { setSel(-1); setExpanded(new Set()); }, [lines.length]);
|
|
363
374
|
|
|
364
375
|
useInput((input, key) => {
|
|
365
376
|
if (!navigable) return;
|
|
366
|
-
if (key.upArrow
|
|
367
|
-
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
if (key.downArrow || input === 'j') {
|
|
371
|
-
setSel((i) => Math.min(lines.length - 1, (i < 0 ? -1 : i) + 1));
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
377
|
+
if (key.upArrow || input === 'k') { setSel((i) => Math.max(0, (i < 0 ? lines.length - 1 : i) - 1)); return; }
|
|
378
|
+
if (key.downArrow || input === 'j') { setSel((i) => Math.min(lines.length - 1, (i < 0 ? -1 : i) + 1)); return; }
|
|
374
379
|
if ((key.return || input === ' ') && sel >= 0) {
|
|
375
|
-
setExpanded((prev) => {
|
|
376
|
-
const next = new Set(prev);
|
|
377
|
-
next.has(sel) ? next.delete(sel) : next.add(sel);
|
|
378
|
-
return next;
|
|
379
|
-
});
|
|
380
|
+
setExpanded((prev) => { const n = new Set(prev); n.has(sel) ? n.delete(sel) : n.add(sel); return n; });
|
|
380
381
|
return;
|
|
381
382
|
}
|
|
382
|
-
// Esc clears selection
|
|
383
383
|
if (key.escape) { setSel(-1); return; }
|
|
384
384
|
});
|
|
385
385
|
|
|
@@ -389,101 +389,88 @@ export function StatusLines({ lines = [], busy = false, navigable = false }) {
|
|
|
389
389
|
return html`
|
|
390
390
|
<${Box} flexDirection="column" marginTop=${1}>
|
|
391
391
|
${lines.map((line, i) => {
|
|
392
|
-
const lineStr
|
|
392
|
+
const lineStr = String(line);
|
|
393
393
|
const isRunning = busy && i === lastIdx;
|
|
394
|
-
const isSel
|
|
395
|
-
const isExp
|
|
394
|
+
const isSel = navigable && sel === i;
|
|
395
|
+
const isExp = expanded.has(i);
|
|
396
396
|
|
|
397
|
-
// ── Sub-agent tree line
|
|
397
|
+
// ── Sub-agent tree line ─────────────────────────────────────────
|
|
398
398
|
const sm = lineStr.match(SUBAGENT);
|
|
399
399
|
if (sm) {
|
|
400
400
|
const [, idx, total, host, result] = sm;
|
|
401
401
|
const isCompleted = /^\s*[✓✗x]/.test(lineStr);
|
|
402
|
-
const running
|
|
402
|
+
const running = busy && !isCompleted;
|
|
403
403
|
const good = !/fail|error|down|offline|unreachable|timeout|deny/i.test(result);
|
|
404
404
|
const branch = Number(idx) === Number(total) ? glyphs.branchEnd : glyphs.branchMid;
|
|
405
|
-
const hostTrunc = host.length > 22 ? host.slice(0, 20) + '…' : host;
|
|
406
|
-
const resultTrunc = result.length > 30 ? result.slice(0, 28) + '…' : result;
|
|
407
405
|
return html`
|
|
408
|
-
<${Box} key=${
|
|
409
|
-
<${
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
: html`<${Text} color=${good ? C.success : C.error} bold>${good ? glyphs.check : glyphs.cross} ${resultTrunc}<//>`}
|
|
418
|
-
<//>
|
|
406
|
+
<${Box} key=${`sa${i}`}>
|
|
407
|
+
<${Text} color=${C.muted}> <//>
|
|
408
|
+
<${Text} color=${C.accent}>${branch} <//>
|
|
409
|
+
<${Text} color=${C.muted}>${idx}/${total} <//>
|
|
410
|
+
<${Text} color=${C.white} bold>${host.slice(0, 20)}<//>
|
|
411
|
+
<${Text} color=${C.muted}> ${dash()} <//>
|
|
412
|
+
${running
|
|
413
|
+
? html`<${Text} color=${C.warning}><${Spinner} type=${spinnerType()} /><//> <${Text} color=${C.muted}>running<//>`
|
|
414
|
+
: html`<${Text} color=${good ? C.success : C.error} bold>${good ? glyphs.check : glyphs.cross} ${result.slice(0, 32)}<///>`}
|
|
419
415
|
<//>`;
|
|
420
416
|
}
|
|
421
417
|
|
|
422
|
-
// ── Regular tool status line
|
|
423
|
-
const { opType, typeTag, verb,
|
|
418
|
+
// ── Regular tool status line ────────────────────────────────────
|
|
419
|
+
const { opType, typeTag, verb, display, done, ok, full } = parseToolLine(lineStr);
|
|
424
420
|
|
|
425
421
|
const opColor = opType === 'read' ? C.success
|
|
426
|
-
: opType === 'edit'
|
|
427
|
-
: opType === 'step'
|
|
422
|
+
: opType === 'edit' ? C.error
|
|
423
|
+
: opType === 'step' ? C.brand
|
|
428
424
|
: C.muted;
|
|
429
425
|
|
|
430
|
-
//
|
|
431
|
-
const
|
|
426
|
+
// Type tag: always dim/muted — it's just metadata, never colored
|
|
427
|
+
const tag = typeTag ? typeTag.slice(0, 3).padEnd(3) : ' ';
|
|
432
428
|
|
|
433
|
-
//
|
|
434
|
-
const
|
|
429
|
+
// Verb label: padded to 8 chars for alignment
|
|
430
|
+
const verbLabel = (verb || 'Run').slice(0, 8).padEnd(8);
|
|
435
431
|
|
|
436
|
-
//
|
|
437
|
-
const
|
|
438
|
-
const actionTrunc = action.length > 28 ? action.slice(0, 26) + '…' : action;
|
|
432
|
+
// Detail text: as much as fits
|
|
433
|
+
const detail = display.length > 52 ? display.slice(0, 50) + '…' : display;
|
|
439
434
|
|
|
440
|
-
// Status
|
|
435
|
+
// Status icon
|
|
441
436
|
const statusIcon = isRunning
|
|
442
437
|
? html`<${Text} color=${C.warning}><${Spinner} type=${spinnerType()} /><//>`
|
|
443
|
-
: done && ok ? html`<${Text} color=${C.success}> ${glyphs.check}<//>`
|
|
444
|
-
: done && !ok ? html`<${Text} color=${C.error}> ${glyphs.cross}<//>`
|
|
438
|
+
: (done && ok) ? html`<${Text} color=${C.success}> ${glyphs.check}<//>`
|
|
439
|
+
: (done && !ok) ? html`<${Text} color=${C.error}> ${glyphs.cross}<//>`
|
|
445
440
|
: null;
|
|
446
441
|
|
|
447
|
-
// Expand toggle hint for navigable lines
|
|
448
|
-
const expHint = navigable && isSel
|
|
449
|
-
? html`<${Text} color=${C.muted} dimColor> [${isExp ? '−' : '+'}]<//>` : null;
|
|
450
|
-
|
|
451
442
|
return html`
|
|
452
|
-
<${Box} key=${
|
|
453
|
-
|
|
454
|
-
|
|
443
|
+
<${Box} key=${`sl${i}`} flexDirection="column">
|
|
444
|
+
<${Box}>
|
|
445
|
+
<${Text} color=${C.muted}> <//>
|
|
446
|
+
<!-- Type tag: dim neutral — NOT colored by op type -->
|
|
447
|
+
<${Text} color=${C.muted} dimColor>${tag}<//>
|
|
455
448
|
<${Text} color=${C.muted}> <//>
|
|
456
|
-
|
|
457
|
-
<${Text} color=${
|
|
458
|
-
|
|
459
|
-
<${Text} color=${
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
: html`<${Text} color=${C.muted} bold=${opType === 'read'}> ${actionTrunc}<//>` }
|
|
449
|
+
<!-- Colored dot — the ONLY colored part -->
|
|
450
|
+
<${Text} color=${opColor}>● <//>
|
|
451
|
+
<!-- Verb: bold for reads, normal otherwise -->
|
|
452
|
+
<${Text} color=${C.white} bold=${opType === 'read'}>${verbLabel}<//>
|
|
453
|
+
<!-- Detail: plain white -->
|
|
454
|
+
<${Text} color=${C.white} dimColor> ${detail}<//>
|
|
463
455
|
${statusIcon}
|
|
464
|
-
${
|
|
456
|
+
${isSel ? html`<${Text} color=${C.muted} dimColor> [${isExp ? '−' : '+'}]<//>` : null}
|
|
465
457
|
<//>
|
|
466
|
-
|
|
467
|
-
<!-- Expanded detail -->
|
|
468
458
|
${isExp ? html`
|
|
469
|
-
<${Box} flexDirection="column"
|
|
470
|
-
<${Text} color=${C.separator}>${'┌' + '─'.repeat(
|
|
471
|
-
<${Box}
|
|
472
|
-
<${Text} color=${C.
|
|
473
|
-
<${Text} color=${C.muted} italic>${lineStr.slice(0, 120)}<//>
|
|
459
|
+
<${Box} flexDirection="column" paddingLeft=${8}>
|
|
460
|
+
<${Text} color=${C.separator}>${'┌' + '─'.repeat(48)}<//>
|
|
461
|
+
<${Box}><${Text} color=${C.separator}>│ <//>
|
|
462
|
+
<${Text} color=${C.muted}>${full.slice(0, 100)}<//>
|
|
474
463
|
<//>
|
|
475
|
-
${
|
|
476
|
-
<${
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
<//>` : null}
|
|
480
|
-
<${Text} color=${C.separator}>${'└' + '─'.repeat(50)}<//>
|
|
464
|
+
${full.length > 100 ? html`<${Box}><${Text} color=${C.separator}>│ <//>
|
|
465
|
+
<${Text} color=${C.muted}>${full.slice(100, 200)}<//>
|
|
466
|
+
<//>` : null}
|
|
467
|
+
<${Text} color=${C.separator}>${'└' + '─'.repeat(48)}<//>
|
|
481
468
|
<//>` : null}
|
|
482
469
|
<//>`;
|
|
483
470
|
})}
|
|
484
471
|
${navigable && lines.length > 1
|
|
485
472
|
? html`<${Box} paddingLeft=${2} marginTop=${0}>
|
|
486
|
-
<${Text} color=${C.muted} dimColor>j/k
|
|
473
|
+
<${Text} color=${C.muted} dimColor>j/k select Enter expand Esc dismiss<//>
|
|
487
474
|
<//>`
|
|
488
475
|
: null}
|
|
489
476
|
<//>`;
|