promptpilot 0.1.8 → 0.2.2
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 +147 -285
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +109 -33
- package/dist/cli.js.map +1 -1
- package/dist/index.js +61 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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:
|
|
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 =
|
|
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 &&
|
|
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.
|
|
1703
|
-
"Sonnet: balanced cost and capability.
|
|
1704
|
-
"Opus: most capable and most expensive.
|
|
1705
|
-
"When routing priority is cheapest_adequate:
|
|
1706
|
-
"When routing priority is best_quality:
|
|
1707
|
-
"When routing priority is fastest_adequate:
|
|
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
|
-
|
|
1799
|
+
if (needsOpus) return opus ?? sonnet;
|
|
1800
|
+
if (isCodeTask) return sonnet ?? opus;
|
|
1801
|
+
return haiku ?? sonnet;
|
|
1755
1802
|
}
|
|
1756
|
-
if (
|
|
1803
|
+
if (priority === "best_quality") {
|
|
1757
1804
|
return opus ?? sonnet ?? haiku;
|
|
1758
1805
|
}
|
|
1759
|
-
if (
|
|
1760
|
-
|
|
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) {
|
|
@@ -2036,15 +2083,16 @@ function renderWideWelcome(options) {
|
|
|
2036
2083
|
style(options.cwd, "dim", options.color)
|
|
2037
2084
|
];
|
|
2038
2085
|
const rightLines = [
|
|
2039
|
-
style("
|
|
2040
|
-
|
|
2041
|
-
"
|
|
2086
|
+
style("Getting started", "accent", options.color),
|
|
2087
|
+
style('promptpilot claude "fix this CI failure"', "bold", options.color),
|
|
2088
|
+
"Pick a Claude tier and pipe straight into Claude",
|
|
2042
2089
|
PANEL_RULE,
|
|
2043
|
-
style("
|
|
2044
|
-
|
|
2090
|
+
style("Or optimize manually", "accent", options.color),
|
|
2091
|
+
style('promptpilot optimize "..." --plain | claude', "bold", options.color),
|
|
2045
2092
|
"",
|
|
2046
2093
|
style("Commands", "accent", options.color),
|
|
2047
|
-
"optimize
|
|
2094
|
+
"claude optimize + tier picker + pipe to claude",
|
|
2095
|
+
"optimize optimize and print to stdout",
|
|
2048
2096
|
"--help show the full CLI reference"
|
|
2049
2097
|
];
|
|
2050
2098
|
const rowCount = Math.max(leftLines.length, rightLines.length);
|
|
@@ -2074,9 +2122,9 @@ function renderCompactWelcome(options) {
|
|
|
2074
2122
|
"",
|
|
2075
2123
|
style(options.cwd, "dim", options.color),
|
|
2076
2124
|
"",
|
|
2077
|
-
style("
|
|
2078
|
-
'promptpilot
|
|
2079
|
-
'promptpilot optimize "..." --
|
|
2125
|
+
style("Getting started", "accent", options.color),
|
|
2126
|
+
'promptpilot claude "fix this CI failure"',
|
|
2127
|
+
'promptpilot optimize "..." --plain | claude',
|
|
2080
2128
|
"",
|
|
2081
2129
|
style("Help", "accent", options.color),
|
|
2082
2130
|
"promptpilot --help"
|
|
@@ -2228,7 +2276,7 @@ async function runCli(argv, io = { stdout: process.stdout, stderr: process.stder
|
|
|
2228
2276
|
`);
|
|
2229
2277
|
return 0;
|
|
2230
2278
|
}
|
|
2231
|
-
if (command !== "optimize") {
|
|
2279
|
+
if (command !== "optimize" && command !== "claude") {
|
|
2232
2280
|
io.stderr.write(`Unknown command: ${command}
|
|
2233
2281
|
`);
|
|
2234
2282
|
io.stderr.write(`${getHelpText()}
|
|
@@ -2271,15 +2319,16 @@ async function runCli(argv, io = { stdout: process.stdout, stderr: process.stder
|
|
|
2271
2319
|
io.stderr.write("A prompt is required.\n");
|
|
2272
2320
|
return 1;
|
|
2273
2321
|
}
|
|
2322
|
+
const wantTierMenu = command === "claude" || parsed.autoClaudeTiers;
|
|
2274
2323
|
let claudeTierChoice = null;
|
|
2275
|
-
if (
|
|
2324
|
+
if (wantTierMenu && io.stderr.isTTY && io.stdin) {
|
|
2276
2325
|
claudeTierChoice = await promptClaudeTierMenu(io.stderr, io.stdin);
|
|
2277
2326
|
if (claudeTierChoice === null) {
|
|
2278
2327
|
return 0;
|
|
2279
2328
|
}
|
|
2280
2329
|
}
|
|
2281
2330
|
const resolvedTargets = (() => {
|
|
2282
|
-
if (!
|
|
2331
|
+
if (!wantTierMenu) return parsed.targets;
|
|
2283
2332
|
if (!claudeTierChoice || claudeTierChoice === "auto") return [...CLAUDE_TIER_TARGETS, ...parsed.targets];
|
|
2284
2333
|
const picked = CLAUDE_TIER_TARGETS.find((t) => t.model.toLowerCase().includes(claudeTierChoice));
|
|
2285
2334
|
return picked ? [picked, ...parsed.targets] : [...CLAUDE_TIER_TARGETS, ...parsed.targets];
|
|
@@ -2316,6 +2365,11 @@ async function runCli(argv, io = { stdout: process.stdout, stderr: process.stder
|
|
|
2316
2365
|
bypassOptimization: parsed.bypassOptimization
|
|
2317
2366
|
});
|
|
2318
2367
|
spinner.stop();
|
|
2368
|
+
if (command === "claude") {
|
|
2369
|
+
const doSpawn = dependencies.spawnClaude ?? spawnClaudeProcess;
|
|
2370
|
+
const exitCode = await doSpawn(result.finalPrompt);
|
|
2371
|
+
return exitCode;
|
|
2372
|
+
}
|
|
2319
2373
|
if (parsed.json) {
|
|
2320
2374
|
io.stdout.write(`${toPrettyJson(result)}
|
|
2321
2375
|
`);
|
|
@@ -2500,9 +2554,16 @@ function parseOptimizeArgs(args) {
|
|
|
2500
2554
|
}
|
|
2501
2555
|
function getHelpText() {
|
|
2502
2556
|
return [
|
|
2503
|
-
"
|
|
2557
|
+
"Usage:",
|
|
2558
|
+
"",
|
|
2559
|
+
" promptpilot claude <prompt> [options]",
|
|
2560
|
+
" Optimize your prompt and pipe it directly to the claude CLI.",
|
|
2561
|
+
" Shows a tier picker (Haiku / Sonnet / Opus / Auto) in interactive mode.",
|
|
2562
|
+
"",
|
|
2563
|
+
" promptpilot optimize <prompt> [options]",
|
|
2564
|
+
" Optimize and print the result to stdout.",
|
|
2504
2565
|
"",
|
|
2505
|
-
"Options:",
|
|
2566
|
+
"Options (shared):",
|
|
2506
2567
|
" --session <id>",
|
|
2507
2568
|
" --model <name> Override auto-selected local Ollama model",
|
|
2508
2569
|
" --mode <mode>",
|
|
@@ -2524,9 +2585,6 @@ function getHelpText() {
|
|
|
2524
2585
|
" --store <local|sqlite>",
|
|
2525
2586
|
" --storage-dir <path>",
|
|
2526
2587
|
" --sqlite-path <path>",
|
|
2527
|
-
" --plain",
|
|
2528
|
-
" --json",
|
|
2529
|
-
" --clipboard Copy optimized prompt to clipboard",
|
|
2530
2588
|
" --debug",
|
|
2531
2589
|
" --save-context",
|
|
2532
2590
|
" --no-context",
|
|
@@ -2536,7 +2594,12 @@ function getHelpText() {
|
|
|
2536
2594
|
" --max-input-tokens <n>",
|
|
2537
2595
|
" --timeout <ms>",
|
|
2538
2596
|
" --bypass-optimization",
|
|
2539
|
-
"
|
|
2597
|
+
"",
|
|
2598
|
+
"Options (optimize only):",
|
|
2599
|
+
" --plain",
|
|
2600
|
+
" --json",
|
|
2601
|
+
" --clipboard Copy optimized prompt to clipboard",
|
|
2602
|
+
" --claude Route between Haiku, Sonnet, and Opus automatically"
|
|
2540
2603
|
].join("\n");
|
|
2541
2604
|
}
|
|
2542
2605
|
function parseTargetCandidate(raw, index) {
|
|
@@ -2597,6 +2660,19 @@ function readPackageVersion() {
|
|
|
2597
2660
|
return "dev";
|
|
2598
2661
|
}
|
|
2599
2662
|
}
|
|
2663
|
+
function spawnClaudeProcess(prompt) {
|
|
2664
|
+
return new Promise((resolve, reject) => {
|
|
2665
|
+
const child = spawn("claude", [], { stdio: ["pipe", "inherit", "inherit"] });
|
|
2666
|
+
if (!child.stdin) {
|
|
2667
|
+
reject(new Error("Failed to open stdin pipe to claude process"));
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2670
|
+
child.stdin.write(prompt);
|
|
2671
|
+
child.stdin.end();
|
|
2672
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
2673
|
+
child.on("error", reject);
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2600
2676
|
function copyToClipboard(text) {
|
|
2601
2677
|
const commands = [
|
|
2602
2678
|
{ cmd: "xclip", args: ["-selection", "clipboard"], platform: "linux" },
|