ispbills-icli 8.4.12 → 8.4.13

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.
@@ -20,7 +20,7 @@ const MENTIONS = [
20
20
  { tag: '@mac', desc: 'A MAC address' },
21
21
  ];
22
22
 
23
- export function Composer({ onSubmit, busy, width = 80, prefill = null, onPrefillConsumed = null }) {
23
+ export function Composer({ onSubmit, busy, width = 80 }) {
24
24
  const [value, setValue] = useState('');
25
25
  const [cursor, setCursor] = useState(0);
26
26
  const [history, setHistory] = useState([]);
@@ -40,18 +40,6 @@ export function Composer({ onSubmit, busy, width = 80, prefill = null, onPrefill
40
40
  lastMut.current = kind;
41
41
  };
42
42
 
43
- // When app.js injects a /paste prefill, load it into the input box once.
44
- useEffect(() => {
45
- if (!prefill) return;
46
- setValue(prefill);
47
- setCursor(prefill.length);
48
- undoRef.current = []; redoRef.current = []; lastMut.current = '';
49
- onPrefillConsumed?.();
50
- }, [prefill]); // eslint-disable-line react-hooks/exhaustive-deps
51
-
52
- // Ctrl+S stash: save the current prompt to restore later (GitHub Copilot CLI parity).
53
- const stashRef = useRef('');
54
-
55
43
  // Bracketed-paste assembly. A right-click / Cmd+V paste is wrapped by the
56
44
  // terminal in \x1b[200~ … \x1b[201~ and can arrive split across several
57
45
  // reads, so we buffer until the end marker before inserting.
@@ -155,18 +143,6 @@ export function Composer({ onSubmit, busy, width = 80, prefill = null, onPrefill
155
143
  return;
156
144
  }
157
145
 
158
- // ── Ctrl+S: stash / pop prompt (GitHub Copilot CLI parity) ──
159
- if (key.ctrl && input === 's') {
160
- if (value) {
161
- stashRef.current = value;
162
- setValue(''); setCursor(0);
163
- } else if (stashRef.current) {
164
- const v = stashRef.current; stashRef.current = '';
165
- setText(v, v.length);
166
- }
167
- return;
168
- }
169
-
170
146
  // ── Menu / history navigation ──
171
147
  if (key.upArrow) {
172
148
  if (menuOpen) { setSel((i) => Math.max(0, i - 1)); return; }
@@ -239,15 +215,6 @@ export function Composer({ onSubmit, busy, width = 80, prefill = null, onPrefill
239
215
  const sugTok = menuOpen && matches[sel] ? (slashMode ? matches[sel].cmd : matches[sel].tag) : '';
240
216
  const ghost = sugTok && sugTok.startsWith(token) && cursor >= value.length ? sugTok.slice(token.length) : '';
241
217
 
242
- // ── Shell mode: prefix '!' triggers yellow $ prompt + orange border ──
243
- const shellMode = value.startsWith('!') && !menuOpen;
244
- const borderColor = busy ? C.gray : shellMode ? C.orange : C.accent;
245
- const promptGlyph = shellMode ? '$ ' : glyphs.prompt + ' ';
246
- const promptColor = busy ? C.gray : shellMode ? C.orange : C.accent;
247
-
248
- // Number of lines in the buffer (for the line-count badge).
249
- const lineCount = value.split('\n').length;
250
-
251
218
  return html`
252
219
  <${Box} flexDirection="column" width=${width}>
253
220
  ${menuOpen
@@ -273,18 +240,16 @@ export function Composer({ onSubmit, busy, width = 80, prefill = null, onPrefill
273
240
  <//>`
274
241
  : null}
275
242
 
276
- <${Box} width=${width} borderStyle=${borderStyle()} borderColor=${borderColor} paddingX=${1}>
277
- <${Text} color=${promptColor}>${promptGlyph}<//>
243
+ <${Box} width=${width} borderStyle=${borderStyle()} borderColor=${busy ? C.gray : C.accent} paddingX=${1}>
244
+ <${Text} color=${busy ? C.gray : C.accent}>${glyphs.prompt} <//>
278
245
  ${empty
279
246
  ? html`<${Box} flexGrow=${1}>
280
247
  <${Text} color=${C.gray} inverse> <//>
281
- <${Text} color=${C.gray}>${busy ? 'Working…' : 'How can I help with your network? · /help · ! shell'}<//>
248
+ <${Text} color=${C.gray}>${busy ? 'Working…' : 'Ask about your network, or type / for commands'}<//>
282
249
  <//>`
283
250
  : html`<${Box} flexGrow=${1}>
284
251
  <${Text}>${before}<//><${Text} inverse>${at}<//><${Text}>${after}<//><${Text} color=${C.gray}>${ghost}<//>
285
252
  <//>`}
286
- ${lineCount > 1
287
- ? html`<${Text} dimColor> ${lineCount}L<//>` : null}
288
253
  <//>
289
254
  <//>`;
290
255
  }
@@ -90,30 +90,14 @@ function renderTable(header, rows) {
90
90
  export function Markdown({ content = '' }) {
91
91
  const rows = [];
92
92
  let inCode = false;
93
- let codeLines = [];
94
93
  const lines = String(content).replace(/\s+$/, '').split('\n');
95
94
 
96
95
  for (let i = 0; i < lines.length; i++) {
97
96
  const line = lines[i].replace(/\t/g, ' ');
98
97
 
99
- // Fenced code block — collect all lines then render as a left-bordered block.
100
- if (line.trim().startsWith('```')) {
101
- if (!inCode) {
102
- inCode = true;
103
- codeLines = [];
104
- } else {
105
- inCode = false;
106
- const captured = codeLines;
107
- rows.push(html`
108
- <${Box} key=${k()} flexDirection="column" marginY=${1}
109
- borderStyle=${asciiSafe() ? 'single' : 'single'} borderColor=${C.gray}
110
- borderTop=${false} borderRight=${false} borderBottom=${false} paddingLeft=${1}>
111
- ${captured.map((cl, ci) => html`<${Text} key=${k()} color=${C.green}>${cl}<//>` )}
112
- <//>`);
113
- }
114
- continue;
115
- }
116
- if (inCode) { codeLines.push(line); continue; }
98
+ // Fenced code block.
99
+ if (line.trim().startsWith('```')) { inCode = !inCode; continue; }
100
+ if (inCode) { rows.push(html`<${Text} key=${k()} color=${C.green}> ${line}<//>`); continue; }
117
101
 
118
102
  // GFM table: header row + separator + body rows.
119
103
  if (isTableRow(line) && i + 1 < lines.length && isTableSep(lines[i + 1])) {
package/src/tui/run.js CHANGED
@@ -8,7 +8,7 @@ import { render } from 'ink';
8
8
  import { App } from './app.js';
9
9
 
10
10
  export async function runTui(cfg, opts = {}) {
11
- const { display = {}, onExternal, initialQuestion, version = '', resumeSession = null } = opts;
11
+ const { display = {}, onExternal, initialQuestion } = opts;
12
12
  let externalAction;
13
13
 
14
14
  const instance = render(
@@ -16,8 +16,6 @@ export async function runTui(cfg, opts = {}) {
16
16
  cfg=${cfg}
17
17
  display=${display}
18
18
  initialQuestion=${initialQuestion}
19
- version=${version}
20
- resumeSession=${resumeSession}
21
19
  onExternal=${(a) => { externalAction = a; onExternal?.(a); }}
22
20
  />`,
23
21
  { exitOnCtrlC: false },
package/src/tui/theme.js CHANGED
@@ -5,36 +5,18 @@ import { glyphs, asciiSafe } from '../ui.js';
5
5
 
6
6
  export { glyphs, asciiSafe };
7
7
 
8
- // Soft-blue brand accent (approx. xterm-256 colour 111) — matches Copilot CLI.
8
+ // Soft-blue brand accent (approx. xterm-256 colour 111).
9
9
  export const ACCENT = '#87afff';
10
-
11
- // Semantic color palette. Where possible mirror Copilot CLI conventions:
12
- // - accent/cyan for interactive / in-flight elements
13
- // - green for success / completed
14
- // - red for errors / destructive
15
- // - yellow for warnings / caution / autopilot
16
- // - magenta for reasoning/thinking
17
- // - gray / dimColor for secondary / completed-dim text
18
10
  export const C = {
19
- accent: ACCENT,
20
- gray: 'gray',
21
- white: 'white',
22
- cyan: 'cyan',
23
- green: 'green',
24
- yellow: 'yellow',
25
- red: 'red',
11
+ accent: ACCENT,
12
+ gray: 'gray',
13
+ white: 'white',
14
+ cyan: 'cyan',
15
+ green: 'green',
16
+ yellow: 'yellow',
17
+ red: 'red',
26
18
  magenta: 'magenta',
27
- blue: 'blue',
28
- orange: '#ffb86c', // shell mode / caution
29
- purple: '#d097ff', // sub-agent / agent role
30
- };
31
-
32
- // Copilot-style task status glyphs (Unicode only; fall back handled per use-site).
33
- // ● filled = step completed ◐ half = step in-progress ○ empty = pending
34
- export const SG = {
35
- filled: '●', // U+25CF completed
36
- half: '◐', // U+25D0 in-progress (used alongside spinner)
37
- empty: '○', // U+25CB pending
19
+ blue: 'blue',
38
20
  };
39
21
 
40
22
  // ink-spinner uses cli-spinners; 'line' is pure ASCII (|/-\), 'dots' is braille.
@@ -48,9 +30,9 @@ export function borderStyle() {
48
30
  return asciiSafe() ? 'single' : 'round';
49
31
  }
50
32
 
51
- // Middle-dot separator. Copilot CLI uses a single space each side.
33
+ // Middle-dot separator, ASCII-safe on classic Windows consoles.
52
34
  export function midDot() {
53
- return asciiSafe() ? ' - ' : ' · ';
35
+ return asciiSafe() ? ' - ' : ' · ';
54
36
  }
55
37
 
56
38
  // Em-dash, ASCII-safe on classic Windows consoles.
package/src/logger.js DELETED
@@ -1,23 +0,0 @@
1
- // Append-only log of every prompt + reply that passes through the icli stream.
2
- // Writes to: ~/.config/ispbills-icli/logs/<userKey>/stream.log
3
- import { appendFileSync, mkdirSync } from 'fs';
4
- import { join } from 'path';
5
- import { userDir } from './userdir.js';
6
-
7
- /**
8
- * Append a prompt/reply pair to the stream log for this user.
9
- * Call once per turn: logStream(cfg, prompt, reply).
10
- * Silently no-ops on any I/O error.
11
- */
12
- export function logStream(cfg, prompt, reply) {
13
- try {
14
- const dir = userDir(cfg, 'logs');
15
- const file = join(dir, 'stream.log');
16
- const ts = new Date().toISOString();
17
- const sep = '─'.repeat(60);
18
- const entry = `\n${sep}\n[${ts}]\nPROMPT: ${String(prompt).trim()}\nREPLY: ${String(reply).trim()}\n`;
19
- appendFileSync(file, entry, 'utf8');
20
- } catch {
21
- // never crash the app over logging
22
- }
23
- }
package/src/resume.js DELETED
@@ -1,30 +0,0 @@
1
- /**
2
- * Temporary session persistence for the /update hot-reload flow.
3
- *
4
- * Before updating, icli serialises the live conversation to a JSON file in
5
- * /tmp so the new binary can resume right where the user left off.
6
- */
7
- import { writeFileSync, readFileSync, existsSync, unlinkSync } from 'fs';
8
- import { tmpdir } from 'os';
9
- import { join } from 'path';
10
-
11
- const PREFIX = 'icli-resume-';
12
-
13
- /** Write a resume file and return its path. */
14
- export function saveResume(convo, plain, items = []) {
15
- const path = join(tmpdir(), `${PREFIX}${Date.now()}.json`);
16
- writeFileSync(path, JSON.stringify({ convo, plain, items, savedAt: new Date().toISOString() }), 'utf8');
17
- return path;
18
- }
19
-
20
- /** Load and delete a resume file. Returns { convo, plain, items } or null. */
21
- export function loadResume(path) {
22
- if (!path || !existsSync(path)) return null;
23
- try {
24
- const data = JSON.parse(readFileSync(path, 'utf8'));
25
- try { unlinkSync(path); } catch { /* best-effort delete */ }
26
- return { convo: data.convo || [], plain: data.plain || [], items: data.items || [] };
27
- } catch {
28
- return null;
29
- }
30
- }
package/src/shell.js DELETED
@@ -1,18 +0,0 @@
1
- import { exec } from 'child_process';
2
-
3
- /**
4
- * Run a shell command and capture combined stdout/stderr.
5
- * Returns { ok, stdout, stderr, exitCode }.
6
- */
7
- export function runShell(cmd, timeout = 30_000) {
8
- return new Promise((resolve) => {
9
- exec(cmd, { timeout, maxBuffer: 512 * 1024, shell: true }, (err, stdout, stderr) => {
10
- resolve({
11
- ok: !err || err.code === 0,
12
- stdout: (stdout || '').trim(),
13
- stderr: (stderr || '').trim(),
14
- exitCode: err?.code ?? 0,
15
- });
16
- });
17
- });
18
- }
@@ -1,43 +0,0 @@
1
- /**
2
- * Background version check against npm registry.
3
- * Caches the result in the session so we only hit npm once per run.
4
- * Resolves to { latest, isNewer } or null on any error (network, etc.).
5
- */
6
-
7
- const PKG_NAME = 'ispbills-icli';
8
- const REGISTRY = `https://registry.npmjs.org/${PKG_NAME}/latest`;
9
-
10
- /** Parse a semver string into [major, minor, patch] ints. */
11
- function parse(v) {
12
- const parts = String(v || '').replace(/[^0-9.]/g, '').split('.').map(Number);
13
- return [parts[0] || 0, parts[1] || 0, parts[2] || 0];
14
- }
15
-
16
- /** Returns true when `remote` is strictly newer than `current`. */
17
- function isNewer(current, remote) {
18
- const [cMaj, cMin, cPat] = parse(current);
19
- const [rMaj, rMin, rPat] = parse(remote);
20
- if (rMaj !== cMaj) return rMaj > cMaj;
21
- if (rMin !== cMin) return rMin > cMin;
22
- return rPat > cPat;
23
- }
24
-
25
- /**
26
- * Check npm for a newer version of ispbills-icli.
27
- * Resolves to { latest: string, isNewer: boolean } or null.
28
- */
29
- export async function checkLatestVersion(currentVersion) {
30
- try {
31
- const ctrl = new AbortController();
32
- const timer = setTimeout(() => ctrl.abort(), 5000);
33
- const res = await fetch(REGISTRY, { signal: ctrl.signal });
34
- clearTimeout(timer);
35
- if (!res.ok) return null;
36
- const data = await res.json();
37
- const latest = data.version;
38
- if (!latest) return null;
39
- return { latest, isNewer: isNewer(currentVersion, latest) };
40
- } catch {
41
- return null;
42
- }
43
- }