promptimizer-cli 0.1.0
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 +18 -0
- package/bin/promptimizer.mjs +290 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# promptimizer-cli
|
|
2
|
+
|
|
3
|
+
Login with a `pmz_live_` key, connect a provider, route prompts, and read savings.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g promptimizer-cli
|
|
7
|
+
# or
|
|
8
|
+
npx promptimizer-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
promptimizer login --key pmz_live_…
|
|
13
|
+
promptimizer connect baseten --key "$BASETEN_API_KEY"
|
|
14
|
+
promptimizer chat "What is 17 * 24?"
|
|
15
|
+
promptimizer savings
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Defaults to the hosted gateway. Override with `--url` or `PROMPTIMIZER_URL`.
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_URL = process.env.PROMPTIMIZER_URL || "https://hackathon-omega-liart.vercel.app/api";
|
|
9
|
+
const CONFIG_PATH = join(homedir(), ".promptimizer", "config.json");
|
|
10
|
+
|
|
11
|
+
const COMMANDS = [
|
|
12
|
+
["login", "Store a Promptimizer API key"],
|
|
13
|
+
["logout", "Forget the saved key"],
|
|
14
|
+
["connect", "Attach a model provider"],
|
|
15
|
+
["chat", "Route a completion"],
|
|
16
|
+
["models", "Show the connected fleet"],
|
|
17
|
+
["savings", "Account savings so far"],
|
|
18
|
+
["providers", "Known base URLs"],
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function die(message, code = 1) {
|
|
22
|
+
process.stderr.write(`${message}\n`);
|
|
23
|
+
process.exit(code);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function out(message = "") {
|
|
27
|
+
process.stdout.write(`${message}\n`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parse(argv) {
|
|
31
|
+
const flags = {};
|
|
32
|
+
const positional = [];
|
|
33
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
34
|
+
const arg = argv[i];
|
|
35
|
+
if (arg === "--") {
|
|
36
|
+
positional.push(...argv.slice(i + 1));
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
if (arg.startsWith("--")) {
|
|
40
|
+
const key = arg.slice(2);
|
|
41
|
+
const next = argv[i + 1];
|
|
42
|
+
if (!next || next.startsWith("-")) flags[key] = true;
|
|
43
|
+
else {
|
|
44
|
+
flags[key] = next;
|
|
45
|
+
i += 1;
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (arg.startsWith("-") && arg.length === 2) {
|
|
50
|
+
const next = argv[i + 1];
|
|
51
|
+
if (next && !next.startsWith("-")) {
|
|
52
|
+
flags[arg.slice(1)] = next;
|
|
53
|
+
i += 1;
|
|
54
|
+
} else flags[arg.slice(1)] = true;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
positional.push(arg);
|
|
58
|
+
}
|
|
59
|
+
return { flags, positional };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function readConfig() {
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
65
|
+
} catch {
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function writeConfig(next) {
|
|
71
|
+
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
72
|
+
writeFileSync(CONFIG_PATH, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
|
|
73
|
+
try {
|
|
74
|
+
chmodSync(CONFIG_PATH, 0o600);
|
|
75
|
+
} catch {
|
|
76
|
+
/* best effort */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function usd(value) {
|
|
81
|
+
const n = Number(value) || 0;
|
|
82
|
+
return Math.abs(n) >= 1 ? `$${n.toFixed(2)}` : `$${n.toFixed(4)}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function help() {
|
|
86
|
+
const width = Math.max(...COMMANDS.map(([name]) => name.length));
|
|
87
|
+
out();
|
|
88
|
+
out("Usage");
|
|
89
|
+
out(" promptimizer <command>");
|
|
90
|
+
out();
|
|
91
|
+
out("Commands");
|
|
92
|
+
for (const [name, desc] of COMMANDS) out(` ${name.padEnd(width + 2)}${desc}`);
|
|
93
|
+
out();
|
|
94
|
+
out("Examples");
|
|
95
|
+
out(" promptimizer login --key pmz_live_…");
|
|
96
|
+
out(" promptimizer connect baseten --key $BASETEN_API_KEY");
|
|
97
|
+
out(' promptimizer chat "What is 17 * 24?"');
|
|
98
|
+
out(" promptimizer savings");
|
|
99
|
+
out();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function request(path, { method = "GET", body, apiKey, sessionId, gatewayURL } = {}) {
|
|
103
|
+
const headers = { "content-type": "application/json" };
|
|
104
|
+
if (apiKey) headers.authorization = `Bearer ${apiKey}`;
|
|
105
|
+
else if (sessionId) {
|
|
106
|
+
headers.authorization = `Bearer ${sessionId}`;
|
|
107
|
+
headers["x-promptimizer-session"] = sessionId;
|
|
108
|
+
}
|
|
109
|
+
const response = await fetch(`${gatewayURL}${path}`, {
|
|
110
|
+
method,
|
|
111
|
+
headers,
|
|
112
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
113
|
+
});
|
|
114
|
+
const data = await response.json().catch(() => ({}));
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
const detail =
|
|
117
|
+
typeof data === "object" && data && "detail" in data ? String(data.detail) : response.statusText;
|
|
118
|
+
die(detail);
|
|
119
|
+
}
|
|
120
|
+
return data;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function gateway(flags, config) {
|
|
124
|
+
const url = String(flags.url || flags.u || config.gatewayURL || DEFAULT_URL).replace(/\/$/, "");
|
|
125
|
+
return url.endsWith("/api") ? url : `${url}/api`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function requireKey(flags, config) {
|
|
129
|
+
const apiKey = flags.key || flags.k || process.env.PROMPTIMIZER_API_KEY || config.apiKey;
|
|
130
|
+
if (!apiKey) die("Missing Promptimizer key. Run promptimizer login --key pmz_live_…");
|
|
131
|
+
return String(apiKey);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function cmdLogin(flags) {
|
|
135
|
+
const apiKey = flags.key || flags.k || process.env.PROMPTIMIZER_API_KEY;
|
|
136
|
+
if (!apiKey) die("Missing --key. Create one at /account.");
|
|
137
|
+
const config = readConfig();
|
|
138
|
+
const gatewayURL = gateway(flags, config);
|
|
139
|
+
await request("/v1/session", { apiKey, gatewayURL });
|
|
140
|
+
writeConfig({ ...config, gatewayURL, apiKey });
|
|
141
|
+
out(`Saved ${gatewayURL}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function cmdLogout() {
|
|
145
|
+
rmSync(CONFIG_PATH, { force: true });
|
|
146
|
+
out("Forgot saved key.");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function cmdProviders(flags) {
|
|
150
|
+
const config = readConfig();
|
|
151
|
+
const data = await request("/v1/providers", { gatewayURL: gateway(flags, config) });
|
|
152
|
+
const rows = data.data ?? [];
|
|
153
|
+
const width = Math.max(8, ...rows.map((row) => String(row.id).length));
|
|
154
|
+
out();
|
|
155
|
+
for (const row of rows) out(` ${String(row.id).padEnd(width + 2)}${row.base_url}`);
|
|
156
|
+
out();
|
|
157
|
+
out(" custom pass --base-url");
|
|
158
|
+
out();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function cmdConnect(flags, positional) {
|
|
162
|
+
const config = readConfig();
|
|
163
|
+
const gatewayURL = gateway(flags, config);
|
|
164
|
+
const provider = String(flags.provider || positional[0] || "").trim();
|
|
165
|
+
const baseURL = flags["base-url"] || flags.baseUrl;
|
|
166
|
+
if (!provider && !baseURL) die("Usage: promptimizer connect <provider>\n promptimizer connect custom --base-url https://…");
|
|
167
|
+
|
|
168
|
+
const mock = provider === "simulator" || provider === "mock";
|
|
169
|
+
let vendorKey = flags.key || flags.k;
|
|
170
|
+
if (!mock && !baseURL && provider && provider !== "custom") {
|
|
171
|
+
const catalog = await request("/v1/providers", { gatewayURL });
|
|
172
|
+
const found = (catalog.data ?? []).find(
|
|
173
|
+
(row) => row.id === provider || String(row.label).toLowerCase() === provider.toLowerCase(),
|
|
174
|
+
);
|
|
175
|
+
if (!found) die(`Unknown provider "${provider}". Run promptimizer providers, or pass --base-url.`);
|
|
176
|
+
if (!vendorKey && found.env && process.env[found.env]) vendorKey = process.env[found.env];
|
|
177
|
+
if (!vendorKey && found.id !== "ollama") {
|
|
178
|
+
die(`Missing API key for ${found.label}. Pass --key or set ${found.env}.`);
|
|
179
|
+
}
|
|
180
|
+
} else if (!mock && !vendorKey && provider !== "ollama") {
|
|
181
|
+
die("Missing provider key. Pass --key.");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const apiKey = flags.pmz || process.env.PROMPTIMIZER_API_KEY || config.apiKey;
|
|
185
|
+
const session = await request("/v1/providers/connect", {
|
|
186
|
+
method: "POST",
|
|
187
|
+
gatewayURL,
|
|
188
|
+
apiKey,
|
|
189
|
+
body: mock
|
|
190
|
+
? { mode: "mock", label: "Promptimizer simulator" }
|
|
191
|
+
: {
|
|
192
|
+
mode: "byok",
|
|
193
|
+
provider: provider && provider !== "custom" ? provider : undefined,
|
|
194
|
+
base_url: baseURL,
|
|
195
|
+
api_key: vendorKey,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
writeConfig({ ...config, gatewayURL, apiKey, sessionId: session.session_id });
|
|
200
|
+
out(`${session.label} ${session.base_url}`);
|
|
201
|
+
out(`${session.models.length} models · baseline ${session.baseline_model}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function cmdChat(flags, positional) {
|
|
205
|
+
const config = readConfig();
|
|
206
|
+
const prompt = String(flags.prompt || positional.join(" ")).trim();
|
|
207
|
+
if (!prompt) die('Usage: promptimizer chat "What is 17 * 24?"');
|
|
208
|
+
const gatewayURL = gateway(flags, config);
|
|
209
|
+
const apiKey = flags.pmz || process.env.PROMPTIMIZER_API_KEY || config.apiKey;
|
|
210
|
+
const sessionId = apiKey ? undefined : config.sessionId;
|
|
211
|
+
if (!apiKey && !sessionId) die("Run promptimizer login or promptimizer connect first.");
|
|
212
|
+
|
|
213
|
+
const result = await request("/v1/chat/completions", {
|
|
214
|
+
method: "POST",
|
|
215
|
+
gatewayURL,
|
|
216
|
+
apiKey,
|
|
217
|
+
sessionId,
|
|
218
|
+
body: { messages: [{ role: "user", content: prompt }] },
|
|
219
|
+
});
|
|
220
|
+
const text = result.choices?.[0]?.message?.content?.trim() ?? "";
|
|
221
|
+
const meta = result.promptimizer ?? {};
|
|
222
|
+
const saved = result.usage?.cost?.saved_usd;
|
|
223
|
+
out();
|
|
224
|
+
out(text);
|
|
225
|
+
out();
|
|
226
|
+
const bits = [meta.model || result.model, meta.tier].filter(Boolean);
|
|
227
|
+
if (saved != null) bits.push(`saved ${usd(saved)}`);
|
|
228
|
+
if (meta.cache_hit) bits.push("cache");
|
|
229
|
+
if (meta.escalated) bits.push("escalated");
|
|
230
|
+
out(bits.join(" · "));
|
|
231
|
+
out();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function cmdModels(flags) {
|
|
235
|
+
const config = readConfig();
|
|
236
|
+
const gatewayURL = gateway(flags, config);
|
|
237
|
+
const apiKey = process.env.PROMPTIMIZER_API_KEY || config.apiKey;
|
|
238
|
+
const sessionId = apiKey ? undefined : config.sessionId;
|
|
239
|
+
if (!apiKey && !sessionId) die("Run promptimizer login or promptimizer connect first.");
|
|
240
|
+
const data = await request("/v1/models", { gatewayURL, apiKey, sessionId });
|
|
241
|
+
const models = data.data ?? [];
|
|
242
|
+
const width = Math.max(8, ...models.map((model) => String(model.tier).length));
|
|
243
|
+
out();
|
|
244
|
+
for (const model of models) {
|
|
245
|
+
const mark = model.id === data.baseline_model ? " baseline" : "";
|
|
246
|
+
out(` ${String(model.tier).padEnd(width + 2)}${model.id}${mark}`);
|
|
247
|
+
}
|
|
248
|
+
out();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function cmdSavings(flags) {
|
|
252
|
+
const config = readConfig();
|
|
253
|
+
const apiKey = requireKey(flags, config);
|
|
254
|
+
const data = await request("/v1/savings", { gatewayURL: gateway(flags, config), apiKey });
|
|
255
|
+
out();
|
|
256
|
+
out(`${usd(data.saved_usd)} saved`);
|
|
257
|
+
out();
|
|
258
|
+
out(` routed ${usd(data.actual_usd)}`);
|
|
259
|
+
out(` baseline ${usd(data.baseline_usd)}`);
|
|
260
|
+
out(` routing ${usd(data.routing_saved_usd)}`);
|
|
261
|
+
out(` cache ${usd(data.cache_saved_usd)}`);
|
|
262
|
+
out(` requests ${data.requests}`);
|
|
263
|
+
out();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function main() {
|
|
267
|
+
const { flags, positional } = parse(process.argv.slice(2));
|
|
268
|
+
if (flags.help || flags.h || positional[0] === "help" || positional.length === 0) {
|
|
269
|
+
help();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (flags.version || flags.v || positional[0] === "version") {
|
|
273
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
274
|
+
const pkg = JSON.parse(readFileSync(join(here, "../package.json"), "utf8"));
|
|
275
|
+
out(pkg.version);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const [command, ...rest] = positional;
|
|
280
|
+
if (command === "login") return cmdLogin(flags);
|
|
281
|
+
if (command === "logout") return cmdLogout();
|
|
282
|
+
if (command === "providers") return cmdProviders(flags);
|
|
283
|
+
if (command === "connect") return cmdConnect(flags, rest);
|
|
284
|
+
if (command === "chat") return cmdChat(flags, rest);
|
|
285
|
+
if (command === "models") return cmdModels(flags);
|
|
286
|
+
if (command === "savings") return cmdSavings(flags);
|
|
287
|
+
die(`Unknown command "${command}".`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
main().catch((error) => die(error instanceof Error ? error.message : String(error)));
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "promptimizer-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Login, connect a provider, route prompts, and read savings.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"promptimizer": "./bin/promptimizer.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/sreecharan-desu/promptimizer.git",
|
|
22
|
+
"directory": "packages/cli"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://hackathon-omega-liart.vercel.app",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/sreecharan-desu/promptimizer/issues"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"promptimizer",
|
|
30
|
+
"llm",
|
|
31
|
+
"cli",
|
|
32
|
+
"byok"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT"
|
|
35
|
+
}
|