prior-cli 1.3.7 → 1.3.9

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 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 = () => {};
@@ -659,11 +661,36 @@ async function startChat(opts = {}) {
659
661
  return;
660
662
  }
661
663
 
664
+ // Alt+V — paste image from clipboard
665
+ if (key.meta && key.name === 'v') {
666
+ try {
667
+ 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 { '' }`;
668
+ const b64 = execSync(`powershell -NoProfile -Command "${ps}"`, { timeout: 5000 }).toString().trim();
669
+ if (b64) {
670
+ _pendingImageBase64 = b64;
671
+ // Print the indicator below the current input line
672
+ process.stdout.write('\x1b[s');
673
+ process.stdout.write(`\x1b[B\r\x1b[2K ${c.brand('◈')} ${c.dim('image from clipboard attached — type your prompt and press enter')}`);
674
+ process.stdout.write('\x1b[u');
675
+ } else {
676
+ process.stdout.write('\x1b[s');
677
+ process.stdout.write(`\x1b[B\r\x1b[2K ${c.muted('✗ No image found in clipboard')}`);
678
+ process.stdout.write('\x1b[u');
679
+ }
680
+ } catch {
681
+ process.stdout.write('\x1b[s');
682
+ process.stdout.write(`\x1b[B\r\x1b[2K ${c.muted('✗ Could not read clipboard')}`);
683
+ process.stdout.write('\x1b[u');
684
+ }
685
+ return;
686
+ }
687
+
662
688
  if (key.name === 'return' || key.name === 'enter' || (key.ctrl && key.name === 'c')) {
663
- // Clear suggestions NOW before readline moves the cursor to the next line
664
- if (_suggCount > 0) {
689
+ // Clear suggestions and image indicator before readline moves cursor
690
+ const clearRows = _suggCount + (_pendingImageBase64 ? 1 : 0);
691
+ if (clearRows > 0) {
665
692
  process.stdout.write('\x1b[s');
666
- for (let i = 0; i < _suggCount; i++) process.stdout.write('\x1b[B\r\x1b[2K');
693
+ for (let i = 0; i < clearRows; i++) process.stdout.write('\x1b[B\r\x1b[2K');
667
694
  process.stdout.write('\x1b[u');
668
695
  _suggCount = 0;
669
696
  }
@@ -944,6 +971,13 @@ Keep it under 350 words. Write prior.md now.`;
944
971
  console.log('');
945
972
 
946
973
  {
974
+ const imageForThisMsg = _pendingImageBase64;
975
+ _pendingImageBase64 = null;
976
+
977
+ if (imageForThisMsg) {
978
+ console.log(c.brand(' ◈') + c.dim(' image attached'));
979
+ }
980
+
947
981
  let responseText = '';
948
982
  let _progressStarted = false;
949
983
  const _thinkStart = Date.now();
@@ -969,6 +1003,7 @@ Keep it under 350 words. Write prior.md now.`;
969
1003
  model: currentModel,
970
1004
  cwd: process.cwd(),
971
1005
  projectContext,
1006
+ imageBase64: imageForThisMsg,
972
1007
  confirm,
973
1008
  signal: _currentAbortController.signal,
974
1009
  send: ev => {
@@ -1259,6 +1294,53 @@ program
1259
1294
  } catch (err) { console.error(c.err(` ✗ ${err.message}`)); }
1260
1295
  });
1261
1296
 
1297
+ // ── UPDATE ─────────────────────────────────────────────────────
1298
+ program
1299
+ .command('update')
1300
+ .description('Check for updates and install if available')
1301
+ .action(async () => {
1302
+ const { execSync } = require('child_process');
1303
+ console.log('');
1304
+ process.stdout.write(c.dim(' Checking for updates…'));
1305
+
1306
+ const fetch = require('node-fetch');
1307
+ let latest;
1308
+ try {
1309
+ const res = await fetch('https://registry.npmjs.org/prior-cli/latest', { timeout: 8000 });
1310
+ if (!res.ok) throw new Error(`Registry error: HTTP ${res.status}`);
1311
+ const data = await res.json();
1312
+ latest = data.version;
1313
+ } catch (err) {
1314
+ clearLine();
1315
+ console.error(c.err(` ✗ Could not reach npm registry: ${err.message}\n`));
1316
+ return;
1317
+ }
1318
+
1319
+ clearLine();
1320
+
1321
+ if (latest === version) {
1322
+ console.log(c.ok(' ✓ Already up to date ') + c.muted(`v${version}`));
1323
+ console.log('');
1324
+ return;
1325
+ }
1326
+
1327
+ console.log(` ${c.muted('Current :')} ${c.white(`v${version}`)}`);
1328
+ console.log(` ${c.muted('Latest :')} ${c.bold(`v${latest}`)}`);
1329
+ console.log('');
1330
+ process.stdout.write(c.dim(' Installing update…'));
1331
+
1332
+ try {
1333
+ execSync('npm install -g prior-cli@latest', { stdio: 'ignore' });
1334
+ clearLine();
1335
+ console.log(c.ok(` ✓ Updated to v${latest} `) + c.muted('restart prior to apply'));
1336
+ } catch (err) {
1337
+ clearLine();
1338
+ console.error(c.err(` ✗ Install failed: ${err.message}`));
1339
+ console.error(c.muted(' Try manually: npm install -g prior-cli@latest'));
1340
+ }
1341
+ console.log('');
1342
+ });
1343
+
1262
1344
  // ── Entry point ────────────────────────────────────────────────
1263
1345
  program
1264
1346
  .name('prior')
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prior-cli",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
4
4
  "description": "Prior Network AI — command-line interface",
5
5
  "bin": {
6
6
  "prior": "bin/prior.js"