promptpilot 0.1.8 → 0.2.1

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.d.ts CHANGED
@@ -21,6 +21,7 @@ interface CliDependencies {
21
21
  columns?: number;
22
22
  user?: string;
23
23
  };
24
+ spawnClaude?: (prompt: string) => Promise<number>;
24
25
  }
25
26
  declare function runCli(argv: string[], io?: CliIO, dependencies?: CliDependencies): Promise<number>;
26
27
 
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/cli.ts
4
4
  import { readFileSync, realpathSync } from "fs";
5
5
  import { fileURLToPath } from "url";
6
- import { execSync } from "child_process";
6
+ import { execSync, spawn } from "child_process";
7
7
 
8
8
  // src/errors.ts
9
9
  var InvalidPromptError = class extends Error {
@@ -1454,6 +1454,16 @@ Mode: Ultra compression. Minimize tokens aggressively.` : getOptimizationSystemP
1454
1454
  };
1455
1455
  }
1456
1456
  const claudeTiersOnly = isClaudeTiersOnlyTargetSet(availableTargets);
1457
+ const routingCandidates = claudeTiersOnly ? filterClaudeTierCandidates(availableTargets, options.input, options.routingPriority) : availableTargets;
1458
+ if (routingCandidates.length === 1) {
1459
+ return {
1460
+ selectedTarget: stripInternalTargetFields(routingCandidates[0]),
1461
+ rankedTargets: [{ ...stripInternalTargetFields(routingCandidates[0]), rank: 1, reason: "Selected by Claude tier pre-filter based on prompt signals." }],
1462
+ routingReason: "Selected by Claude tier pre-filter based on prompt signals.",
1463
+ routingWarnings: [],
1464
+ routingProvider: "heuristic"
1465
+ };
1466
+ }
1457
1467
  const response = await this.client.generateJson({
1458
1468
  model: routerModel,
1459
1469
  timeoutMs: options.input.timeoutMs ?? this.config.timeoutMs,
@@ -1471,7 +1481,7 @@ Mode: Ultra compression. Minimize tokens aggressively.` : getOptimizationSystemP
1471
1481
  targetHints: options.input.targetHints ?? [],
1472
1482
  workloadBias: options.workloadBias,
1473
1483
  routingPriority: options.routingPriority,
1474
- candidateTargets: availableTargets.map((target) => ({
1484
+ candidateTargets: routingCandidates.map((target) => ({
1475
1485
  id: target.id,
1476
1486
  provider: target.provider,
1477
1487
  model: target.model,
@@ -1490,7 +1500,7 @@ Mode: Ultra compression. Minimize tokens aggressively.` : getOptimizationSystemP
1490
1500
  new Set((response.rankedTargetIds ?? []).map((value) => value.trim()).filter(Boolean))
1491
1501
  ).slice(0, Math.max(1, options.routingTopK));
1492
1502
  const rankedTargets = rankedTargetIds.map((id, index) => {
1493
- const target = availableTargets.find((candidate) => candidate.id === id);
1503
+ const target = routingCandidates.find((candidate) => candidate.id === id);
1494
1504
  if (!target) {
1495
1505
  return null;
1496
1506
  }
@@ -1501,7 +1511,7 @@ Mode: Ultra compression. Minimize tokens aggressively.` : getOptimizationSystemP
1501
1511
  };
1502
1512
  }).filter((value) => value !== null);
1503
1513
  const selectedTargetId = response.selectedTargetId?.trim();
1504
- const selectedTargetCandidate = (selectedTargetId && availableTargets.find((candidate) => candidate.id === selectedTargetId)) ?? (rankedTargets[0] ? availableTargets.find(
1514
+ const selectedTargetCandidate = (selectedTargetId && routingCandidates.find((candidate) => candidate.id === selectedTargetId)) ?? (rankedTargets[0] ? routingCandidates.find(
1505
1515
  (candidate) => candidate.provider === rankedTargets[0].provider && candidate.model === rankedTargets[0].model && candidate.label === rankedTargets[0].label
1506
1516
  ) ?? null : null);
1507
1517
  if (!selectedTargetCandidate || rankedTargets.length === 0) {
@@ -1699,12 +1709,13 @@ function buildDownstreamRoutingSystemPrompt(priority, workloadBias, claudeTiersO
1699
1709
  if (claudeTiersOnly) {
1700
1710
  lines.push(
1701
1711
  "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."
1712
+ "Haiku: fastest and cheapest. ONLY suitable for email, chat, support, summarization, and trivial one-sentence rewrites. Do NOT use Haiku for any coding, debugging, refactoring, or technical tasks.",
1713
+ "Sonnet: balanced cost and capability. The DEFAULT for all coding, debugging, refactoring, writing, and general-purpose tasks. If the prompt mentions code, a file, a module, a bug, or any technical work, choose Sonnet at minimum.",
1714
+ "Opus: most capable and most expensive. Use for complex architecture decisions, multi-constraint agentic planning, system design, long-horizon reasoning, or when the prompt explicitly requires the strongest model.",
1715
+ "When routing priority is cheapest_adequate: Haiku for non-technical lightweight tasks only, Sonnet for anything involving code or technical content, Opus only when clearly necessary.",
1716
+ "When routing priority is best_quality: Opus for all code and reasoning tasks, Sonnet for writing and non-technical tasks.",
1717
+ "When routing priority is fastest_adequate: Haiku only for lightweight non-technical tasks, Sonnet otherwise.",
1718
+ "IMPORTANT: refactor, debug, fix, auth, module, CI, test, and TypeScript are all coding signals \u2014 always choose Sonnet or Opus for these, never Haiku."
1708
1719
  );
1709
1720
  }
1710
1721
  return lines.join("\n");
@@ -1740,6 +1751,39 @@ function isClaudeTiersOnlyTargetSet(targets) {
1740
1751
  (t) => t.provider === "anthropic" && /haiku|sonnet|opus/i.test(t.model)
1741
1752
  );
1742
1753
  }
1754
+ function isCodeSignal(input) {
1755
+ const task = (input.task ?? "").toLowerCase();
1756
+ const preset = (input.preset ?? "").toLowerCase();
1757
+ const hints = input.targetHints ?? [];
1758
+ return task === "code" || preset === "code" || hints.some((h) => ["coding", "agentic", "tool_use", "refactor", "debugging", "architecture"].includes(h)) || /\b(refactor|debug|fix|auth|module|ci|test|typescript|javascript|function|class|api|endpoint|build|deploy|lint|migration)\b/i.test(input.prompt);
1759
+ }
1760
+ function isArchitectureSignal(input) {
1761
+ const hints = input.targetHints ?? [];
1762
+ return hints.includes("architecture") || /\b(architect|architecture|design system|migration plan|multi.?step|long.?horizon|agentic.*plan|system design|microservice|monolith)\b/i.test(input.prompt);
1763
+ }
1764
+ function filterClaudeTierCandidates(targets, input, priority) {
1765
+ if (priority === "best_quality") {
1766
+ const filtered = targets.filter((t) => /opus|sonnet/i.test(t.model));
1767
+ return filtered.length > 0 ? filtered : targets;
1768
+ }
1769
+ if (priority === "cheapest_adequate") {
1770
+ if (isArchitectureSignal(input)) {
1771
+ const filtered2 = targets.filter((t) => /opus|sonnet/i.test(t.model));
1772
+ return filtered2.length > 0 ? filtered2 : targets;
1773
+ }
1774
+ if (isCodeSignal(input)) {
1775
+ const sonnet = targets.find((t) => /sonnet/i.test(t.model));
1776
+ return sonnet ? [sonnet] : targets.filter((t) => !/haiku/i.test(t.model));
1777
+ }
1778
+ const filtered = targets.filter((t) => /haiku|sonnet/i.test(t.model));
1779
+ return filtered.length > 0 ? filtered : targets;
1780
+ }
1781
+ if (priority === "fastest_adequate") {
1782
+ const filtered = targets.filter((t) => !/opus/i.test(t.model));
1783
+ return filtered.length > 0 ? filtered : targets;
1784
+ }
1785
+ return targets;
1786
+ }
1743
1787
  function selectClaudeTierHeuristic(input, priority, targets) {
1744
1788
  const haiku = targets.find((t) => /haiku/i.test(t.model)) ?? null;
1745
1789
  const sonnet = targets.find((t) => /sonnet/i.test(t.model)) ?? null;
@@ -1750,15 +1794,18 @@ function selectClaudeTierHeuristic(input, priority, targets) {
1750
1794
  const prompt = input.prompt;
1751
1795
  const isLightweight = ["email", "chat", "support", "summarization"].includes(task) || ["email", "chat", "support", "summarization"].includes(preset) || hints.some((h) => ["email", "support", "chat", "summarization"].includes(h));
1752
1796
  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";
1797
+ const isCodeTask = ["code"].includes(task) || ["code"].includes(preset) || hints.some((h) => ["coding", "agentic", "tool_use", "refactor", "debugging", "architecture"].includes(h)) || /\b(refactor|debug|fix|auth|module|ci|test|typescript|javascript|function|class|api|endpoint)\b/i.test(prompt);
1753
1798
  if (priority === "fastest_adequate") {
1754
- return isLightweight || !needsOpus ? haiku ?? sonnet : sonnet ?? haiku;
1799
+ if (needsOpus) return opus ?? sonnet;
1800
+ if (isCodeTask) return sonnet ?? opus;
1801
+ return haiku ?? sonnet;
1755
1802
  }
1756
- if (needsOpus) {
1803
+ if (priority === "best_quality") {
1757
1804
  return opus ?? sonnet ?? haiku;
1758
1805
  }
1759
- if (isLightweight && priority === "cheapest_adequate") {
1760
- return haiku ?? sonnet;
1761
- }
1806
+ if (needsOpus) return opus ?? sonnet;
1807
+ if (isCodeTask) return sonnet ?? opus;
1808
+ if (isLightweight) return haiku ?? sonnet;
1762
1809
  return sonnet ?? haiku ?? opus;
1763
1810
  }
1764
1811
  function inferCapabilities(target) {
@@ -2228,7 +2275,7 @@ async function runCli(argv, io = { stdout: process.stdout, stderr: process.stder
2228
2275
  `);
2229
2276
  return 0;
2230
2277
  }
2231
- if (command !== "optimize") {
2278
+ if (command !== "optimize" && command !== "claude") {
2232
2279
  io.stderr.write(`Unknown command: ${command}
2233
2280
  `);
2234
2281
  io.stderr.write(`${getHelpText()}
@@ -2271,15 +2318,16 @@ async function runCli(argv, io = { stdout: process.stdout, stderr: process.stder
2271
2318
  io.stderr.write("A prompt is required.\n");
2272
2319
  return 1;
2273
2320
  }
2321
+ const wantTierMenu = command === "claude" || parsed.autoClaudeTiers;
2274
2322
  let claudeTierChoice = null;
2275
- if (parsed.autoClaudeTiers && io.stderr.isTTY && io.stdin) {
2323
+ if (wantTierMenu && io.stderr.isTTY && io.stdin) {
2276
2324
  claudeTierChoice = await promptClaudeTierMenu(io.stderr, io.stdin);
2277
2325
  if (claudeTierChoice === null) {
2278
2326
  return 0;
2279
2327
  }
2280
2328
  }
2281
2329
  const resolvedTargets = (() => {
2282
- if (!parsed.autoClaudeTiers) return parsed.targets;
2330
+ if (!wantTierMenu) return parsed.targets;
2283
2331
  if (!claudeTierChoice || claudeTierChoice === "auto") return [...CLAUDE_TIER_TARGETS, ...parsed.targets];
2284
2332
  const picked = CLAUDE_TIER_TARGETS.find((t) => t.model.toLowerCase().includes(claudeTierChoice));
2285
2333
  return picked ? [picked, ...parsed.targets] : [...CLAUDE_TIER_TARGETS, ...parsed.targets];
@@ -2316,6 +2364,11 @@ async function runCli(argv, io = { stdout: process.stdout, stderr: process.stder
2316
2364
  bypassOptimization: parsed.bypassOptimization
2317
2365
  });
2318
2366
  spinner.stop();
2367
+ if (command === "claude") {
2368
+ const doSpawn = dependencies.spawnClaude ?? spawnClaudeProcess;
2369
+ const exitCode = await doSpawn(result.finalPrompt);
2370
+ return exitCode;
2371
+ }
2319
2372
  if (parsed.json) {
2320
2373
  io.stdout.write(`${toPrettyJson(result)}
2321
2374
  `);
@@ -2500,9 +2553,16 @@ function parseOptimizeArgs(args) {
2500
2553
  }
2501
2554
  function getHelpText() {
2502
2555
  return [
2503
- "promptpilot optimize <prompt> [options]",
2556
+ "Usage:",
2557
+ "",
2558
+ " promptpilot claude <prompt> [options]",
2559
+ " Optimize your prompt and pipe it directly to the claude CLI.",
2560
+ " Shows a tier picker (Haiku / Sonnet / Opus / Auto) in interactive mode.",
2561
+ "",
2562
+ " promptpilot optimize <prompt> [options]",
2563
+ " Optimize and print the result to stdout.",
2504
2564
  "",
2505
- "Options:",
2565
+ "Options (shared):",
2506
2566
  " --session <id>",
2507
2567
  " --model <name> Override auto-selected local Ollama model",
2508
2568
  " --mode <mode>",
@@ -2524,9 +2584,6 @@ function getHelpText() {
2524
2584
  " --store <local|sqlite>",
2525
2585
  " --storage-dir <path>",
2526
2586
  " --sqlite-path <path>",
2527
- " --plain",
2528
- " --json",
2529
- " --clipboard Copy optimized prompt to clipboard",
2530
2587
  " --debug",
2531
2588
  " --save-context",
2532
2589
  " --no-context",
@@ -2536,7 +2593,12 @@ function getHelpText() {
2536
2593
  " --max-input-tokens <n>",
2537
2594
  " --timeout <ms>",
2538
2595
  " --bypass-optimization",
2539
- " --claude Route between Haiku, Sonnet, and Opus automatically"
2596
+ "",
2597
+ "Options (optimize only):",
2598
+ " --plain",
2599
+ " --json",
2600
+ " --clipboard Copy optimized prompt to clipboard",
2601
+ " --claude Route between Haiku, Sonnet, and Opus automatically"
2540
2602
  ].join("\n");
2541
2603
  }
2542
2604
  function parseTargetCandidate(raw, index) {
@@ -2597,6 +2659,19 @@ function readPackageVersion() {
2597
2659
  return "dev";
2598
2660
  }
2599
2661
  }
2662
+ function spawnClaudeProcess(prompt) {
2663
+ return new Promise((resolve, reject) => {
2664
+ const child = spawn("claude", [], { stdio: ["pipe", "inherit", "inherit"] });
2665
+ if (!child.stdin) {
2666
+ reject(new Error("Failed to open stdin pipe to claude process"));
2667
+ return;
2668
+ }
2669
+ child.stdin.write(prompt);
2670
+ child.stdin.end();
2671
+ child.on("close", (code) => resolve(code ?? 1));
2672
+ child.on("error", reject);
2673
+ });
2674
+ }
2600
2675
  function copyToClipboard(text) {
2601
2676
  const commands = [
2602
2677
  { cmd: "xclip", args: ["-selection", "clipboard"], platform: "linux" },