promptimizer-cli 0.1.48 → 0.1.50
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/promptimizer.mjs +167 -29
- package/package.json +1 -1
package/bin/promptimizer.mjs
CHANGED
|
@@ -26,6 +26,133 @@ const color = process.stdout.isTTY
|
|
|
26
26
|
? (code, text) => `${code}${text}${ANSI.reset}`
|
|
27
27
|
: (_code, text) => text;
|
|
28
28
|
|
|
29
|
+
const ANSI_OK = Boolean(process.stdout.isTTY);
|
|
30
|
+
|
|
31
|
+
/** Inline markdown → ANSI (bold, italic, code, links). */
|
|
32
|
+
function styleInline(text) {
|
|
33
|
+
let s = String(text);
|
|
34
|
+
// code first so we don't style inside backticks
|
|
35
|
+
s = s.replace(/`([^`]+)`/g, (_, code) => color(ANSI.cyan, code));
|
|
36
|
+
s = s.replace(/\*\*([^*]+)\*\*/g, (_, t) => color(ANSI.bold, t));
|
|
37
|
+
s = s.replace(/__([^_]+)__/g, (_, t) => color(ANSI.bold, t));
|
|
38
|
+
s = s.replace(/(?<!\*)\*([^*\n]+)\*(?!\*)/g, (_, t) => color(ANSI.dim, t));
|
|
39
|
+
s = s.replace(/(?<!_)_([^_\n]+)_(?!_)/g, (_, t) => color(ANSI.dim, t));
|
|
40
|
+
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, url) => `${color(ANSI.blue, label)} ${color(ANSI.dim, `(${url})`)}`);
|
|
41
|
+
return s;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Render markdown for the terminal. Keeps structure readable without extra deps.
|
|
46
|
+
* Headers, lists, fences, quotes, hr, tables (plain), paragraphs.
|
|
47
|
+
*/
|
|
48
|
+
function renderMarkdown(source) {
|
|
49
|
+
if (!source) return "";
|
|
50
|
+
const lines = String(source).replace(/\r\n/g, "\n").split("\n");
|
|
51
|
+
const outLines = [];
|
|
52
|
+
let i = 0;
|
|
53
|
+
let inFence = false;
|
|
54
|
+
let fenceLang = "";
|
|
55
|
+
|
|
56
|
+
while (i < lines.length) {
|
|
57
|
+
const line = lines[i];
|
|
58
|
+
|
|
59
|
+
if (line.startsWith("```")) {
|
|
60
|
+
if (!inFence) {
|
|
61
|
+
inFence = true;
|
|
62
|
+
fenceLang = line.slice(3).trim();
|
|
63
|
+
outLines.push(color(ANSI.dim, fenceLang ? `┌─ ${fenceLang}` : "┌─"));
|
|
64
|
+
} else {
|
|
65
|
+
inFence = false;
|
|
66
|
+
fenceLang = "";
|
|
67
|
+
outLines.push(color(ANSI.dim, "└─"));
|
|
68
|
+
}
|
|
69
|
+
i += 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (inFence) {
|
|
74
|
+
outLines.push(` ${color(ANSI.cyan, line)}`);
|
|
75
|
+
i += 1;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (/^#{1,6}\s+/.test(line)) {
|
|
80
|
+
const level = line.match(/^#+/)[0].length;
|
|
81
|
+
const title = line.replace(/^#{1,6}\s+/, "");
|
|
82
|
+
const styled = styleInline(title);
|
|
83
|
+
outLines.push(level <= 2 ? color(ANSI.bold, styled) : styled);
|
|
84
|
+
i += 1;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (/^\s*([-*_] *){3,}\s*$/.test(line)) {
|
|
89
|
+
outLines.push(color(ANSI.dim, " ───"));
|
|
90
|
+
i += 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (/^\s*>\s?/.test(line)) {
|
|
95
|
+
const body = line.replace(/^\s*>\s?/, "");
|
|
96
|
+
outLines.push(`${color(ANSI.dim, "│")} ${styleInline(body)}`);
|
|
97
|
+
i += 1;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const ul = line.match(/^(\s*)([-*+])\s+(.*)$/);
|
|
102
|
+
if (ul) {
|
|
103
|
+
const indent = Math.min(Math.floor(ul[1].length / 2), 4);
|
|
104
|
+
outLines.push(`${" ".repeat(indent)}${color(ANSI.magenta, "•")} ${styleInline(ul[3])}`);
|
|
105
|
+
i += 1;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const ol = line.match(/^(\s*)(\d+)[.)]\s+(.*)$/);
|
|
110
|
+
if (ol) {
|
|
111
|
+
const indent = Math.min(Math.floor(ol[1].length / 2), 4);
|
|
112
|
+
outLines.push(`${" ".repeat(indent)}${color(ANSI.magenta, `${ol[2]}.`)} ${styleInline(ol[3])}`);
|
|
113
|
+
i += 1;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (line.includes("|") && line.trim().startsWith("|")) {
|
|
118
|
+
// simple table row — strip pipes, pad lightly
|
|
119
|
+
const cells = line
|
|
120
|
+
.split("|")
|
|
121
|
+
.slice(1, -1)
|
|
122
|
+
.map((c) => c.trim());
|
|
123
|
+
if (cells.every((c) => /^:?-+:?$/.test(c))) {
|
|
124
|
+
i += 1;
|
|
125
|
+
continue; // separator
|
|
126
|
+
}
|
|
127
|
+
outLines.push(` ${cells.map((c) => styleInline(c)).join(color(ANSI.dim, " · "))}`);
|
|
128
|
+
i += 1;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!line.trim()) {
|
|
133
|
+
outLines.push("");
|
|
134
|
+
i += 1;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
outLines.push(styleInline(line));
|
|
139
|
+
i += 1;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Trim trailing blank lines
|
|
143
|
+
while (outLines.length && outLines[outLines.length - 1] === "") outLines.pop();
|
|
144
|
+
return outLines.join("\n");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function printAnswer(text) {
|
|
148
|
+
if (!text) {
|
|
149
|
+
out(color(ANSI.dim, "(empty)"));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const rendered = ANSI_OK ? renderMarkdown(text) : text;
|
|
153
|
+
process.stdout.write(`${rendered}\n`);
|
|
154
|
+
}
|
|
155
|
+
|
|
29
156
|
const COMMANDS = [
|
|
30
157
|
["", "Start interactive session (Gemini-style REPL)"],
|
|
31
158
|
["login", "Save a Promptimizer API key"],
|
|
@@ -335,7 +462,7 @@ async function complete(flags, config, messages) {
|
|
|
335
462
|
});
|
|
336
463
|
}
|
|
337
464
|
|
|
338
|
-
/** Stream a completion;
|
|
465
|
+
/** Stream a completion; buffers tokens then returns { text, result }. */
|
|
339
466
|
async function completeStream(flags, config, messages) {
|
|
340
467
|
const gatewayURL = gateway(flags, config);
|
|
341
468
|
const { apiKey, sessionId } = authFromConfig(flags, config);
|
|
@@ -371,34 +498,47 @@ async function completeStream(flags, config, messages) {
|
|
|
371
498
|
promptimizer: undefined,
|
|
372
499
|
choices: [{ message: { role: "assistant", content: "" } }],
|
|
373
500
|
};
|
|
501
|
+
let spinTimer = null;
|
|
502
|
+
let spinFrame = 0;
|
|
503
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
504
|
+
if (ANSI_OK) {
|
|
505
|
+
spinTimer = setInterval(() => {
|
|
506
|
+
const frame = frames[spinFrame++ % frames.length];
|
|
507
|
+
process.stdout.write(`\r${color(ANSI.dim, ` ${frame} routing…`)}`);
|
|
508
|
+
}, 80);
|
|
509
|
+
}
|
|
374
510
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
text += delta;
|
|
393
|
-
|
|
511
|
+
try {
|
|
512
|
+
while (true) {
|
|
513
|
+
const { done, value } = await reader.read();
|
|
514
|
+
if (done) break;
|
|
515
|
+
buffer += decoder.decode(value, { stream: true });
|
|
516
|
+
const parts = buffer.split("\n");
|
|
517
|
+
buffer = parts.pop() ?? "";
|
|
518
|
+
for (const line of parts) {
|
|
519
|
+
const trimmed = line.trim();
|
|
520
|
+
if (!trimmed.startsWith("data:")) continue;
|
|
521
|
+
const data = trimmed.slice(5).trim();
|
|
522
|
+
if (!data) continue;
|
|
523
|
+
if (data === "[DONE]") continue;
|
|
524
|
+
try {
|
|
525
|
+
const parsed = JSON.parse(data);
|
|
526
|
+
if (parsed.error?.message) throw new Error(parsed.error.message);
|
|
527
|
+
const delta = parsed.choices?.[0]?.delta?.content;
|
|
528
|
+
if (delta) text += delta;
|
|
529
|
+
if (parsed.promptimizer) result.promptimizer = parsed.promptimizer;
|
|
530
|
+
if (parsed.usage) result.usage = parsed.usage;
|
|
531
|
+
if (parsed.model) result.model = parsed.model;
|
|
532
|
+
} catch (err) {
|
|
533
|
+
if (err instanceof Error && err.message && !err.message.includes("JSON")) throw err;
|
|
394
534
|
}
|
|
395
|
-
if (parsed.promptimizer) result.promptimizer = parsed.promptimizer;
|
|
396
|
-
if (parsed.usage) result.usage = parsed.usage;
|
|
397
|
-
if (parsed.model) result.model = parsed.model;
|
|
398
|
-
} catch (err) {
|
|
399
|
-
if (err instanceof Error && err.message && !err.message.includes("JSON")) throw err;
|
|
400
535
|
}
|
|
401
536
|
}
|
|
537
|
+
} finally {
|
|
538
|
+
if (spinTimer) {
|
|
539
|
+
clearInterval(spinTimer);
|
|
540
|
+
process.stdout.write("\r\x1b[2K");
|
|
541
|
+
}
|
|
402
542
|
}
|
|
403
543
|
|
|
404
544
|
result.choices = [{ message: { role: "assistant", content: text } }];
|
|
@@ -545,8 +685,7 @@ async function cmdChat(flags, positional) {
|
|
|
545
685
|
out();
|
|
546
686
|
out(color(ANSI.magenta, "✦"));
|
|
547
687
|
const { text, result } = await completeStream(flags, config, [{ role: "user", content: prompt }]);
|
|
548
|
-
|
|
549
|
-
process.stdout.write("\n");
|
|
688
|
+
printAnswer(text);
|
|
550
689
|
out();
|
|
551
690
|
printMeta(result);
|
|
552
691
|
out();
|
|
@@ -755,8 +894,7 @@ async function interactive(flags) {
|
|
|
755
894
|
out(color(ANSI.magenta, "✦"));
|
|
756
895
|
try {
|
|
757
896
|
const { text, result } = await completeStream(flags, readConfig(), history);
|
|
758
|
-
|
|
759
|
-
process.stdout.write("\n");
|
|
897
|
+
printAnswer(text);
|
|
760
898
|
history.push({ role: "assistant", content: text });
|
|
761
899
|
out();
|
|
762
900
|
printMeta(result);
|