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