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.
@@ -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 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})`)}`);
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 = String(source).replace(/\r\n/g, "\n").split("\n");
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; // separator
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
- const rendered = ANSI_OK ? renderMarkdown(text) : text;
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(`${text}\n`);
200
+ process.stdout.write(`${rendered}\n`);
180
201
  return;
181
202
  }
182
- const rendered = renderMarkdown(text);
183
203
  const cacheHit = Boolean(opts.cacheHit);
184
- // ~chars per second for the visible body (escapes are free)
185
- const cps = cacheHit ? 14_000 : opts.fast ? 9_000 : 5_500;
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(4, Math.round((1000 * chunkSize) / cps)));
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptimizer-cli",
3
- "version": "0.1.51",
3
+ "version": "0.1.52",
4
4
  "description": "Interactive Promptimizer CLI — Gemini-style REPL for quality-aware routing.",
5
5
  "type": "module",
6
6
  "bin": {