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 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
- return [
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
- ].join("\n");
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: parsed.targets,
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) {