promptimizer-cli 0.1.25 → 0.1.27

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.
@@ -190,10 +190,8 @@ function banner(session, version) {
190
190
  if (hostCount > 1) out(` ${color(ANSI.dim, `${hostCount} hosts merged · router picks across all`)}`);
191
191
  out(` ${color(ANSI.dim, `baseline ${baseline}`)}`);
192
192
  out();
193
- out(
194
- ` ${color(ANSI.dim, "Type a prompt, or /help /hosts /models /connect /disconnect /clear /quit")}`,
195
- );
196
- out(` ${color(ANSI.dim, "Cache keys on full history — /clear then repeat a prompt to see cache hit")}`);
193
+ out(` ${color(ANSI.dim, "Type a prompt, or /help /hosts /models /connect /disconnect /clear /quit")}`);
194
+ out(` ${color(ANSI.dim, "Repeating the same last message hits prompt cache; /clear resets the chat thread")}`);
197
195
  out();
198
196
  }
199
197
 
@@ -302,8 +300,18 @@ function printMeta(result) {
302
300
  if (meta.provider_id || meta.host) bits.push(String(meta.provider_id || meta.host));
303
301
  if (saved != null) bits.push(`saved ${usd(saved)}`);
304
302
  if (meta.exact_cache_hit) bits.push(color(ANSI.green, "cache hit"));
305
- else if (meta.prefix_cache_hit) bits.push(color(ANSI.green, "prefix cache"));
303
+ else if (meta.prompt_cache_hit) bits.push(color(ANSI.green, "prompt cache"));
304
+ else if (meta.semantic_cache_hit) {
305
+ const mode = meta.semantic_cache_mode === "full" ? "semantic full" : meta.semantic_cache_mode === "prompt" ? "prompt cache" : "semantic hybrid";
306
+ const sim =
307
+ meta.semantic_similarity != null ? ` ${Math.round(Number(meta.semantic_similarity) * 100)}%` : "";
308
+ bits.push(color(ANSI.green, `${mode}${sim}`));
309
+ } else if (meta.prefix_cache_hit) bits.push(color(ANSI.green, "prefix cache"));
306
310
  else if ("cache_hit" in meta) bits.push(color(ANSI.gray, "miss"));
311
+ if (meta.quality_gate) bits.push(`gate:${meta.quality_gate}`);
312
+ if (meta.quality_audit) {
313
+ bits.push(meta.quality_audit_pass === false ? color(ANSI.yellow, "audit fail") : "audit ok");
314
+ }
307
315
  if (meta.escalated) bits.push(meta.escalation_reason ? `escalated:${meta.escalation_reason}` : "escalated");
308
316
  if (meta.latency_ms != null) bits.push(`${Math.round(Number(meta.latency_ms))}ms`);
309
317
  out(color(ANSI.dim, ` ↳ ${bits.join(" · ")}`));
@@ -321,6 +329,76 @@ async function complete(flags, config, messages) {
321
329
  });
322
330
  }
323
331
 
