promptimizer-cli 0.1.51 → 0.1.52
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 +42 -27
- package/package.json +1 -1
package/bin/promptimizer.mjs
CHANGED
|
@@ -28,16 +28,39 @@ const color = process.stdout.isTTY
|
|
|
28
28
|
|
|
29
29
|
const ANSI_OK = Boolean(process.stdout.isTTY);
|
|
30
30
|
|
|
31
|
-
/** Inline markdown → ANSI (bold, italic, code, links). */
|
|
31
|
+
/** Inline markdown → ANSI (bold, italic, code, links). Strips markers even without a TTY. */
|
|
32
32
|
function styleInline(text) {
|
|
33
33
|
let s = String(text);
|
|
34
|
-
// code first
|
|
35
|
-
|
|
36
|
-
s = s.replace(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
// Protect code spans first
|
|
35
|
+
const codes = [];
|
|
36
|
+
s = s.replace(/`([^`]+)`/g, (_, code) => {
|
|
37
|
+
const token = `\u0000C${codes.length}\u0000`;
|
|
38
|
+
codes.push(color(ANSI.cyan, code));
|
|
39
|
+
return token;
|
|
40
|
+
});
|
|
41
|
+
// Bold before italic so ** wins over *
|
|
42
|
+
s = s.replace(/\*\*((?:[^*]|\*(?!\*))+?)\*\*/g, (_, t) => color(ANSI.bold, t));
|
|
43
|
+
s = s.replace(/__((?:[^_]|_(?!_))+?)__/g, (_, t) => color(ANSI.bold, t));
|
|
44
|
+
s = s.replace(/(?<!\*)\*([^*\n]+?)\*(?!\*)/g, (_, t) => color(ANSI.dim, t));
|
|
45
|
+
s = s.replace(/(?<!_)_([^_\n]+?)_(?!_)/g, (_, t) => color(ANSI.dim, t));
|
|
46
|
+
s = s.replace(
|
|
47
|
+
/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
48
|
+
(_, label, url) => `${color(ANSI.blue, label)} ${color(ANSI.dim, `(${url})`)}`,
|
|
49
|
+
);
|
|
50
|
+
// Restore code
|
|
51
|
+
s = s.replace(/\u0000C(\d+)\u0000/g, (_, i) => codes[Number(i)] ?? "");
|
|
52
|
+
return s;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Soft-normalize messy model markdown before line parsing. */
|
|
56
|
+
function normalizeMarkdownSource(source) {
|
|
57
|
+
let s = String(source).replace(/\r\n/g, "\n");
|
|
58
|
+
// Break glued heading markers: "---### Title" / "text### Title"
|
|
59
|
+
s = s.replace(/([^\n#])(#{1,6}\s)/g, "$1\n$2");
|
|
60
|
+
// Break glued thematic break before heading: "---###"
|
|
61
|
+
s = s.replace(/(-{3,}|_{3,}|\*{3,})(#{1,6}\s)/g, "$1\n$2");
|
|
62
|
+
// Ensure list markers start on their own line when jammed after text
|
|
63
|
+
s = s.replace(/([.:;!?])\s*([-*+]\s+\*\*)/g, "$1\n$2");
|
|
41
64
|
return s;
|
|
42
65
|
}
|
|
43
66
|
|
|
@@ -47,7 +70,7 @@ function styleInline(text) {
|
|
|
47
70
|
*/
|
|
48
71
|
function renderMarkdown(source) {
|
|
49
72
|
if (!source) return "";
|
|
50
|
-
const lines =
|
|
73
|
+
const lines = normalizeMarkdownSource(source).split("\n");
|
|
51
74
|
const outLines = [];
|
|
52
75
|
let i = 0;
|
|
53
76
|
let inFence = false;
|
|
@@ -56,10 +79,10 @@ function renderMarkdown(source) {
|
|
|
56
79
|
while (i < lines.length) {
|
|
57
80
|
const line = lines[i];
|
|
58
81
|
|
|
59
|
-
if (line.startsWith("```")) {
|
|
82
|
+
if (line.trimStart().startsWith("```")) {
|
|
60
83
|
if (!inFence) {
|
|
61
84
|
inFence = true;
|
|
62
|
-
fenceLang = line.slice(3).trim();
|
|
85
|
+
fenceLang = line.trimStart().slice(3).trim();
|
|
63
86
|
outLines.push(color(ANSI.dim, fenceLang ? `┌─ ${fenceLang}` : "┌─"));
|
|
64
87
|
} else {
|
|
65
88
|
inFence = false;
|
|
@@ -80,7 +103,7 @@ function renderMarkdown(source) {
|
|
|
80
103
|
const level = line.match(/^#+/)[0].length;
|
|
81
104
|
const title = line.replace(/^#{1,6}\s+/, "");
|
|
82
105
|
const styled = styleInline(title);
|
|
83
|
-
outLines.push(level <= 2 ? color(ANSI.bold, styled) : styled);
|
|
106
|
+
outLines.push(level <= 2 ? color(ANSI.bold, styled) : color(ANSI.bold, styled));
|
|
84
107
|
i += 1;
|
|
85
108
|
continue;
|
|
86
109
|
}
|
|
@@ -115,14 +138,13 @@ function renderMarkdown(source) {
|
|
|
115
138
|
}
|
|
116
139
|
|
|
117
140
|
if (line.includes("|") && line.trim().startsWith("|")) {
|
|
118
|
-
// simple table row — strip pipes, pad lightly
|
|
119
141
|
const cells = line
|
|
120
142
|
.split("|")
|
|
121
143
|
.slice(1, -1)
|
|
122
144
|
.map((c) => c.trim());
|
|
123
145
|
if (cells.every((c) => /^:?-+:?$/.test(c))) {
|
|
124
146
|
i += 1;
|
|
125
|
-
continue;
|
|
147
|
+
continue;
|
|
126
148
|
}
|
|
127
149
|
outLines.push(` ${cells.map((c) => styleInline(c)).join(color(ANSI.dim, " · "))}`);
|
|
128
150
|
i += 1;
|
|
@@ -139,7 +161,6 @@ function renderMarkdown(source) {
|
|
|
139
161
|
i += 1;
|
|
140
162
|
}
|
|
141
163
|
|
|
142
|
-
// Trim trailing blank lines
|
|
143
164
|
while (outLines.length && outLines[outLines.length - 1] === "") outLines.pop();
|
|
144
165
|
return outLines.join("\n");
|
|
145
166
|
}
|
|
@@ -149,8 +170,7 @@ function printAnswer(text) {
|
|
|
149
170
|
out(color(ANSI.dim, "(empty)"));
|
|
150
171
|
return;
|
|
151
172
|
}
|
|
152
|
-
|
|
153
|
-
process.stdout.write(`${rendered}\n`);
|
|
173
|
+
process.stdout.write(`${renderMarkdown(text)}\n`);
|
|
154
174
|
}
|
|
155
175
|
|
|
156
176
|
function sleep(ms) {
|
|
@@ -175,17 +195,15 @@ async function streamAnswer(text, opts = {}) {
|
|
|
175
195
|
out(color(ANSI.dim, "(empty)"));
|
|
176
196
|
return;
|
|
177
197
|
}
|
|
198
|
+
const rendered = renderMarkdown(text);
|
|
178
199
|
if (!ANSI_OK) {
|
|
179
|
-
process.stdout.write(`${
|
|
200
|
+
process.stdout.write(`${rendered}\n`);
|
|
180
201
|
return;
|
|
181
202
|
}
|
|
182
|
-
const rendered = renderMarkdown(text);
|
|
183
203
|
const cacheHit = Boolean(opts.cacheHit);
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
const chunkSize = Math.max(8, Math.floor(cps / 80));
|
|
204
|
+
const cps = cacheHit ? 16_000 : opts.fast ? 10_000 : 6_000;
|
|
205
|
+
const chunkSize = Math.max(12, Math.floor(cps / 90));
|
|
187
206
|
let pending = "";
|
|
188
|
-
let visible = 0;
|
|
189
207
|
|
|
190
208
|
for (const part of ansiChunks(rendered)) {
|
|
191
209
|
if (part.startsWith("\x1b")) {
|
|
@@ -195,19 +213,16 @@ async function streamAnswer(text, opts = {}) {
|
|
|
195
213
|
for (let i = 0; i < part.length; ) {
|
|
196
214
|
const slice = part.slice(i, i + chunkSize);
|
|
197
215
|
pending += slice;
|
|
198
|
-
visible += slice.length;
|
|
199
216
|
i += slice.length;
|
|
200
217
|
if (pending.length >= chunkSize || slice.includes("\n")) {
|
|
201
218
|
process.stdout.write(pending);
|
|
202
219
|
pending = "";
|
|
203
|
-
await sleep(Math.max(
|
|
220
|
+
await sleep(Math.max(3, Math.round((1000 * chunkSize) / cps)));
|
|
204
221
|
}
|
|
205
222
|
}
|
|
206
223
|
}
|
|
207
224
|
if (pending) process.stdout.write(pending);
|
|
208
225
|
process.stdout.write("\n");
|
|
209
|
-
// silence unused when tiny answers finish in one tick
|
|
210
|
-
void visible;
|
|
211
226
|
}
|
|
212
227
|
|
|
213
228
|
function isCacheHit(meta) {
|