prior-cli 1.3.8 → 1.3.10
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/prior.js +75 -31
- package/lib/agent.js +7 -4
- package/package.json +1 -1
package/bin/prior.js
CHANGED
|
@@ -7,6 +7,7 @@ const readline = require('readline');
|
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const os = require('os');
|
|
9
9
|
const fs = require('fs');
|
|
10
|
+
const { execSync } = require('child_process');
|
|
10
11
|
const { version } = require('../package.json');
|
|
11
12
|
|
|
12
13
|
const api = require('../lib/api');
|
|
@@ -607,6 +608,7 @@ async function startChat(opts = {}) {
|
|
|
607
608
|
const chatHistory = [];
|
|
608
609
|
let currentModel = opts.model || null;
|
|
609
610
|
let _currentAbortController = null;
|
|
611
|
+
let _pendingImageBase64 = null; // set by alt+v clipboard paste
|
|
610
612
|
|
|
611
613
|
// ── Live slash-command suggestions ──────────────────────────
|
|
612
614
|
let clearSuggestions = () => {};
|
|
@@ -626,27 +628,56 @@ async function startChat(opts = {}) {
|
|
|
626
628
|
{ cmd: '/exit', desc: 'Exit' },
|
|
627
629
|
];
|
|
628
630
|
|
|
629
|
-
|
|
630
|
-
let
|
|
631
|
+
// Unified sub-row tracker — covers image indicator + slash suggestions
|
|
632
|
+
let _subRowCount = 0;
|
|
633
|
+
let _suggTimer = null;
|
|
631
634
|
|
|
632
|
-
|
|
633
|
-
|
|
635
|
+
// Clear all rows rendered below the input line
|
|
636
|
+
function clearAllSubRows() {
|
|
637
|
+
if (!_subRowCount) return;
|
|
634
638
|
process.stdout.write('\x1b[s');
|
|
635
|
-
for (let i = 0; i <
|
|
639
|
+
for (let i = 0; i < _subRowCount; i++) process.stdout.write('\x1b[B\r\x1b[2K');
|
|
636
640
|
process.stdout.write('\x1b[u');
|
|
637
|
-
|
|
638
|
-
}
|
|
641
|
+
_subRowCount = 0;
|
|
642
|
+
}
|
|
639
643
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
644
|
+
// Alias used by the loop's rl.question callback
|
|
645
|
+
clearSuggestions = clearAllSubRows;
|
|
646
|
+
|
|
647
|
+
// Redraw image indicator + slash suggestions — always called together so
|
|
648
|
+
// they share the same row-count and never stomp on each other.
|
|
649
|
+
function renderSubRows(line) {
|
|
650
|
+
clearAllSubRows();
|
|
651
|
+
process.stdout.write('\x1b[s');
|
|
652
|
+
let rows = 0;
|
|
653
|
+
|
|
654
|
+
// Image indicator — always first, persists across backspace/typing
|
|
655
|
+
if (_pendingImageBase64) {
|
|
656
|
+
process.stdout.write(`\x1b[B\r\x1b[2K ${c.brand('◈')} ${c.dim('[Image] attached · alt+v to replace · type your prompt and press enter')}`);
|
|
657
|
+
rows++;
|
|
648
658
|
}
|
|
649
|
-
|
|
659
|
+
|
|
660
|
+
// Slash-command suggestions
|
|
661
|
+
if ((line || '').startsWith('/')) {
|
|
662
|
+
const word = line.split(' ')[0];
|
|
663
|
+
const matches = SLASH_CMDS.filter(({ cmd }) => cmd.startsWith(word));
|
|
664
|
+
for (const { cmd, desc } of matches) {
|
|
665
|
+
process.stdout.write(`\x1b[B\r\x1b[2K${c.brand(' ' + cmd.padEnd(14))}${c.dim(desc)}`);
|
|
666
|
+
rows++;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
process.stdout.write('\x1b[u');
|
|
671
|
+
_subRowCount = rows;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Show a one-off message below the input (cleared on next keypress)
|
|
675
|
+
function flashSubRow(msg) {
|
|
676
|
+
clearAllSubRows();
|
|
677
|
+
process.stdout.write('\x1b[s');
|
|
678
|
+
process.stdout.write(`\x1b[B\r\x1b[2K ${msg}`);
|
|
679
|
+
process.stdout.write('\x1b[u');
|
|
680
|
+
_subRowCount = 1;
|
|
650
681
|
}
|
|
651
682
|
|
|
652
683
|
process.stdin.on('keypress', (ch, key) => {
|
|
@@ -659,27 +690,32 @@ async function startChat(opts = {}) {
|
|
|
659
690
|
return;
|
|
660
691
|
}
|
|
661
692
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
693
|
+
// Alt+V — grab image from Windows clipboard
|
|
694
|
+
if (key.meta && key.name === 'v') {
|
|
695
|
+
try {
|
|
696
|
+
const ps = `Add-Type -AssemblyName System.Windows.Forms; $img = [System.Windows.Forms.Clipboard]::GetImage(); if ($img) { $ms = New-Object System.IO.MemoryStream; $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); [Convert]::ToBase64String($ms.ToArray()) } else { '' }`;
|
|
697
|
+
const b64 = execSync(`powershell -NoProfile -Command "${ps}"`, { timeout: 5000 }).toString().trim();
|
|
698
|
+
if (b64) {
|
|
699
|
+
_pendingImageBase64 = b64;
|
|
700
|
+
renderSubRows(rl.line || ''); // immediate redraw with indicator
|
|
701
|
+
} else {
|
|
702
|
+
flashSubRow(c.muted('✗ No image found in clipboard'));
|
|
703
|
+
}
|
|
704
|
+
} catch {
|
|
705
|
+
flashSubRow(c.muted('✗ Could not read clipboard'));
|
|
669
706
|
}
|
|
670
707
|
return;
|
|
671
708
|
}
|
|
672
709
|
|
|
710
|
+
if (key.name === 'return' || key.name === 'enter' || (key.ctrl && key.name === 'c')) {
|
|
711
|
+
clearAllSubRows();
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Redraw sub-rows on every keypress so backspace / typing never wipes them
|
|
673
716
|
_suggTimer = setTimeout(() => {
|
|
674
717
|
_suggTimer = null;
|
|
675
|
-
|
|
676
|
-
if (!line.startsWith('/')) {
|
|
677
|
-
clearSuggestions();
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
const word = line.split(' ')[0];
|
|
681
|
-
const matches = SLASH_CMDS.filter(({ cmd }) => cmd.startsWith(word));
|
|
682
|
-
renderSuggestions(matches);
|
|
718
|
+
renderSubRows(rl.line || '');
|
|
683
719
|
}, 50);
|
|
684
720
|
});
|
|
685
721
|
}
|
|
@@ -944,6 +980,13 @@ Keep it under 350 words. Write prior.md now.`;
|
|
|
944
980
|
console.log('');
|
|
945
981
|
|
|
946
982
|
{
|
|
983
|
+
const imageForThisMsg = _pendingImageBase64;
|
|
984
|
+
_pendingImageBase64 = null;
|
|
985
|
+
|
|
986
|
+
if (imageForThisMsg) {
|
|
987
|
+
console.log(c.brand(' ◈') + c.dim(' image attached'));
|
|
988
|
+
}
|
|
989
|
+
|
|
947
990
|
let responseText = '';
|
|
948
991
|
let _progressStarted = false;
|
|
949
992
|
const _thinkStart = Date.now();
|
|
@@ -969,6 +1012,7 @@ Keep it under 350 words. Write prior.md now.`;
|
|
|
969
1012
|
model: currentModel,
|
|
970
1013
|
cwd: process.cwd(),
|
|
971
1014
|
projectContext,
|
|
1015
|
+
imageBase64: imageForThisMsg,
|
|
972
1016
|
confirm,
|
|
973
1017
|
signal: _currentAbortController.signal,
|
|
974
1018
|
send: ev => {
|
package/lib/agent.js
CHANGED
|
@@ -10,11 +10,11 @@ const MAX_ITER = 14;
|
|
|
10
10
|
|
|
11
11
|
// ── Single inference call (server just runs Ollama + returns) ─
|
|
12
12
|
|
|
13
|
-
async function infer(messages, model, token, { cwd, uncensored, projectContext } = {}, signal) {
|
|
13
|
+
async function infer(messages, model, token, { cwd, uncensored, projectContext, imageBase64 } = {}, signal) {
|
|
14
14
|
const res = await fetch(`${CLI_BASE}/api/infer`, {
|
|
15
15
|
method: 'POST',
|
|
16
16
|
headers: { 'Content-Type': 'application/json' },
|
|
17
|
-
body: JSON.stringify({ messages, model, token, cwd, uncensored, projectContext }),
|
|
17
|
+
body: JSON.stringify({ messages, model, token, cwd, uncensored, projectContext, imageBase64 }),
|
|
18
18
|
timeout: 120000,
|
|
19
19
|
signal,
|
|
20
20
|
});
|
|
@@ -200,12 +200,13 @@ function stripToolTags(text) {
|
|
|
200
200
|
|
|
201
201
|
const CONFIRM_TOOLS = new Set(['run_command', 'file_delete', 'file_write']);
|
|
202
202
|
|
|
203
|
-
async function runAgent({ messages, model, uncensored, cwd, projectContext, send, confirm, signal }) {
|
|
203
|
+
async function runAgent({ messages, model, uncensored, cwd, projectContext, imageBase64, send, confirm, signal }) {
|
|
204
204
|
const token = getToken();
|
|
205
205
|
const history = [...messages];
|
|
206
206
|
|
|
207
207
|
let totalPromptTokens = 0;
|
|
208
208
|
let totalCompletionTokens = 0;
|
|
209
|
+
let pendingImage = imageBase64 || null; // only sent on first iteration
|
|
209
210
|
|
|
210
211
|
for (let iter = 0; iter < MAX_ITER; iter++) {
|
|
211
212
|
|
|
@@ -218,8 +219,10 @@ async function runAgent({ messages, model, uncensored, cwd, projectContext, send
|
|
|
218
219
|
send({ type: 'thinking' });
|
|
219
220
|
|
|
220
221
|
let result;
|
|
222
|
+
const iterImage = pendingImage;
|
|
223
|
+
pendingImage = null; // clear after first use
|
|
221
224
|
try {
|
|
222
|
-
result = await infer(history, model || 'qwen3.5:4b', token, { cwd, uncensored, projectContext }, signal);
|
|
225
|
+
result = await infer(history, model || 'qwen3.5:4b', token, { cwd, uncensored, projectContext, imageBase64: iterImage }, signal);
|
|
223
226
|
} catch (err) {
|
|
224
227
|
await trackTokenUsage(token, totalPromptTokens, totalCompletionTokens);
|
|
225
228
|
if (err.name === 'AbortError' || signal?.aborted) {
|