332
+ /** Stream a completion; writes tokens to stdout as they arrive. Returns { text, result }. */
333
+ async function completeStream(flags, config, messages) {
334
+ const gatewayURL = gateway(flags, config);
335
+ const { apiKey, sessionId } = authFromConfig(flags, config);
336
+ const headers = { "content-type": "application/json" };
337
+ if (apiKey) headers.authorization = `Bearer ${apiKey}`;
338
+ else if (sessionId) {
339
+ headers.authorization = `Bearer ${sessionId}`;
340
+ headers["x-promptimizer-session"] = sessionId;
341
+ }
342
+ const response = await fetch(`${gatewayURL}/v1/chat/completions`, {
343
+ method: "POST",
344
+ headers,
345
+ body: JSON.stringify({ messages, stream: true }),
346
+ });
347
+ if (!response.ok) {
348
+ const data = await response.json().catch(() => ({}));
349
+ const detail =
350
+ typeof data === "object" && data && "detail" in data ? String(data.detail) : response.statusText;
351
+ throw Object.assign(new Error(detail), { status: response.status });
352
+ }
353
+ if (!response.body) {
354
+ const data = await response.json();
355
+ return { text: data.choices?.[0]?.message?.content?.trim() ?? "", result: data };
356
+ }
357
+
358
+ const reader = response.body.getReader();
359
+ const decoder = new TextDecoder();
360
+ let buffer = "";
361
+ let text = "";
362
+ let result = {
363
+ model: undefined,
364
+ usage: undefined,
365
+ promptimizer: undefined,
366
+ choices: [{ message: { role: "assistant", content: "" } }],
367
+ };
368
+
369
+ while (true) {
370
+ const { done, value } = await reader.read();
371
+ if (done) break;
372
+ buffer += decoder.decode(value, { stream: true });
373
+ const parts = buffer.split("\n");
374
+ buffer = parts.pop() ?? "";
375
+ for (const line of parts) {
376
+ const trimmed = line.trim();
377
+ if (!trimmed.startsWith("data:")) continue;
378
+ const data = trimmed.slice(5).trim();
379
+ if (!data) continue;
380
+ if (data === "[DONE]") continue;
381
+ try {
382
+ const parsed = JSON.parse(data);
383
+ if (parsed.error?.message) throw new Error(parsed.error.message);
384
+ const delta = parsed.choices?.[0]?.delta?.content;
385
+ if (delta) {
386
+ text += delta;
387
+ process.stdout.write(delta);
388
+ }
389
+ if (parsed.promptimizer) result.promptimizer = parsed.promptimizer;
390
+ if (parsed.usage) result.usage = parsed.usage;
391
+ if (parsed.model) result.model = parsed.model;
392
+ } catch (err) {
393
+ if (err instanceof Error && err.message && !err.message.includes("JSON")) throw err;
394
+ }
395
+ }
396
+ }
397
+
398
+ result.choices = [{ message: { role: "assistant", content: text } }];
399
+ return { text, result };
400
+ }
401
+
324
402
  async function cmdLogin(flags) {
325
403
  const apiKey = flags.key || flags.k || process.env.PROMPTIMIZER_API_KEY;
326
404
  if (!apiKey) die("Missing --key. Create one at /account.");
@@ -457,10 +535,11 @@ async function cmdChat(flags, positional) {
457
535
  const config = readConfig();
458
536
  const prompt = String(flags.prompt || positional.join(" ")).trim();
459
537
  if (!prompt) die('Usage: promptimizer chat "What is 17 * 24?"');
460
- const result = await complete(flags, config, [{ role: "user", content: prompt }]);
461
- const text = result.choices?.[0]?.message?.content?.trim() ?? "";
462
538
  out();
463
- out(text);
539
+ out(color(ANSI.magenta, "✦"));
540
+ const { text, result } = await completeStream(flags, config, [{ role: "user", content: prompt }]);
541
+ if (!text) process.stdout.write(color(ANSI.dim, "(empty)"));
542
+ process.stdout.write("\n");
464
543
  out();
465
544
  printMeta(result);
466
545
  out();
@@ -665,20 +744,18 @@ async function interactive(flags) {
665
744
  }
666
745
 
667
746
  history.push({ role: "user", content: trimmed });
668
- process.stdout.write(color(ANSI.dim, " … routing\r"));
747
+ out();
748
+ out(color(ANSI.magenta, "✦"));
669
749
  try {
670
- const result = await complete(flags, readConfig(), history);
671
- process.stdout.write(" \r");
672
- const text = result.choices?.[0]?.message?.content?.trim() ?? "";
750
+ const { text, result } = await completeStream(flags, readConfig(), history);
751
+ if (!text) process.stdout.write(color(ANSI.dim, "(empty)"));
752
+ process.stdout.write("\n");
673
753
  history.push({ role: "assistant", content: text });
674
754
  out();
675
- out(color(ANSI.magenta, "✦"));
676
- out(text);
677
- out();
678
755
  printMeta(result);
679
756
  out();
680
757
  } catch (error) {
681
- process.stdout.write(" \r");
758
+ process.stdout.write("\n");
682
759
  history.pop();
683
760
  out(color(ANSI.yellow, ` ${error instanceof Error ? error.message : String(error)}`));
684
761
  out();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptimizer-cli",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "Interactive Promptimizer CLI — Gemini-style REPL for quality-aware routing.",
5
5
  "type": "module",
6
6
  "bin": {