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 +1 -0
- package/dist/cli.js +99 -24
- 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.d.ts
CHANGED
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) {
|
|
@@ -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 (
|
|
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 (!
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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" },
|