ispbills-icli 8.4.5 → 8.4.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/clipboard.js +35 -1
- package/src/commands.js +2 -0
- package/src/tui/app.js +36 -4
- package/src/tui/composer.js +10 -1
package/package.json
CHANGED
package/src/clipboard.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Cross-platform "copy to system clipboard" helper. Best-effort: resolves to
|
|
2
2
|
// false (rather than throwing) when no clipboard tool is available so the TUI
|
|
3
3
|
// can show a friendly notice.
|
|
4
|
-
import { spawn } from 'child_process';
|
|
4
|
+
import { spawn, execFile } from 'child_process';
|
|
5
5
|
|
|
6
6
|
function candidates() {
|
|
7
7
|
if (process.platform === 'darwin') return [['pbcopy', []]];
|
|
@@ -40,3 +40,37 @@ export async function copyToClipboard(text) {
|
|
|
40
40
|
}
|
|
41
41
|
return null;
|
|
42
42
|
}
|
|
43
|
+
|
|
44
|
+
// ── Read from clipboard ─────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
function readCandidates() {
|
|
47
|
+
if (process.platform === 'darwin') return [['pbpaste', []]];
|
|
48
|
+
if (process.platform === 'win32') return [['powershell', ['-noprofile', '-command', 'Get-Clipboard']]];
|
|
49
|
+
return [
|
|
50
|
+
['wl-paste', ['--no-newline']],
|
|
51
|
+
['xclip', ['-selection', 'clipboard', '-o']],
|
|
52
|
+
['xsel', ['--clipboard', '--output']],
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Read text from the system clipboard.
|
|
58
|
+
* Returns the clipboard string on success, or null when no tool is available
|
|
59
|
+
* or the clipboard is empty.
|
|
60
|
+
*/
|
|
61
|
+
export function readFromClipboard() {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
const cands = readCandidates();
|
|
64
|
+
let i = 0;
|
|
65
|
+
function tryNext() {
|
|
66
|
+
if (i >= cands.length) { resolve(null); return; }
|
|
67
|
+
const [cmd, args] = cands[i++];
|
|
68
|
+
execFile(cmd, args, { timeout: 3000, maxBuffer: 1024 * 512 }, (err, stdout) => {
|
|
69
|
+
if (err) { tryNext(); return; }
|
|
70
|
+
const text = stdout.trimEnd();
|
|
71
|
+
resolve(text || null);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
tryNext();
|
|
75
|
+
});
|
|
76
|
+
}
|
package/src/commands.js
CHANGED
|
@@ -56,6 +56,8 @@ export const SLASH_COMMANDS = [
|
|
|
56
56
|
{ cmd: '/autopilot', hint: '', desc: 'Toggle autopilot (auto-apply fixes)', group: 'Session', local: true },
|
|
57
57
|
{ cmd: '/model', hint: '[name]', desc: 'Show or set the AI model', group: 'Session', local: true },
|
|
58
58
|
{ cmd: '/reasoning', hint: '', desc: 'Toggle reasoning display (Ctrl+T)', group: 'Session', local: true },
|
|
59
|
+
{ cmd: '/paste', hint: '', desc: 'Paste multiline text from clipboard into input', group: 'Session', local: true },
|
|
60
|
+
{ cmd: '/about', hint: '', desc: 'About iCopilot — version, server, user', group: 'Session', local: true },
|
|
59
61
|
{ cmd: '/new', hint: '', desc: 'Start a fresh conversation', group: 'Session', local: true },
|
|
60
62
|
{ cmd: '/history', hint: '', desc: 'Show conversation history', group: 'Session', local: true },
|
|
61
63
|
{ cmd: '/clear', hint: '', desc: 'Clear the screen', group: 'Session', local: true },
|
package/src/tui/app.js
CHANGED
|
@@ -16,7 +16,7 @@ import { slashToQuestion, SLASH_COMMANDS } from '../commands.js';
|
|
|
16
16
|
import { saveConfig } from '../config.js';
|
|
17
17
|
import { shortModel } from '../ui.js';
|
|
18
18
|
import { loadNotes, addNote, forgetNote, memoryPreamble } from '../memory.js';
|
|
19
|
-
import { copyToClipboard } from '../clipboard.js';
|
|
19
|
+
import { copyToClipboard, readFromClipboard } from '../clipboard.js';
|
|
20
20
|
import { createGist } from '../gist.js';
|
|
21
21
|
import { git as gitCmd, commitBackup } from '../gitrepo.js';
|
|
22
22
|
import { runShell } from '../shell.js';
|
|
@@ -66,6 +66,7 @@ export function App({ cfg, display = {}, onExternal, onExit, initialQuestion, ve
|
|
|
66
66
|
const [startedAt, setStartedAt] = useState(0);
|
|
67
67
|
const [choice, setChoice] = useState(null); // { title, note, options, allowText, sel, text, focusText, onPick }
|
|
68
68
|
const [clearEpoch, setClearEpoch] = useState(0); // bump to remount <Static> on /clear /new
|
|
69
|
+
const [prefillText, setPrefillText] = useState(null); // clipboard paste → pre-fills Composer
|
|
69
70
|
|
|
70
71
|
const convo = useRef([]); // [{role, content}] for the backend
|
|
71
72
|
const plain = useRef([]); // plain-text transcript for exit reprint
|
|
@@ -357,8 +358,39 @@ export function App({ cfg, display = {}, onExternal, onExit, initialQuestion, ve
|
|
|
357
358
|
}
|
|
358
359
|
|
|
359
360
|
// ── Clipboard ──
|
|
360
|
-
|
|
361
|
-
|
|
361
|
+
// ── /about ─────────────────────────────────────────────────────────────
|
|
362
|
+
if (q === '/about') {
|
|
363
|
+
const lines = [
|
|
364
|
+
`iCopilot CLI v${version || '?'}`,
|
|
365
|
+
`Server ${cfg.url || '(unknown)'}`,
|
|
366
|
+
`User ${cfg.user?.name || '—'} (${cfg.user?.role || '—'})`,
|
|
367
|
+
`Model ${shortModel(model || cfg._model || '—')}`,
|
|
368
|
+
'',
|
|
369
|
+
'Type /help for all commands · Ctrl+C to exit',
|
|
370
|
+
'npm: ispbills-icli · github: lupael/IspBills',
|
|
371
|
+
];
|
|
372
|
+
push({ type: 'notice', text: lines.join('\n'), color: C.accent });
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ── /paste ─────────────────────────────────────────────────────────────
|
|
377
|
+
if (q === '/paste') {
|
|
378
|
+
push({ type: 'notice', text: `${glyphs.arrow} Reading clipboard…`, color: C.gray });
|
|
379
|
+
readFromClipboard().then((text) => {
|
|
380
|
+
if (!text) {
|
|
381
|
+
push({ type: 'notice', text: `${glyphs.cross} Clipboard is empty or no clipboard tool found.\nInstall xclip / wl-clipboard (Linux), or use macOS/Windows.`, color: C.red });
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const lines = text.split('\n').length;
|
|
385
|
+
push({ type: 'notice',
|
|
386
|
+
text: `${glyphs.check} Pasted ${lines} line${lines !== 1 ? 's' : ''} (${text.length} chars) into the input box — review and press Enter to send.`,
|
|
387
|
+
color: C.green });
|
|
388
|
+
setPrefillText(text);
|
|
389
|
+
});
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (q === '/copy') { const ans = lastAnswerRef.current;
|
|
362
394
|
if (!ans) { push({ type: 'notice', text: 'Nothing to copy yet — ask something first.', color: C.gray }); return; }
|
|
363
395
|
copyToClipboard(ans).then((tool) => push({ type: 'notice',
|
|
364
396
|
text: tool ? `${glyphs.check} Copied last answer (${ans.length} chars) to the clipboard.`
|
|
@@ -545,7 +577,7 @@ export function App({ cfg, display = {}, onExternal, onExit, initialQuestion, ve
|
|
|
545
577
|
<${Box} flexDirection="column" marginTop=${1}>
|
|
546
578
|
${choice
|
|
547
579
|
? html`<${Choice} ...${choice} />`
|
|
548
|
-
: html`<${Composer} onSubmit=${onSubmit} busy=${busy} width=${cols} />`}
|
|
580
|
+
: html`<${Composer} onSubmit=${onSubmit} busy=${busy} width=${cols} prefill=${prefillText} onPrefillConsumed=${() => setPrefillText(null)} />`}
|
|
549
581
|
<${Box} paddingX=${1} width=${cols}>
|
|
550
582
|
<${Box} flexGrow=${1}>
|
|
551
583
|
<${Text} dimColor wrap="truncate-end">${ctx}<//>
|
package/src/tui/composer.js
CHANGED
|
@@ -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 }) {
|
|
23
|
+
export function Composer({ onSubmit, busy, width = 80, prefill = null, onPrefillConsumed = null }) {
|
|
24
24
|
const [value, setValue] = useState('');
|
|
25
25
|
const [cursor, setCursor] = useState(0);
|
|
26
26
|
const [history, setHistory] = useState([]);
|
|
@@ -40,6 +40,15 @@ export function Composer({ onSubmit, busy, width = 80 }) {
|
|
|
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
|
+
|
|
43
52
|
// Ctrl+S stash: save the current prompt to restore later (GitHub Copilot CLI parity).
|
|
44
53
|
const stashRef = useRef('');
|
|
45
54
|
|