promptimizer-cli 0.1.17 → 0.1.19
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/README.md +23 -7
- package/bin/promptimizer.mjs +267 -101
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
# promptimizer-cli
|
|
2
2
|
|
|
3
|
+
Gemini-style interactive routing CLI for Promptimizer.
|
|
4
|
+
|
|
3
5
|
```bash
|
|
4
6
|
npm install -g promptimizer-cli
|
|
5
|
-
|
|
6
|
-
npx promptimizer-cli login --help
|
|
7
|
+
promptimizer
|
|
7
8
|
```
|
|
8
9
|
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
```text
|
|
11
|
+
██████╗ ███╗ ███╗███████╗
|
|
12
|
+
…
|
|
13
|
+
Promptimizer v0.1.17
|
|
14
|
+
Type a prompt, or /help /models /savings /clear /quit
|
|
15
|
+
› What is 17 * 24?
|
|
16
|
+
✦
|
|
17
|
+
408
|
|
18
|
+
↳ thinkingmachines/inkling-small · economy · saved $0.0001
|
|
14
19
|
```
|
|
15
20
|
|
|
21
|
+
## Commands
|
|
22
|
+
|
|
23
|
+
| Command | What it does |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `promptimizer` | Interactive multi-turn session |
|
|
26
|
+
| `promptimizer login --key pmz_live_…` | Save API key |
|
|
27
|
+
| `promptimizer connect baseten --key $BASETEN_API_KEY` | Attach provider |
|
|
28
|
+
| `promptimizer chat "…"` | One-shot completion |
|
|
29
|
+
| `promptimizer models` | List fleet |
|
|
30
|
+
| `promptimizer savings` | Account ledger |
|
|
31
|
+
|
|
16
32
|
Defaults to the hosted gateway. Override with `--url` or `PROMPTIMIZER_URL`.
|
package/bin/promptimizer.mjs
CHANGED
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
3
4
|
import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
5
|
import { homedir } from "node:os";
|
|
5
6
|
import { dirname, join } from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
7
9
|
|
|
8
10
|
const DEFAULT_URL = process.env.PROMPTIMIZER_URL || "https://hackathon-omega-liart.vercel.app/api";
|
|
9
11
|
const CONFIG_PATH = join(homedir(), ".promptimizer", "config.json");
|
|
10
12
|
|
|
13
|
+
const ANSI = {
|
|
14
|
+
reset: "\x1b[0m",
|
|
15
|
+
bold: "\x1b[1m",
|
|
16
|
+
dim: "\x1b[2m",
|
|
17
|
+
cyan: "\x1b[36m",
|
|
18
|
+
green: "\x1b[32m",
|
|
19
|
+
yellow: "\x1b[33m",
|
|
20
|
+
magenta: "\x1b[35m",
|
|
21
|
+
gray: "\x1b[90m",
|
|
22
|
+
blue: "\x1b[34m",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const color = process.stdout.isTTY
|
|
26
|
+
? (code, text) => `${code}${text}${ANSI.reset}`
|
|
27
|
+
: (_code, text) => text;
|
|
28
|
+
|
|
11
29
|
const COMMANDS = [
|
|
30
|
+
["", "Start interactive session (Gemini-style REPL)"],
|
|
12
31
|
["login", "Save a Promptimizer API key"],
|
|
13
32
|
["logout", "Remove the saved key"],
|
|
14
33
|
["connect", "Attach a model provider"],
|
|
15
|
-
["chat", "Route
|
|
34
|
+
["chat", "Route one completion"],
|
|
16
35
|
["models", "List the connected fleet"],
|
|
17
36
|
["savings", "Show account savings"],
|
|
18
37
|
["providers", "List known provider URLs"],
|
|
@@ -24,10 +43,6 @@ const COMMAND_HELP = {
|
|
|
24
43
|
" promptimizer login --key <pmz_live_...>",
|
|
25
44
|
"",
|
|
26
45
|
"Save a key from /account. Stored in ~/.promptimizer/config.json.",
|
|
27
|
-
"",
|
|
28
|
-
"Options",
|
|
29
|
-
" --key, -k Promptimizer API key",
|
|
30
|
-
" --url, -u Gateway URL",
|
|
31
46
|
],
|
|
32
47
|
logout: ["Usage", " promptimizer logout", "", "Deletes ~/.promptimizer/config.json."],
|
|
33
48
|
connect: [
|
|
@@ -35,54 +50,16 @@ const COMMAND_HELP = {
|
|
|
35
50
|
" promptimizer connect <provider> --key <vendor-key>",
|
|
36
51
|
" promptimizer connect custom --base-url <url> --key <vendor-key>",
|
|
37
52
|
" promptimizer connect simulator",
|
|
38
|
-
"",
|
|
39
|
-
"Attach a provider to the signed-in account. Known hosts do not need --base-url.",
|
|
40
|
-
"",
|
|
41
|
-
"Options",
|
|
42
|
-
" --key, -k Provider API key (or $BASETEN_API_KEY, $GROQ_API_KEY, …)",
|
|
43
|
-
" --base-url Required for custom",
|
|
44
|
-
" --pmz Promptimizer key (else the saved login)",
|
|
45
|
-
" --url, -u Gateway URL",
|
|
46
53
|
],
|
|
47
54
|
chat: [
|
|
48
55
|
"Usage",
|
|
49
|
-
' promptimizer chat "
|
|
50
|
-
"",
|
|
51
|
-
"Route one completion through the connected provider.",
|
|
52
|
-
"",
|
|
53
|
-
"Options",
|
|
54
|
-
" --prompt Prompt text",
|
|
55
|
-
" --pmz Promptimizer key (else the saved login)",
|
|
56
|
-
" --url, -u Gateway URL",
|
|
57
|
-
],
|
|
58
|
-
models: [
|
|
59
|
-
"Usage",
|
|
60
|
-
" promptimizer models",
|
|
61
|
-
"",
|
|
62
|
-
"List chat models on the current session.",
|
|
63
|
-
"",
|
|
64
|
-
"Options",
|
|
65
|
-
" --url, -u Gateway URL",
|
|
66
|
-
],
|
|
67
|
-
savings: [
|
|
68
|
-
"Usage",
|
|
69
|
-
" promptimizer savings",
|
|
70
|
-
"",
|
|
71
|
-
"Print routed spend versus the frontier baseline.",
|
|
72
|
-
"",
|
|
73
|
-
"Options",
|
|
74
|
-
" --key, -k Promptimizer API key",
|
|
75
|
-
" --url, -u Gateway URL",
|
|
76
|
-
],
|
|
77
|
-
providers: [
|
|
78
|
-
"Usage",
|
|
79
|
-
" promptimizer providers",
|
|
80
|
-
"",
|
|
81
|
-
"Print known provider ids and base URLs.",
|
|
56
|
+
' promptimizer chat "What is 17 * 24?"',
|
|
82
57
|
"",
|
|
83
|
-
"
|
|
84
|
-
" --url, -u Gateway URL",
|
|
58
|
+
"One-shot. Prefer bare `promptimizer` for a multi-turn session.",
|
|
85
59
|
],
|
|
60
|
+
models: ["Usage", " promptimizer models"],
|
|
61
|
+
savings: ["Usage", " promptimizer savings"],
|
|
62
|
+
providers: ["Usage", " promptimizer providers"],
|
|
86
63
|
};
|
|
87
64
|
|
|
88
65
|
function die(message, code = 1) {
|
|
@@ -149,23 +126,56 @@ function usd(value) {
|
|
|
149
126
|
return Math.abs(n) >= 1 ? `$${n.toFixed(2)}` : `$${n.toFixed(4)}`;
|
|
150
127
|
}
|
|
151
128
|
|
|
129
|
+
function printVersion() {
|
|
130
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
131
|
+
const pkg = JSON.parse(readFileSync(join(here, "../package.json"), "utf8"));
|
|
132
|
+
out(pkg.version);
|
|
133
|
+
return pkg.version;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function banner(session, version) {
|
|
137
|
+
const label = session?.label || "not connected";
|
|
138
|
+
const models = session?.models?.length ?? 0;
|
|
139
|
+
const baseline = session?.baseline_model || "—";
|
|
140
|
+
out();
|
|
141
|
+
out(color(ANSI.cyan, " ██████╗ ███╗ ███╗███████╗"));
|
|
142
|
+
out(color(ANSI.cyan, " ██╔══██╗████╗ ████║╚══███╔╝"));
|
|
143
|
+
out(color(ANSI.cyan, " ██████╔╝██╔████╔██║ ███╔╝ "));
|
|
144
|
+
out(color(ANSI.cyan, " ██╔═══╝ ██║╚██╔╝██║ ███╔╝ "));
|
|
145
|
+
out(color(ANSI.cyan, " ██║ ██║ ╚═╝ ██║███████╗"));
|
|
146
|
+
out(color(ANSI.cyan, " ╚═╝ ╚═╝ ╚═╝╚══════╝"));
|
|
147
|
+
out();
|
|
148
|
+
out(` ${color(ANSI.bold, "Promptimizer")} ${color(ANSI.dim, `v${version}`)}`);
|
|
149
|
+
out(` ${color(ANSI.dim, "Quality-aware routing · OpenAI-compatible")}`);
|
|
150
|
+
out();
|
|
151
|
+
out(` ${color(ANSI.green, "●")} ${label}${models ? ` · ${models} models` : ""}`);
|
|
152
|
+
out(` ${color(ANSI.dim, `baseline ${baseline}`)}`);
|
|
153
|
+
out();
|
|
154
|
+
out(` ${color(ANSI.dim, "Type a prompt, or /help /models /savings /clear /quit")}`);
|
|
155
|
+
out(` ${color(ANSI.dim, "Cache keys on full history — /clear then repeat a prompt to see cache hit")}`);
|
|
156
|
+
out();
|
|
157
|
+
}
|
|
158
|
+
|
|
152
159
|
function help() {
|
|
153
|
-
|
|
154
|
-
out("Usage: promptimizer [--url <gateway>] <command> [options]");
|
|
160
|
+
out("Usage: promptimizer [--url <gateway>] [command] [options]");
|
|
155
161
|
out();
|
|
156
|
-
out("
|
|
162
|
+
out(" promptimizer Interactive session (REPL)");
|
|
163
|
+
out(' promptimizer chat "…" One-shot completion');
|
|
157
164
|
out();
|
|
158
165
|
out("Commands");
|
|
159
|
-
|
|
166
|
+
const width = Math.max(...COMMANDS.map(([name]) => name.length || 1));
|
|
167
|
+
for (const [name, desc] of COMMANDS) {
|
|
168
|
+
const label = name || "(default)";
|
|
169
|
+
out(` ${label.padEnd(width + 4)}${desc}`);
|
|
170
|
+
}
|
|
160
171
|
out();
|
|
161
172
|
out("Global options");
|
|
162
173
|
out(" --url, -u Gateway URL (default: hosted app, or $PROMPTIMIZER_URL)");
|
|
163
174
|
out(" --help, -h Show help");
|
|
164
175
|
out(" --version, -v Print version");
|
|
165
176
|
out();
|
|
166
|
-
out("Run `promptimizer <command> --help` for command flags.");
|
|
167
|
-
out();
|
|
168
177
|
out("Examples");
|
|
178
|
+
out(" promptimizer");
|
|
169
179
|
out(" promptimizer login --key pmz_live_…");
|
|
170
180
|
out(" promptimizer connect baseten --key $BASETEN_API_KEY");
|
|
171
181
|
out(' promptimizer chat "What is 17 * 24?"');
|
|
@@ -197,7 +207,7 @@ async function request(path, { method = "GET", body, apiKey, sessionId, gatewayU
|
|
|
197
207
|
if (!response.ok) {
|
|
198
208
|
const detail =
|
|
199
209
|
typeof data === "object" && data && "detail" in data ? String(data.detail) : response.statusText;
|
|
200
|
-
|
|
210
|
+
throw Object.assign(new Error(detail), { status: response.status });
|
|
201
211
|
}
|
|
202
212
|
return data;
|
|
203
213
|
}
|
|
@@ -213,6 +223,46 @@ function requireKey(flags, config) {
|
|
|
213
223
|
return String(apiKey);
|
|
214
224
|
}
|
|
215
225
|
|
|
226
|
+
function authFromConfig(flags, config) {
|
|
227
|
+
const apiKey = flags.pmz || process.env.PROMPTIMIZER_API_KEY || config.apiKey;
|
|
228
|
+
const sessionId = apiKey ? undefined : config.sessionId;
|
|
229
|
+
if (!apiKey && !sessionId) {
|
|
230
|
+
throw new Error("Not signed in. Run promptimizer login --key pmz_live_…");
|
|
231
|
+
}
|
|
232
|
+
return { apiKey, sessionId };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function loadSession(flags, config) {
|
|
236
|
+
const gatewayURL = gateway(flags, config);
|
|
237
|
+
const { apiKey, sessionId } = authFromConfig(flags, config);
|
|
238
|
+
return request("/v1/session", { gatewayURL, apiKey, sessionId });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function printMeta(result) {
|
|
242
|
+
const meta = result.promptimizer ?? {};
|
|
243
|
+
const saved = result.usage?.cost?.saved_usd;
|
|
244
|
+
const bits = [meta.model || result.model, meta.tier].filter(Boolean);
|
|
245
|
+
if (saved != null) bits.push(`saved ${usd(saved)}`);
|
|
246
|
+
if (meta.exact_cache_hit) bits.push(color(ANSI.green, "cache hit"));
|
|
247
|
+
else if (meta.prefix_cache_hit) bits.push(color(ANSI.green, "prefix cache"));
|
|
248
|
+
else if ("cache_hit" in meta) bits.push(color(ANSI.gray, "miss"));
|
|
249
|
+
if (meta.escalated) bits.push("escalated");
|
|
250
|
+
if (meta.latency_ms != null) bits.push(`${Math.round(Number(meta.latency_ms))}ms`);
|
|
251
|
+
out(color(ANSI.dim, ` ↳ ${bits.join(" · ")}`));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function complete(flags, config, messages) {
|
|
255
|
+
const gatewayURL = gateway(flags, config);
|
|
256
|
+
const { apiKey, sessionId } = authFromConfig(flags, config);
|
|
257
|
+
return request("/v1/chat/completions", {
|
|
258
|
+
method: "POST",
|
|
259
|
+
gatewayURL,
|
|
260
|
+
apiKey,
|
|
261
|
+
sessionId,
|
|
262
|
+
body: { messages },
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
216
266
|
async function cmdLogin(flags) {
|
|
217
267
|
const apiKey = flags.key || flags.k || process.env.PROMPTIMIZER_API_KEY;
|
|
218
268
|
if (!apiKey) die("Missing --key. Create one at /account.");
|
|
@@ -220,12 +270,12 @@ async function cmdLogin(flags) {
|
|
|
220
270
|
const gatewayURL = gateway(flags, config);
|
|
221
271
|
await request("/v1/session", { apiKey, gatewayURL });
|
|
222
272
|
writeConfig({ ...config, gatewayURL, apiKey });
|
|
223
|
-
out(
|
|
273
|
+
out(`${color(ANSI.green, "✓")} Saved ${gatewayURL}`);
|
|
224
274
|
}
|
|
225
275
|
|
|
226
276
|
function cmdLogout() {
|
|
227
277
|
rmSync(CONFIG_PATH, { force: true });
|
|
228
|
-
out("Forgot saved key
|
|
278
|
+
out(`${color(ANSI.green, "✓")} Forgot saved key.`);
|
|
229
279
|
}
|
|
230
280
|
|
|
231
281
|
async function cmdProviders(flags) {
|
|
@@ -245,7 +295,9 @@ async function cmdConnect(flags, positional) {
|
|
|
245
295
|
const gatewayURL = gateway(flags, config);
|
|
246
296
|
const provider = String(flags.provider || positional[0] || "").trim();
|
|
247
297
|
const baseURL = flags["base-url"] || flags.baseUrl;
|
|
248
|
-
if (!provider && !baseURL)
|
|
298
|
+
if (!provider && !baseURL) {
|
|
299
|
+
die("Usage: promptimizer connect <provider>\n promptimizer connect custom --base-url https://…");
|
|
300
|
+
}
|
|
249
301
|
|
|
250
302
|
const mock = provider === "simulator" || provider === "mock";
|
|
251
303
|
let vendorKey = flags.key || flags.k;
|
|
@@ -279,53 +331,34 @@ async function cmdConnect(flags, positional) {
|
|
|
279
331
|
});
|
|
280
332
|
|
|
281
333
|
writeConfig({ ...config, gatewayURL, apiKey, sessionId: session.session_id });
|
|
282
|
-
out(`${session.label} ${session.base_url}`);
|
|
283
|
-
out(
|
|
334
|
+
out(`${color(ANSI.green, "✓")} ${session.label} ${session.base_url}`);
|
|
335
|
+
out(` ${session.models.length} models · baseline ${session.baseline_model}`);
|
|
284
336
|
}
|
|
285
337
|
|
|
286
338
|
async function cmdChat(flags, positional) {
|
|
287
339
|
const config = readConfig();
|
|
288
340
|
const prompt = String(flags.prompt || positional.join(" ")).trim();
|
|
289
341
|
if (!prompt) die('Usage: promptimizer chat "What is 17 * 24?"');
|
|
290
|
-
const
|
|
291
|
-
const apiKey = flags.pmz || process.env.PROMPTIMIZER_API_KEY || config.apiKey;
|
|
292
|
-
const sessionId = apiKey ? undefined : config.sessionId;
|
|
293
|
-
if (!apiKey && !sessionId) die("Run promptimizer login or promptimizer connect first.");
|
|
294
|
-
|
|
295
|
-
const result = await request("/v1/chat/completions", {
|
|
296
|
-
method: "POST",
|
|
297
|
-
gatewayURL,
|
|
298
|
-
apiKey,
|
|
299
|
-
sessionId,
|
|
300
|
-
body: { messages: [{ role: "user", content: prompt }] },
|
|
301
|
-
});
|
|
342
|
+
const result = await complete(flags, config, [{ role: "user", content: prompt }]);
|
|
302
343
|
const text = result.choices?.[0]?.message?.content?.trim() ?? "";
|
|
303
|
-
const meta = result.promptimizer ?? {};
|
|
304
|
-
const saved = result.usage?.cost?.saved_usd;
|
|
305
344
|
out();
|
|
306
345
|
out(text);
|
|
307
346
|
out();
|
|
308
|
-
|
|
309
|
-
if (saved != null) bits.push(`saved ${usd(saved)}`);
|
|
310
|
-
if (meta.cache_hit) bits.push("cache");
|
|
311
|
-
if (meta.escalated) bits.push("escalated");
|
|
312
|
-
out(bits.join(" · "));
|
|
347
|
+
printMeta(result);
|
|
313
348
|
out();
|
|
314
349
|
}
|
|
315
350
|
|
|
316
351
|
async function cmdModels(flags) {
|
|
317
352
|
const config = readConfig();
|
|
318
353
|
const gatewayURL = gateway(flags, config);
|
|
319
|
-
const apiKey =
|
|
320
|
-
const sessionId = apiKey ? undefined : config.sessionId;
|
|
321
|
-
if (!apiKey && !sessionId) die("Run promptimizer login or promptimizer connect first.");
|
|
354
|
+
const { apiKey, sessionId } = authFromConfig(flags, config);
|
|
322
355
|
const data = await request("/v1/models", { gatewayURL, apiKey, sessionId });
|
|
323
356
|
const models = data.data ?? [];
|
|
324
357
|
const width = Math.max(8, ...models.map((model) => String(model.tier).length));
|
|
325
358
|
out();
|
|
326
359
|
for (const model of models) {
|
|
327
|
-
const mark = model.id === data.baseline_model ? " baseline" : "";
|
|
328
|
-
out(` ${String(model.tier).padEnd(width + 2)}${model.id}${mark}`);
|
|
360
|
+
const mark = model.id === data.baseline_model ? color(ANSI.yellow, " baseline") : "";
|
|
361
|
+
out(` ${color(ANSI.dim, String(model.tier).padEnd(width + 2))}${model.id}${mark}`);
|
|
329
362
|
}
|
|
330
363
|
out();
|
|
331
364
|
}
|
|
@@ -335,7 +368,7 @@ async function cmdSavings(flags) {
|
|
|
335
368
|
const apiKey = requireKey(flags, config);
|
|
336
369
|
const data = await request("/v1/savings", { gatewayURL: gateway(flags, config), apiKey });
|
|
337
370
|
out();
|
|
338
|
-
out(`${usd(data.saved_usd)} saved`);
|
|
371
|
+
out(`${color(ANSI.bold, usd(data.saved_usd))} saved ${color(ANSI.dim, `(${Number(data.saved_pct || 0).toFixed(1)}%)`)}`);
|
|
339
372
|
out();
|
|
340
373
|
out(` routed ${usd(data.actual_usd)}`);
|
|
341
374
|
out(` baseline ${usd(data.baseline_usd)}`);
|
|
@@ -345,10 +378,137 @@ async function cmdSavings(flags) {
|
|
|
345
378
|
out();
|
|
346
379
|
}
|
|
347
380
|
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
381
|
+
async function interactive(flags) {
|
|
382
|
+
if (!input.isTTY || !output.isTTY) {
|
|
383
|
+
help();
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const config = readConfig();
|
|
388
|
+
const version = (() => {
|
|
389
|
+
try {
|
|
390
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
391
|
+
return JSON.parse(readFileSync(join(here, "../package.json"), "utf8")).version;
|
|
392
|
+
} catch {
|
|
393
|
+
return "0.0.0";
|
|
394
|
+
}
|
|
395
|
+
})();
|
|
396
|
+
|
|
397
|
+
let session = null;
|
|
398
|
+
try {
|
|
399
|
+
session = await loadSession(flags, config);
|
|
400
|
+
} catch (error) {
|
|
401
|
+
banner(null, version);
|
|
402
|
+
out(color(ANSI.yellow, ` ${error instanceof Error ? error.message : String(error)}`));
|
|
403
|
+
out(color(ANSI.dim, " Run: promptimizer login --key pmz_live_…"));
|
|
404
|
+
out();
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
banner(session, version);
|
|
409
|
+
|
|
410
|
+
const rl = createInterface({ input, output, terminal: true });
|
|
411
|
+
const history = [];
|
|
412
|
+
|
|
413
|
+
const slashHelp = () => {
|
|
414
|
+
out();
|
|
415
|
+
out(` ${color(ANSI.bold, "/help")} this list`);
|
|
416
|
+
out(` ${color(ANSI.bold, "/models")} fleet + tiers`);
|
|
417
|
+
out(` ${color(ANSI.bold, "/savings")} account ledger`);
|
|
418
|
+
out(` ${color(ANSI.bold, "/session")} provider status`);
|
|
419
|
+
out(` ${color(ANSI.bold, "/clear")} clear chat history`);
|
|
420
|
+
out(` ${color(ANSI.bold, "/quit")} exit`);
|
|
421
|
+
out();
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
while (true) {
|
|
426
|
+
let line;
|
|
427
|
+
try {
|
|
428
|
+
line = await rl.question(color(ANSI.cyan, "› "));
|
|
429
|
+
} catch {
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
const trimmed = line.trim();
|
|
433
|
+
if (!trimmed) continue;
|
|
434
|
+
|
|
435
|
+
if (trimmed === "/quit" || trimmed === "/exit" || trimmed === "/q") break;
|
|
436
|
+
|
|
437
|
+
if (trimmed === "/help" || trimmed === "/?") {
|
|
438
|
+
slashHelp();
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (trimmed === "/clear") {
|
|
443
|
+
history.length = 0;
|
|
444
|
+
out(color(ANSI.dim, " History cleared."));
|
|
445
|
+
out();
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (trimmed === "/session") {
|
|
450
|
+
try {
|
|
451
|
+
session = await loadSession(flags, readConfig());
|
|
452
|
+
out();
|
|
453
|
+
out(` ${session.label} · ${session.mode}`);
|
|
454
|
+
out(` ${session.base_url}`);
|
|
455
|
+
out(` ${session.models.length} models · baseline ${session.baseline_model}`);
|
|
456
|
+
out();
|
|
457
|
+
} catch (error) {
|
|
458
|
+
out(color(ANSI.yellow, ` ${error instanceof Error ? error.message : String(error)}`));
|
|
459
|
+
}
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (trimmed === "/models") {
|
|
464
|
+
try {
|
|
465
|
+
await cmdModels(flags);
|
|
466
|
+
} catch (error) {
|
|
467
|
+
out(color(ANSI.yellow, ` ${error instanceof Error ? error.message : String(error)}`));
|
|
468
|
+
}
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (trimmed === "/savings") {
|
|
473
|
+
try {
|
|
474
|
+
await cmdSavings(flags);
|
|
475
|
+
} catch (error) {
|
|
476
|
+
out(color(ANSI.yellow, ` ${error instanceof Error ? error.message : String(error)}`));
|
|
477
|
+
}
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (trimmed.startsWith("/")) {
|
|
482
|
+
out(color(ANSI.dim, " Unknown command. Try /help"));
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
history.push({ role: "user", content: trimmed });
|
|
487
|
+
process.stdout.write(color(ANSI.dim, " … routing\r"));
|
|
488
|
+
try {
|
|
489
|
+
const result = await complete(flags, readConfig(), history);
|
|
490
|
+
process.stdout.write(" \r");
|
|
491
|
+
const text = result.choices?.[0]?.message?.content?.trim() ?? "";
|
|
492
|
+
history.push({ role: "assistant", content: text });
|
|
493
|
+
out();
|
|
494
|
+
out(color(ANSI.magenta, "✦"));
|
|
495
|
+
out(text);
|
|
496
|
+
out();
|
|
497
|
+
printMeta(result);
|
|
498
|
+
out();
|
|
499
|
+
} catch (error) {
|
|
500
|
+
process.stdout.write(" \r");
|
|
501
|
+
history.pop();
|
|
502
|
+
out(color(ANSI.yellow, ` ${error instanceof Error ? error.message : String(error)}`));
|
|
503
|
+
out();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
} finally {
|
|
507
|
+
rl.close();
|
|
508
|
+
out();
|
|
509
|
+
out(color(ANSI.dim, " bye"));
|
|
510
|
+
out();
|
|
511
|
+
}
|
|
352
512
|
}
|
|
353
513
|
|
|
354
514
|
async function main() {
|
|
@@ -362,23 +522,29 @@ async function main() {
|
|
|
362
522
|
else help();
|
|
363
523
|
return;
|
|
364
524
|
}
|
|
365
|
-
if (
|
|
366
|
-
|
|
525
|
+
if (flags.help || flags.h) {
|
|
526
|
+
if (positional[0]) commandHelp(positional[0]);
|
|
527
|
+
else help();
|
|
367
528
|
return;
|
|
368
529
|
}
|
|
369
530
|
|
|
531
|
+
if (positional.length === 0) {
|
|
532
|
+
return interactive(flags);
|
|
533
|
+
}
|
|
534
|
+
|
|
370
535
|
const [command, ...rest] = positional;
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
return;
|
|
536
|
+
try {
|
|
537
|
+
if (command === "login") return await cmdLogin(flags);
|
|
538
|
+
if (command === "logout") return cmdLogout();
|
|
539
|
+
if (command === "providers") return await cmdProviders(flags);
|
|
540
|
+
if (command === "connect") return await cmdConnect(flags, rest);
|
|
541
|
+
if (command === "chat") return await cmdChat(flags, rest);
|
|
542
|
+
if (command === "models") return await cmdModels(flags);
|
|
543
|
+
if (command === "savings") return await cmdSavings(flags);
|
|
544
|
+
if (command === "repl" || command === "i") return await interactive(flags);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
die(error instanceof Error ? error.message : String(error));
|
|
374
547
|
}
|
|
375
|
-
if (command === "login") return cmdLogin(flags);
|
|
376
|
-
if (command === "logout") return cmdLogout();
|
|
377
|
-
if (command === "providers") return cmdProviders(flags);
|
|
378
|
-
if (command === "connect") return cmdConnect(flags, rest);
|
|
379
|
-
if (command === "chat") return cmdChat(flags, rest);
|
|
380
|
-
if (command === "models") return cmdModels(flags);
|
|
381
|
-
if (command === "savings") return cmdSavings(flags);
|
|
382
548
|
die(`Unknown command "${command}". Run promptimizer --help.`);
|
|
383
549
|
}
|
|
384
550
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "promptimizer-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.19",
|
|
4
|
+
"description": "Interactive Promptimizer CLI — Gemini-style REPL for quality-aware routing.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"promptimizer": "./bin/promptimizer.mjs"
|