ispbills-icli 8.3.0 → 8.3.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/README.md +2 -2
- package/package.json +1 -1
- package/src/tui/app.js +3 -2
- package/src/tui/composer.js +49 -7
package/README.md
CHANGED
|
@@ -188,7 +188,7 @@ suggestion (accept with **Tab** or **→**), and runs the highlighted command on
|
|
|
188
188
|
| **@** | Mention a device/entity (e.g. `@olt:`, `@onu:`); **Tab** / **→** completes the tag |
|
|
189
189
|
| **↑/↓** (empty input) | Navigate input history |
|
|
190
190
|
| **Ctrl+Z** / **Ctrl+Y** | Undo / redo the input line |
|
|
191
|
-
| **Paste** | Multi-line pastes
|
|
191
|
+
| **Paste** | Multi-line pastes (incl. mouse right-click) keep their line breaks and are never auto-submitted — press **Enter** to send |
|
|
192
192
|
| **Shift+Tab** | Toggle autopilot mode |
|
|
193
193
|
| **Ctrl+T** | Toggle reasoning display |
|
|
194
194
|
| **Ctrl+L** | Clear the transcript |
|
|
@@ -225,7 +225,7 @@ iCli connects to the same AI engine as the in-browser terminal:
|
|
|
225
225
|
- **Bot memory** — `/remember` saves standing operator notes that are injected into every answer (`/memory`, `/forget` to manage)
|
|
226
226
|
- **Backups in git** — `/backup` archives config to a per-user git repo; run `/git status|log|commit|push` from the prompt
|
|
227
227
|
- **Share as a gist** — `/gist` publishes the last answer or transcript to a secret GitHub gist
|
|
228
|
-
- **Copy & paste** — `/copy` puts the last answer on the clipboard; multi-line pastes and **Ctrl+Z / Ctrl+Y** undo/redo work in the input
|
|
228
|
+
- **Copy & paste** — `/copy` puts the last answer on the clipboard; multi-line pastes (mouse right-click or Cmd/Ctrl+V) keep their line breaks, and **Ctrl+Z / Ctrl+Y** undo/redo work in the input
|
|
229
229
|
- **Full-screen terminal UI** — a full-height alternate-screen TUI (built on [Ink](https://github.com/vadimdemedes/ink)) with the banner pinned to the top, a bottom-pinned bordered input, a selectable slash-command menu, input history, and a bottom-anchored transcript — the same layout as GitHub Copilot CLI / Gemini CLI. Piped/non-interactive use falls back to plain streaming output
|
|
230
230
|
- **Terminal-aware rendering** — the layout reflows to your terminal width and resizes live; on classic Windows consoles (`cmd.exe` / legacy PowerShell) it auto-switches to ASCII-safe glyphs and borders so nothing shows as broken boxes
|
|
231
231
|
|
package/package.json
CHANGED
package/src/tui/app.js
CHANGED
|
@@ -100,8 +100,9 @@ export function App({ cfg, display = {}, onExternal, onExit, initialQuestion })
|
|
|
100
100
|
const lastAnswerRef = useRef(''); // most recent assistant answer (for /copy, /gist)
|
|
101
101
|
const choiceActionRef = useRef(null); // action to run when a non-AI Choice is picked
|
|
102
102
|
|
|
103
|
-
// Enable terminal bracketed-paste so multi-line pastes
|
|
104
|
-
//
|
|
103
|
+
// Enable terminal bracketed-paste so multi-line pastes (mouse right-click or
|
|
104
|
+
// Cmd/Ctrl+V) arrive wrapped in markers as one block — the Composer inserts
|
|
105
|
+
// them with newlines preserved instead of submitting on the first newline.
|
|
105
106
|
useEffect(() => {
|
|
106
107
|
try { process.stdout.write('\x1b[?2004h'); } catch {}
|
|
107
108
|
return () => { try { process.stdout.write('\x1b[?2004l'); } catch {} };
|
package/src/tui/composer.js
CHANGED
|
@@ -40,6 +40,26 @@ export function Composer({ onSubmit, busy, width = 80 }) {
|
|
|
40
40
|
lastMut.current = kind;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
// Bracketed-paste assembly. A right-click / Cmd+V paste is wrapped by the
|
|
44
|
+
// terminal in \x1b[200~ … \x1b[201~ and can arrive split across several
|
|
45
|
+
// reads, so we buffer until the end marker before inserting.
|
|
46
|
+
const pasteRef = useRef({ active: false, buf: '' });
|
|
47
|
+
|
|
48
|
+
// Insert pasted text at the cursor, PRESERVING newlines so multi-line pastes
|
|
49
|
+
// (configs, logs) keep their structure. Tabs → 2 spaces; other control chars
|
|
50
|
+
// and the bracketed-paste markers are dropped.
|
|
51
|
+
const insertPaste = (raw) => {
|
|
52
|
+
const text = raw
|
|
53
|
+
.replace(/\x1b?\[20[01]~/g, '')
|
|
54
|
+
.replace(/\r\n?/g, '\n')
|
|
55
|
+
.replace(/\t/g, ' ')
|
|
56
|
+
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
|
|
57
|
+
if (!text) return;
|
|
58
|
+
snapshot('paste');
|
|
59
|
+
setValue((v) => v.slice(0, cursor) + text + v.slice(cursor));
|
|
60
|
+
setCursor((c) => c + text.length);
|
|
61
|
+
};
|
|
62
|
+
|
|
43
63
|
// Determine the token being typed at the cursor to drive the palette.
|
|
44
64
|
const upto = value.slice(0, cursor);
|
|
45
65
|
const tokStart = Math.max(upto.lastIndexOf(' '), upto.lastIndexOf('\n')) + 1;
|
|
@@ -79,6 +99,25 @@ export function Composer({ onSubmit, busy, width = 80 }) {
|
|
|
79
99
|
useInput((input, key) => {
|
|
80
100
|
if (busy) return; // ignore edits while a turn streams (App handles Ctrl+C/Esc)
|
|
81
101
|
|
|
102
|
+
// ── Bracketed paste (mouse right-click / Cmd+V), possibly split reads ──
|
|
103
|
+
// Ink strips the leading ESC, so the terminal's \x1b[200~ … \x1b[201~
|
|
104
|
+
// wrappers arrive as the marker text "[200~" / "[201~" (sometimes as their
|
|
105
|
+
// own useInput calls). Buffer everything between them so embedded newlines
|
|
106
|
+
// are never treated as Enter, then insert the whole block at once.
|
|
107
|
+
const START = '[200~';
|
|
108
|
+
const END = '[201~';
|
|
109
|
+
if (pasteRef.current.active || input.includes(START)) {
|
|
110
|
+
pasteRef.current.active = true;
|
|
111
|
+
pasteRef.current.buf += input;
|
|
112
|
+
const e = pasteRef.current.buf.indexOf(END);
|
|
113
|
+
if (e === -1) return; // still receiving the paste
|
|
114
|
+
const s = pasteRef.current.buf.indexOf(START);
|
|
115
|
+
const raw = pasteRef.current.buf.slice(s + START.length, e);
|
|
116
|
+
pasteRef.current = { active: false, buf: '' };
|
|
117
|
+
insertPaste(raw);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
82
121
|
// ── Submit / complete ──
|
|
83
122
|
// Some terminals/PTYs deliver Enter as input="\r" without key.return, so
|
|
84
123
|
// treat a lone CR/LF as Enter too (a paste is a longer chunk, never "\r").
|
|
@@ -155,20 +194,23 @@ export function Composer({ onSubmit, busy, width = 80 }) {
|
|
|
155
194
|
|
|
156
195
|
// ── Printable input & paste ──
|
|
157
196
|
if (input && !key.ctrl && !key.meta) {
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
text =
|
|
197
|
+
// A multi-char chunk or one containing newlines/tabs is a non-bracketed
|
|
198
|
+
// paste (terminal without bracketed-paste support): keep its newlines.
|
|
199
|
+
if (input.length > 1 || /[\r\n\t]/.test(input)) { insertPaste(input); return; }
|
|
200
|
+
const text = input.replace(/[\x00-\x1f\x7f]/g, '');
|
|
162
201
|
if (!text) return;
|
|
163
|
-
snapshot(
|
|
202
|
+
snapshot('type');
|
|
164
203
|
setValue((v) => v.slice(0, cursor) + text + v.slice(cursor));
|
|
165
204
|
setCursor((c) => c + text.length);
|
|
166
205
|
}
|
|
167
206
|
});
|
|
168
207
|
|
|
169
208
|
const before = value.slice(0, cursor);
|
|
170
|
-
const
|
|
171
|
-
|
|
209
|
+
const rawAt = value[cursor];
|
|
210
|
+
// When the cursor sits on a newline, show an inverse space and keep the line
|
|
211
|
+
// break in `after` so the buffer still renders across multiple lines.
|
|
212
|
+
const at = (rawAt === undefined || rawAt === '\n') ? ' ' : rawAt;
|
|
213
|
+
const after = rawAt === '\n' ? '\n' + value.slice(cursor + 1) : value.slice(cursor + 1);
|
|
172
214
|
const empty = value.length === 0;
|
|
173
215
|
const sugTok = menuOpen && matches[sel] ? (slashMode ? matches[sel].cmd : matches[sel].tag) : '';
|
|
174
216
|
const ghost = sugTok && sugTok.startsWith(token) && cursor >= value.length ? sugTok.slice(token.length) : '';
|