promptpilot 0.1.7 → 0.1.8
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/dist/cli.js +216 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +94 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1417,6 +1417,18 @@ Mode: Ultra compression. Minimize tokens aggressively.` : getOptimizationSystemP
|
|
|
1417
1417
|
};
|
|
1418
1418
|
}
|
|
1419
1419
|
if (!this.client.listModels) {
|
|
1420
|
+
if (isClaudeTiersOnlyTargetSet(availableTargets)) {
|
|
1421
|
+
const selected = selectClaudeTierHeuristic(options.input, options.routingPriority, availableTargets);
|
|
1422
|
+
if (selected) {
|
|
1423
|
+
return {
|
|
1424
|
+
selectedTarget: stripInternalTargetFields(selected),
|
|
1425
|
+
rankedTargets: [{ ...stripInternalTargetFields(selected), rank: 1, reason: "Selected by Claude tier heuristic (no local Qwen router available)." }],
|
|
1426
|
+
routingReason: "Selected by Claude tier heuristic (no local Qwen router available).",
|
|
1427
|
+
routingWarnings: [],
|
|
1428
|
+
routingProvider: "heuristic"
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1420
1432
|
return {
|
|
1421
1433
|
selectedTarget: null,
|
|
1422
1434
|
rankedTargets: [],
|
|
@@ -1441,12 +1453,13 @@ Mode: Ultra compression. Minimize tokens aggressively.` : getOptimizationSystemP
|
|
|
1441
1453
|
routingProvider: null
|
|
1442
1454
|
};
|
|
1443
1455
|
}
|
|
1456
|
+
const claudeTiersOnly = isClaudeTiersOnlyTargetSet(availableTargets);
|
|
1444
1457
|
const response = await this.client.generateJson({
|
|
1445
1458
|
model: routerModel,
|
|
1446
1459
|
timeoutMs: options.input.timeoutMs ?? this.config.timeoutMs,
|
|
1447
1460
|
temperature: 0,
|
|
1448
1461
|
format: "json",
|
|
1449
|
-
systemPrompt: buildDownstreamRoutingSystemPrompt(options.routingPriority, options.workloadBias),
|
|
1462
|
+
systemPrompt: buildDownstreamRoutingSystemPrompt(options.routingPriority, options.workloadBias, claudeTiersOnly),
|
|
1450
1463
|
prompt: JSON.stringify(
|
|
1451
1464
|
{
|
|
1452
1465
|
objective: "Rank the caller-supplied downstream targets for this prompt and choose the best top target.",
|
|
@@ -1510,6 +1523,18 @@ Mode: Ultra compression. Minimize tokens aggressively.` : getOptimizationSystemP
|
|
|
1510
1523
|
routingProvider: routerModel
|
|
1511
1524
|
};
|
|
1512
1525
|
} catch {
|
|
1526
|
+
if (isClaudeTiersOnlyTargetSet(availableTargets)) {
|
|
1527
|
+
const selected = selectClaudeTierHeuristic(options.input, options.routingPriority, availableTargets);
|
|
1528
|
+
if (selected) {
|
|
1529
|
+
return {
|
|
1530
|
+
selectedTarget: stripInternalTargetFields(selected),
|
|
1531
|
+
rankedTargets: [{ ...stripInternalTargetFields(selected), rank: 1, reason: "Selected by Claude tier heuristic (Qwen routing failed)." }],
|
|
1532
|
+
routingReason: "Selected by Claude tier heuristic (Qwen routing failed).",
|
|
1533
|
+
routingWarnings: ["Qwen downstream routing failed; fell back to Claude tier heuristic."],
|
|
1534
|
+
routingProvider: "heuristic"
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1513
1538
|
return {
|
|
1514
1539
|
selectedTarget: null,
|
|
1515
1540
|
rankedTargets: [],
|
|
@@ -1658,8 +1683,8 @@ function stripInternalTargetFields(target) {
|
|
|
1658
1683
|
latencyRank: target.latencyRank
|
|
1659
1684
|
};
|
|
1660
1685
|
}
|
|
1661
|
-
function buildDownstreamRoutingSystemPrompt(priority, workloadBias) {
|
|
1662
|
-
|
|
1686
|
+
function buildDownstreamRoutingSystemPrompt(priority, workloadBias, claudeTiersOnly = false) {
|
|
1687
|
+
const lines = [
|
|
1663
1688
|
"You are a downstream model router for PromptPilot.",
|
|
1664
1689
|
"Return strict JSON only with this shape:",
|
|
1665
1690
|
'{"selectedTargetId":"string","rankedTargetIds":["string"],"reason":"string"}',
|
|
@@ -1670,7 +1695,71 @@ function buildDownstreamRoutingSystemPrompt(priority, workloadBias) {
|
|
|
1670
1695
|
"Code-first means ambiguous prompts should default toward coding-capable or agentic-capable targets.",
|
|
1671
1696
|
"Explicit email, support, chat, and lightweight writing prompts may prefer cheaper lighter targets.",
|
|
1672
1697
|
"Do not invent targets. Do not output prose outside JSON."
|
|
1673
|
-
]
|
|
1698
|
+
];
|
|
1699
|
+
if (claudeTiersOnly) {
|
|
1700
|
+
lines.push(
|
|
1701
|
+
"You are choosing between Claude model tiers (Haiku, Sonnet, Opus).",
|
|
1702
|
+
"Haiku: fastest and cheapest. Best for email, chat, support, summarization, and simple rewrites. Avoid for deep coding or multi-step reasoning.",
|
|
1703
|
+
"Sonnet: balanced cost and capability. Best for coding, debugging, refactoring, writing, and general-purpose tasks. The default for most prompts.",
|
|
1704
|
+
"Opus: most capable and most expensive. Reserve for complex architecture decisions, multi-constraint agentic planning, long-horizon reasoning, or prompts that clearly require the strongest model.",
|
|
1705
|
+
"When routing priority is cheapest_adequate: prefer Haiku for lightweight tasks, Sonnet for most code and writing tasks, and Opus only when clearly necessary.",
|
|
1706
|
+
"When routing priority is best_quality: prefer Opus for code and reasoning, Sonnet for writing and simple code.",
|
|
1707
|
+
"When routing priority is fastest_adequate: prefer Haiku unless the task clearly needs Sonnet-level capability."
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
return lines.join("\n");
|
|
1711
|
+
}
|
|
1712
|
+
var CLAUDE_TIER_TARGETS = [
|
|
1713
|
+
{
|
|
1714
|
+
provider: "anthropic",
|
|
1715
|
+
model: "claude-haiku-4-5",
|
|
1716
|
+
label: "anthropic:claude-haiku-4-5",
|
|
1717
|
+
capabilities: ["writing", "email", "support", "chat", "summarization"],
|
|
1718
|
+
costRank: 1,
|
|
1719
|
+
latencyRank: 1
|
|
1720
|
+
},
|
|
1721
|
+
{
|
|
1722
|
+
provider: "anthropic",
|
|
1723
|
+
model: "claude-sonnet-4-6",
|
|
1724
|
+
label: "anthropic:claude-sonnet-4-6",
|
|
1725
|
+
capabilities: ["coding", "writing", "agentic", "tool_use", "refactor", "debugging"],
|
|
1726
|
+
costRank: 2,
|
|
1727
|
+
latencyRank: 2
|
|
1728
|
+
},
|
|
1729
|
+
{
|
|
1730
|
+
provider: "anthropic",
|
|
1731
|
+
model: "claude-opus-4-6",
|
|
1732
|
+
label: "anthropic:claude-opus-4-6",
|
|
1733
|
+
capabilities: ["coding", "agentic", "tool_use", "refactor", "debugging", "architecture", "writing"],
|
|
1734
|
+
costRank: 3,
|
|
1735
|
+
latencyRank: 3
|
|
1736
|
+
}
|
|
1737
|
+
];
|
|
1738
|
+
function isClaudeTiersOnlyTargetSet(targets) {
|
|
1739
|
+
return targets.length >= 2 && targets.every(
|
|
1740
|
+
(t) => t.provider === "anthropic" && /haiku|sonnet|opus/i.test(t.model)
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
function selectClaudeTierHeuristic(input, priority, targets) {
|
|
1744
|
+
const haiku = targets.find((t) => /haiku/i.test(t.model)) ?? null;
|
|
1745
|
+
const sonnet = targets.find((t) => /sonnet/i.test(t.model)) ?? null;
|
|
1746
|
+
const opus = targets.find((t) => /opus/i.test(t.model)) ?? null;
|
|
1747
|
+
const task = (input.task ?? "").toLowerCase();
|
|
1748
|
+
const preset = (input.preset ?? "").toLowerCase();
|
|
1749
|
+
const hints = input.targetHints ?? [];
|
|
1750
|
+
const prompt = input.prompt;
|
|
1751
|
+
const isLightweight = ["email", "chat", "support", "summarization"].includes(task) || ["email", "chat", "support", "summarization"].includes(preset) || hints.some((h) => ["email", "support", "chat", "summarization"].includes(h));
|
|
1752
|
+
const needsOpus = /\b(architect|architecture|design system|migration plan|multi.?step|complex.*refactor|long.?horizon|agentic.*plan)\b/i.test(prompt) || hints.includes("architecture") || priority === "best_quality";
|
|
1753
|
+
if (priority === "fastest_adequate") {
|
|
1754
|
+
return isLightweight || !needsOpus ? haiku ?? sonnet : sonnet ?? haiku;
|
|
1755
|
+
}
|
|
1756
|
+
if (needsOpus) {
|
|
1757
|
+
return opus ?? sonnet ?? haiku;
|
|
1758
|
+
}
|
|
1759
|
+
if (isLightweight && priority === "cheapest_adequate") {
|
|
1760
|
+
return haiku ?? sonnet;
|
|
1761
|
+
}
|
|
1762
|
+
return sonnet ?? haiku ?? opus;
|
|
1674
1763
|
}
|
|
1675
1764
|
function inferCapabilities(target) {
|
|
1676
1765
|
const lower = `${target.provider} ${target.model} ${target.label ?? ""}`.toLowerCase();
|
|
@@ -1819,6 +1908,109 @@ function createOptimizer(config = {}) {
|
|
|
1819
1908
|
return new PromptOptimizer(config);
|
|
1820
1909
|
}
|
|
1821
1910
|
|
|
1911
|
+
// src/cliMenu.ts
|
|
1912
|
+
var ARROW_UP = "\x1B[A";
|
|
1913
|
+
var ARROW_DOWN = "\x1B[B";
|
|
1914
|
+
var ENTER = "\r";
|
|
1915
|
+
var CTRL_C = "";
|
|
1916
|
+
var ESCAPE = "\x1B";
|
|
1917
|
+
var CLAUDE_TIER_OPTIONS = [
|
|
1918
|
+
{
|
|
1919
|
+
key: "auto",
|
|
1920
|
+
label: "Auto",
|
|
1921
|
+
badge: "recommended",
|
|
1922
|
+
description: "PromptPilot picks the best tier for your prompt"
|
|
1923
|
+
},
|
|
1924
|
+
{
|
|
1925
|
+
key: "haiku",
|
|
1926
|
+
label: "Haiku",
|
|
1927
|
+
badge: "fastest \xB7 cheapest",
|
|
1928
|
+
description: "email, chat, summarization, simple rewrites"
|
|
1929
|
+
},
|
|
1930
|
+
{
|
|
1931
|
+
key: "sonnet",
|
|
1932
|
+
label: "Sonnet",
|
|
1933
|
+
badge: "balanced",
|
|
1934
|
+
description: "coding, debugging, writing, general-purpose"
|
|
1935
|
+
},
|
|
1936
|
+
{
|
|
1937
|
+
key: "opus",
|
|
1938
|
+
label: "Opus",
|
|
1939
|
+
badge: "most capable",
|
|
1940
|
+
description: "architecture, complex reasoning, agentic planning"
|
|
1941
|
+
}
|
|
1942
|
+
];
|
|
1943
|
+
function renderClaudeMenu(options, selected) {
|
|
1944
|
+
const lines = ["Select Claude model tier:\n"];
|
|
1945
|
+
for (let index = 0; index < options.length; index++) {
|
|
1946
|
+
const opt = options[index];
|
|
1947
|
+
const isSelected = index === selected;
|
|
1948
|
+
const cursor = isSelected ? "\u276F" : " ";
|
|
1949
|
+
const label = isSelected ? `\x1B[1m${opt.label}\x1B[0m` : opt.label;
|
|
1950
|
+
const badge = `\x1B[2m${opt.badge}\x1B[0m`;
|
|
1951
|
+
const desc = `\x1B[2m${opt.description}\x1B[0m`;
|
|
1952
|
+
lines.push(` ${cursor} ${label.padEnd(isSelected ? 14 : 6)} ${badge}`);
|
|
1953
|
+
lines.push(` ${desc}`);
|
|
1954
|
+
}
|
|
1955
|
+
lines.push("\n \x1B[2m\u2191/\u2193 move Enter confirm q cancel\x1B[0m");
|
|
1956
|
+
return lines.join("\n");
|
|
1957
|
+
}
|
|
1958
|
+
async function promptClaudeTierMenu(stderr, stdin) {
|
|
1959
|
+
return new Promise((resolve) => {
|
|
1960
|
+
let selected = 0;
|
|
1961
|
+
const options = CLAUDE_TIER_OPTIONS;
|
|
1962
|
+
const lineCount = options.length * 2 + 3;
|
|
1963
|
+
const draw = (first) => {
|
|
1964
|
+
if (!first) {
|
|
1965
|
+
stderr.write(`\x1B[${lineCount}A`);
|
|
1966
|
+
}
|
|
1967
|
+
stderr.write(`\x1B[?25l${renderClaudeMenu(options, selected)}
|
|
1968
|
+
`);
|
|
1969
|
+
};
|
|
1970
|
+
const cleanup = () => {
|
|
1971
|
+
stderr.write("\x1B[?25h");
|
|
1972
|
+
stdin.setRawMode(false);
|
|
1973
|
+
stdin.pause();
|
|
1974
|
+
stdin.removeListener("data", onData);
|
|
1975
|
+
};
|
|
1976
|
+
const onData = (chunk) => {
|
|
1977
|
+
if (chunk === CTRL_C) {
|
|
1978
|
+
cleanup();
|
|
1979
|
+
process.exit(0);
|
|
1980
|
+
}
|
|
1981
|
+
if (chunk === "q" || chunk === ESCAPE) {
|
|
1982
|
+
cleanup();
|
|
1983
|
+
stderr.write("\n");
|
|
1984
|
+
resolve(null);
|
|
1985
|
+
return;
|
|
1986
|
+
}
|
|
1987
|
+
if (chunk === ARROW_UP) {
|
|
1988
|
+
selected = (selected - 1 + options.length) % options.length;
|
|
1989
|
+
draw(false);
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1992
|
+
if (chunk === ARROW_DOWN) {
|
|
1993
|
+
selected = (selected + 1) % options.length;
|
|
1994
|
+
draw(false);
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
if (chunk === ENTER) {
|
|
1998
|
+
cleanup();
|
|
1999
|
+
stderr.write(`
|
|
2000
|
+
Selected: \x1B[1m${options[selected].label}\x1B[0m
|
|
2001
|
+
|
|
2002
|
+
`);
|
|
2003
|
+
resolve(options[selected].key);
|
|
2004
|
+
}
|
|
2005
|
+
};
|
|
2006
|
+
stdin.setRawMode(true);
|
|
2007
|
+
stdin.resume();
|
|
2008
|
+
stdin.setEncoding("utf8");
|
|
2009
|
+
stdin.on("data", onData);
|
|
2010
|
+
draw(true);
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
|
|
1822
2014
|
// src/cliWelcome.ts
|
|
1823
2015
|
import { basename } from "path";
|
|
1824
2016
|
var MIN_WIDE_COLUMNS = 76;
|
|
@@ -2079,6 +2271,19 @@ async function runCli(argv, io = { stdout: process.stdout, stderr: process.stder
|
|
|
2079
2271
|
io.stderr.write("A prompt is required.\n");
|
|
2080
2272
|
return 1;
|
|
2081
2273
|
}
|
|
2274
|
+
let claudeTierChoice = null;
|
|
2275
|
+
if (parsed.autoClaudeTiers && io.stderr.isTTY && io.stdin) {
|
|
2276
|
+
claudeTierChoice = await promptClaudeTierMenu(io.stderr, io.stdin);
|
|
2277
|
+
if (claudeTierChoice === null) {
|
|
2278
|
+
return 0;
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
const resolvedTargets = (() => {
|
|
2282
|
+
if (!parsed.autoClaudeTiers) return parsed.targets;
|
|
2283
|
+
if (!claudeTierChoice || claudeTierChoice === "auto") return [...CLAUDE_TIER_TARGETS, ...parsed.targets];
|
|
2284
|
+
const picked = CLAUDE_TIER_TARGETS.find((t) => t.model.toLowerCase().includes(claudeTierChoice));
|
|
2285
|
+
return picked ? [picked, ...parsed.targets] : [...CLAUDE_TIER_TARGETS, ...parsed.targets];
|
|
2286
|
+
})();
|
|
2082
2287
|
const spinner = createSpinner(io.stderr, io.stderr.isTTY ?? false);
|
|
2083
2288
|
try {
|
|
2084
2289
|
spinner.start("optimizing");
|
|
@@ -2096,7 +2301,7 @@ async function runCli(argv, io = { stdout: process.stdout, stderr: process.stder
|
|
|
2096
2301
|
maxLength: parsed.maxLength,
|
|
2097
2302
|
tags: parsed.tags,
|
|
2098
2303
|
pinnedConstraints: parsed.pinnedConstraints,
|
|
2099
|
-
availableTargets:
|
|
2304
|
+
availableTargets: resolvedTargets,
|
|
2100
2305
|
routingEnabled: parsed.routingEnabled,
|
|
2101
2306
|
routingPriority: parsed.routingPriority,
|
|
2102
2307
|
routingTopK: parsed.routingTopK,
|
|
@@ -2168,6 +2373,7 @@ function parseOptimizeArgs(args) {
|
|
|
2168
2373
|
clearSession: false,
|
|
2169
2374
|
useContext: true,
|
|
2170
2375
|
bypassOptimization: false,
|
|
2376
|
+
autoClaudeTiers: false,
|
|
2171
2377
|
help: false,
|
|
2172
2378
|
tags: [],
|
|
2173
2379
|
pinnedConstraints: [],
|
|
@@ -2278,6 +2484,9 @@ function parseOptimizeArgs(args) {
|
|
|
2278
2484
|
case "--bypass-optimization":
|
|
2279
2485
|
parsed.bypassOptimization = true;
|
|
2280
2486
|
break;
|
|
2487
|
+
case "--claude":
|
|
2488
|
+
parsed.autoClaudeTiers = true;
|
|
2489
|
+
break;
|
|
2281
2490
|
case "--help":
|
|
2282
2491
|
case "-h":
|
|
2283
2492
|
parsed.help = true;
|
|
@@ -2326,7 +2535,8 @@ function getHelpText() {
|
|
|
2326
2535
|
" --max-context-tokens <n>",
|
|
2327
2536
|
" --max-input-tokens <n>",
|
|
2328
2537
|
" --timeout <ms>",
|
|
2329
|
-
" --bypass-optimization"
|
|
2538
|
+
" --bypass-optimization",
|
|
2539
|
+
" --claude Route between Haiku, Sonnet, and Opus automatically"
|
|
2330
2540
|
].join("\n");
|
|
2331
2541
|
}
|
|
2332
2542
|
function parseTargetCandidate(raw, index) {
|