promptpilot 0.1.3 → 0.1.4
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 +20 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +389 -30
- package/dist/cli.js.map +1 -1
- package/dist/index.js +208 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,6 +48,26 @@ Install from npm:
|
|
|
48
48
|
npm install -g promptpilot
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
Run `promptpilot` with no arguments in an interactive terminal to open the CLI welcome screen:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
PromptPilot v0.1.x
|
|
55
|
+
┌──────────────────────────────────────────────────────────────────────────────┐
|
|
56
|
+
│ Welcome back │
|
|
57
|
+
│ │
|
|
58
|
+
│ .-''''-. Launchpad │
|
|
59
|
+
│ .' .--. '. Run promptpilot optimize "..." │
|
|
60
|
+
│ / / oo \ \ Pipe directly into Claude with | claude│
|
|
61
|
+
│ | \_==_/ | │
|
|
62
|
+
│ \ \_/ \_/ / Custom local model │
|
|
63
|
+
│ '._/|__|\_.' Use --model promptpilot-compressor │
|
|
64
|
+
│ │
|
|
65
|
+
│ /Users/you/project Commands │
|
|
66
|
+
│ optimize optimize and route prompts │
|
|
67
|
+
│ --help show the full CLI reference │
|
|
68
|
+
└──────────────────────────────────────────────────────────────────────────────┘
|
|
69
|
+
```
|
|
70
|
+
|
|
51
71
|
Install one or two small Ollama models so the local router has options:
|
|
52
72
|
|
|
53
73
|
```bash
|
package/dist/cli.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { createOptimizer } from './index.js';
|
|
|
3
3
|
|
|
4
4
|
type CliWriter = {
|
|
5
5
|
write(message: string): void;
|
|
6
|
+
isTTY?: boolean;
|
|
7
|
+
columns?: number;
|
|
6
8
|
};
|
|
7
9
|
interface CliIO {
|
|
8
10
|
stdout: CliWriter;
|
|
@@ -12,6 +14,13 @@ interface CliIO {
|
|
|
12
14
|
interface CliDependencies {
|
|
13
15
|
createOptimizer: typeof createOptimizer;
|
|
14
16
|
readStdin: (stdin?: NodeJS.ReadStream) => Promise<string>;
|
|
17
|
+
getCliInfo?: (stdout: CliWriter) => {
|
|
18
|
+
cwd: string;
|
|
19
|
+
version: string;
|
|
20
|
+
color: boolean;
|
|
21
|
+
columns?: number;
|
|
22
|
+
user?: string;
|
|
23
|
+
};
|
|
15
24
|
}
|
|
16
25
|
declare function runCli(argv: string[], io?: CliIO, dependencies?: CliDependencies): Promise<number>;
|
|
17
26
|
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { realpathSync } from "fs";
|
|
4
|
+
import { readFileSync, realpathSync } from "fs";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
|
|
7
7
|
// src/errors.ts
|
|
@@ -360,13 +360,13 @@ var modeGuidance = {
|
|
|
360
360
|
clarity: "Improve clarity, remove ambiguity, and keep the request easy for a downstream model to follow.",
|
|
361
361
|
concise: "Minimize token count while preserving user intent, constraints, and expected output.",
|
|
362
362
|
detailed: "Make the request explicit and complete, including structure and success criteria.",
|
|
363
|
-
structured: "Organize the request into
|
|
363
|
+
structured: "Organize the request into sections only when that improves clarity or token efficiency.",
|
|
364
364
|
persuasive: "Refine wording so the request is compelling and likely to elicit a thoughtful response.",
|
|
365
365
|
compress: "Aggressively compress redundant wording while preserving the meaning and critical constraints.",
|
|
366
366
|
claude_cli: "Optimize specifically for Claude CLI: compact sections, direct instructions, and minimal boilerplate."
|
|
367
367
|
};
|
|
368
368
|
var presetGuidance = {
|
|
369
|
-
code: "Favor precise technical requirements, edge cases,
|
|
369
|
+
code: "Favor precise technical requirements, edge cases, expected output format, and a compact inspect-plan-act-test-reflect loop for code tasks.",
|
|
370
370
|
email: "Preserve the sender's goal, tone, and audience; aim for a realistic and usable writing request.",
|
|
371
371
|
essay: "Preserve thesis, structure, and voice guidance while making the prompt clearer.",
|
|
372
372
|
support: "Favor concise issue context, user impact, and desired resolution details.",
|
|
@@ -384,6 +384,10 @@ function getOptimizationSystemPrompt(mode, preset) {
|
|
|
384
384
|
"- Preserve critical constraints and task goals.",
|
|
385
385
|
"- Improve clarity, structure, and downstream usefulness.",
|
|
386
386
|
"- Keep the result compact when the mode requests compression.",
|
|
387
|
+
"- Do not force sections when direct phrasing is shorter and equally clear.",
|
|
388
|
+
"- Remove redundancy aggressively when the source prompt repeats the same goal multiple ways.",
|
|
389
|
+
"- For code tasks, prefer a terse agent brief over narrative prose.",
|
|
390
|
+
"- For code tasks, structure the prompt around a Karpathy-style loop: inspect, plan, act, test, reflect, repeat.",
|
|
387
391
|
`Mode guidance: ${modeGuidance[mode]}`,
|
|
388
392
|
preset ? `Preset guidance: ${presetGuidance[preset]}` : "Preset guidance: none"
|
|
389
393
|
].join("\n");
|
|
@@ -712,7 +716,11 @@ function tokenize(value) {
|
|
|
712
716
|
);
|
|
713
717
|
}
|
|
714
718
|
function extractConstraints(value) {
|
|
715
|
-
return
|
|
719
|
+
return Array.from(
|
|
720
|
+
new Set(
|
|
721
|
+
value.split(/\n+/).flatMap((line) => line.split(/(?<=[.!?])\s+/)).map((line) => line.trim().replace(/^[-*]\s*/, "")).filter((line) => line.length > 0 && line.length <= 180).filter((line) => /(must|should|avoid|do not|don't|never|exactly|at most|under|limit|max|preserve|keep)/i.test(line))
|
|
722
|
+
)
|
|
723
|
+
).slice(0, 8);
|
|
716
724
|
}
|
|
717
725
|
function extractEntities(value) {
|
|
718
726
|
return Array.from(
|
|
@@ -907,6 +915,7 @@ var PromptOptimizer = class {
|
|
|
907
915
|
);
|
|
908
916
|
let provider = input.bypassOptimization ? "heuristic" : this.config.provider ?? DEFAULT_PROVIDER;
|
|
909
917
|
let model = provider === "ollama" ? this.config.ollamaModel ?? "auto" : "heuristic";
|
|
918
|
+
let usedPreprocessedFallback = false;
|
|
910
919
|
let optimizedPrompt = originalPrompt;
|
|
911
920
|
let providerWarnings = [];
|
|
912
921
|
let providerChanges = [];
|
|
@@ -941,6 +950,11 @@ var PromptOptimizer = class {
|
|
|
941
950
|
optimizedPrompt = ollamaResult.optimizedPrompt;
|
|
942
951
|
providerWarnings = ollamaResult.warnings;
|
|
943
952
|
providerChanges = ollamaResult.changes;
|
|
953
|
+
if (ollamaResult.source === "preprocessed") {
|
|
954
|
+
provider = "heuristic";
|
|
955
|
+
model = "cheap-preprocess";
|
|
956
|
+
usedPreprocessedFallback = true;
|
|
957
|
+
}
|
|
944
958
|
} else if (provider === "ollama") {
|
|
945
959
|
provider = "heuristic";
|
|
946
960
|
model = "heuristic";
|
|
@@ -949,7 +963,7 @@ var PromptOptimizer = class {
|
|
|
949
963
|
];
|
|
950
964
|
}
|
|
951
965
|
}
|
|
952
|
-
if (provider === "heuristic") {
|
|
966
|
+
if (provider === "heuristic" && !usedPreprocessedFallback) {
|
|
953
967
|
const fallback = this.heuristicOptimize({
|
|
954
968
|
input: {
|
|
955
969
|
...input,
|
|
@@ -1081,29 +1095,88 @@ var PromptOptimizer = class {
|
|
|
1081
1095
|
});
|
|
1082
1096
|
}
|
|
1083
1097
|
async tryOllamaOptimization(options) {
|
|
1098
|
+
const preprocessedPrompt = cheapCompress(options.input.prompt);
|
|
1099
|
+
const preprocessedTokenCount = this.estimator.estimateText(preprocessedPrompt);
|
|
1100
|
+
const ultraMode = preprocessedTokenCount > 500;
|
|
1084
1101
|
try {
|
|
1085
1102
|
if (!await this.client.isAvailable()) {
|
|
1086
|
-
return
|
|
1103
|
+
return {
|
|
1104
|
+
optimizedPrompt: preprocessedPrompt,
|
|
1105
|
+
changes: ["Applied cheap local preprocessing because Ollama was unavailable."],
|
|
1106
|
+
warnings: ["Ollama was unavailable, so PromptPilot kept the cheap preprocessed prompt."],
|
|
1107
|
+
source: "preprocessed"
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
const systemPrompt = ultraMode ? `${getOptimizationSystemPrompt(options.input.mode, options.input.preset)}
|
|
1111
|
+
Mode: Ultra compression. Minimize tokens aggressively.` : getOptimizationSystemPrompt(options.input.mode, options.input.preset);
|
|
1112
|
+
const optimizationPrompt = buildOptimizationPrompt(
|
|
1113
|
+
{
|
|
1114
|
+
...options.input,
|
|
1115
|
+
prompt: preprocessedPrompt
|
|
1116
|
+
},
|
|
1117
|
+
options.relevantContext,
|
|
1118
|
+
options.extractedConstraints
|
|
1119
|
+
);
|
|
1120
|
+
const timeoutMs = options.input.timeoutMs ?? this.config.timeoutMs;
|
|
1121
|
+
let optimizedPrompt = "";
|
|
1122
|
+
let responseChanges = [];
|
|
1123
|
+
let responseWarnings = [];
|
|
1124
|
+
try {
|
|
1125
|
+
const response = await this.client.generateJson({
|
|
1126
|
+
systemPrompt,
|
|
1127
|
+
prompt: optimizationPrompt,
|
|
1128
|
+
timeoutMs,
|
|
1129
|
+
model: options.model,
|
|
1130
|
+
temperature: this.config.temperature,
|
|
1131
|
+
format: "json"
|
|
1132
|
+
});
|
|
1133
|
+
optimizedPrompt = normalizeWhitespace(response.optimizedPrompt ?? "");
|
|
1134
|
+
responseChanges = response.changes ?? [];
|
|
1135
|
+
responseWarnings = response.warnings ?? [];
|
|
1136
|
+
} catch {
|
|
1137
|
+
const raw = await this.client.generate({
|
|
1138
|
+
systemPrompt,
|
|
1139
|
+
prompt: optimizationPrompt,
|
|
1140
|
+
timeoutMs,
|
|
1141
|
+
model: options.model,
|
|
1142
|
+
temperature: this.config.temperature
|
|
1143
|
+
});
|
|
1144
|
+
optimizedPrompt = sanitizeTextOptimizationOutput(raw);
|
|
1145
|
+
responseChanges = [`Applied text-only Ollama optimization with ${options.model}.`];
|
|
1087
1146
|
}
|
|
1088
|
-
const response = await this.client.generateJson({
|
|
1089
|
-
systemPrompt: getOptimizationSystemPrompt(options.input.mode, options.input.preset),
|
|
1090
|
-
prompt: buildOptimizationPrompt(options.input, options.relevantContext, options.extractedConstraints),
|
|
1091
|
-
timeoutMs: options.input.timeoutMs ?? this.config.timeoutMs,
|
|
1092
|
-
model: options.model,
|
|
1093
|
-
temperature: this.config.temperature,
|
|
1094
|
-
format: "json"
|
|
1095
|
-
});
|
|
1096
|
-
const optimizedPrompt = normalizeWhitespace(response.optimizedPrompt ?? "");
|
|
1097
1147
|
if (!optimizedPrompt) {
|
|
1098
|
-
return
|
|
1148
|
+
return {
|
|
1149
|
+
optimizedPrompt: preprocessedPrompt,
|
|
1150
|
+
changes: ["Applied cheap local preprocessing because the model returned an empty optimization."],
|
|
1151
|
+
warnings: ["The local optimizer returned an empty result, so PromptPilot kept the preprocessed prompt."],
|
|
1152
|
+
source: "preprocessed"
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
const optimizedTokenCount = this.estimator.estimateText(optimizedPrompt);
|
|
1156
|
+
if (isCompressionSensitiveMode(options.input.mode) && optimizedTokenCount >= preprocessedTokenCount) {
|
|
1157
|
+
return {
|
|
1158
|
+
optimizedPrompt: preprocessedPrompt,
|
|
1159
|
+
changes: [
|
|
1160
|
+
...responseChanges,
|
|
1161
|
+
"Kept the cheap preprocessed prompt because the model output was not smaller."
|
|
1162
|
+
],
|
|
1163
|
+
warnings: responseWarnings,
|
|
1164
|
+
source: "preprocessed"
|
|
1165
|
+
};
|
|
1099
1166
|
}
|
|
1100
1167
|
return {
|
|
1101
1168
|
optimizedPrompt,
|
|
1102
|
-
changes:
|
|
1103
|
-
warnings:
|
|
1169
|
+
changes: responseChanges.length > 0 ? responseChanges : [`Applied Ollama optimization with ${options.model}.`],
|
|
1170
|
+
warnings: responseWarnings,
|
|
1171
|
+
source: "ollama"
|
|
1104
1172
|
};
|
|
1105
1173
|
} catch {
|
|
1106
|
-
return
|
|
1174
|
+
return {
|
|
1175
|
+
optimizedPrompt: preprocessedPrompt,
|
|
1176
|
+
changes: ["Applied cheap local preprocessing because Ollama optimization failed."],
|
|
1177
|
+
warnings: ["Ollama optimization failed, so PromptPilot kept the preprocessed prompt."],
|
|
1178
|
+
source: "preprocessed"
|
|
1179
|
+
};
|
|
1107
1180
|
}
|
|
1108
1181
|
}
|
|
1109
1182
|
async resolveOllamaModel(options) {
|
|
@@ -1406,16 +1479,14 @@ var PromptOptimizer = class {
|
|
|
1406
1479
|
}
|
|
1407
1480
|
}
|
|
1408
1481
|
heuristicOptimize(options) {
|
|
1409
|
-
const
|
|
1410
|
-
|
|
1411
|
-
options.input.task ? `Task type: ${options.input.task}` : "",
|
|
1412
|
-
options.input.tone ? `Tone: ${options.input.tone}` : "",
|
|
1413
|
-
options.input.outputFormat ? `Output format: ${options.input.outputFormat}` : "",
|
|
1414
|
-
options.input.maxLength ? `Maximum length: ${options.input.maxLength}` : "",
|
|
1415
|
-
options.constraints.length ? `Critical constraints: ${options.constraints.join("; ")}` : ""
|
|
1416
|
-
].filter(Boolean);
|
|
1482
|
+
const isCodeRequest = isCodeFirstRequest(options.input);
|
|
1483
|
+
const lines = isCodeRequest ? buildCodeFirstHeuristicPrompt(options.input, options.constraints) : buildGeneralHeuristicPrompt(options.input, options.constraints);
|
|
1417
1484
|
const optimizedPrompt = lines.join("\n");
|
|
1418
|
-
const changes =
|
|
1485
|
+
const changes = isCodeRequest ? [
|
|
1486
|
+
"Compressed the prompt into a code-agent brief.",
|
|
1487
|
+
"Removed redundant narrative phrasing.",
|
|
1488
|
+
"Applied a Karpathy-style inspect-plan-act-test-reflect loop."
|
|
1489
|
+
] : ["Normalized prompt structure for downstream model consumption."];
|
|
1419
1490
|
if (options.input.mode === "compress" || options.input.mode === "concise") {
|
|
1420
1491
|
changes.push("Applied concise formatting to reduce token usage.");
|
|
1421
1492
|
}
|
|
@@ -1496,6 +1567,14 @@ ${contextBlock}`);
|
|
|
1496
1567
|
if (constraints.length > 0) {
|
|
1497
1568
|
sections.push(`Constraints:
|
|
1498
1569
|
- ${constraints.join("\n- ")}`);
|
|
1570
|
+
}
|
|
1571
|
+
if (isCodeFirstRequest(input.input)) {
|
|
1572
|
+
sections.push(`Execution loop:
|
|
1573
|
+
- Inspect the relevant files and current behavior.
|
|
1574
|
+
- Plan the smallest safe next step.
|
|
1575
|
+
- Act with minimal, reversible changes.
|
|
1576
|
+
- Test or validate the result.
|
|
1577
|
+
- Reflect on gaps or risks, then repeat.`);
|
|
1499
1578
|
}
|
|
1500
1579
|
const desiredOutput = [
|
|
1501
1580
|
input.routingDecision.selectedTarget ? `Selected target: ${formatTargetLabel(input.routingDecision.selectedTarget)}` : input.input.targetModel ? `Target model: ${input.input.targetModel}` : "Target model: claude",
|
|
@@ -1595,16 +1674,266 @@ function describeDownstreamTarget(target) {
|
|
|
1595
1674
|
function formatTargetLabel(target) {
|
|
1596
1675
|
return target.label ?? `${target.provider}:${target.model}`;
|
|
1597
1676
|
}
|
|
1677
|
+
function isCompressionSensitiveMode(mode) {
|
|
1678
|
+
return mode === "compress" || mode === "concise" || mode === "claude_cli";
|
|
1679
|
+
}
|
|
1680
|
+
function cheapCompress(text) {
|
|
1681
|
+
return normalizeWhitespace(text).replace(/\b(?:please|kindly|just)\b/gi, "").replace(/\bI\s+(?:want|need|would\s+like\s+to)\b/gi, "").replace(/\s+([,.;:!?])/g, "$1").replace(/\s{2,}/g, " ").trim();
|
|
1682
|
+
}
|
|
1683
|
+
function sanitizeTextOptimizationOutput(raw) {
|
|
1684
|
+
const normalized = normalizeWhitespace(raw);
|
|
1685
|
+
if (!normalized) {
|
|
1686
|
+
return "";
|
|
1687
|
+
}
|
|
1688
|
+
if (!containsReasoningLeak(normalized)) {
|
|
1689
|
+
return stripWrappingQuotes(normalized);
|
|
1690
|
+
}
|
|
1691
|
+
const candidates = raw.split(/\n{2,}/).map((chunk) => stripWrappingQuotes(normalizeWhitespace(chunk))).filter(Boolean).filter((chunk) => !containsReasoningLeak(chunk)).filter((chunk) => !/^(role|task|guidelines|thinking|thinking process|attempt|critique|final decision|analysis)\b/i.test(chunk)).filter((chunk) => !/^[-*]\s/.test(chunk)).filter((chunk) => !/^\d+\.\s/.test(chunk));
|
|
1692
|
+
return candidates.at(-1) ?? stripWrappingQuotes(normalized);
|
|
1693
|
+
}
|
|
1694
|
+
function containsReasoningLeak(text) {
|
|
1695
|
+
return /(thinking process|analyze the request|drafting the optimized prompt|critique \d|attempt \d|final decision)/i.test(text);
|
|
1696
|
+
}
|
|
1697
|
+
function stripWrappingQuotes(text) {
|
|
1698
|
+
return text.replace(/^["'`]+|["'`]+$/g, "").trim();
|
|
1699
|
+
}
|
|
1700
|
+
function isCodeFirstRequest(input) {
|
|
1701
|
+
if (input.task === "code" || input.preset === "code") {
|
|
1702
|
+
return true;
|
|
1703
|
+
}
|
|
1704
|
+
if ((input.targetHints ?? []).some((hint) => ["coding", "agentic", "refactor", "debugging", "tool_use", "architecture"].includes(hint))) {
|
|
1705
|
+
return true;
|
|
1706
|
+
}
|
|
1707
|
+
return /\b(code|coding|repo|repository|refactor|patch|debug|bug|ci|test|typescript|javascript|agent|tool)\b/i.test(
|
|
1708
|
+
input.prompt
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
function buildGeneralHeuristicPrompt(input, constraints) {
|
|
1712
|
+
return [
|
|
1713
|
+
`Request: ${summarizePrompt(input.prompt, 320)}`,
|
|
1714
|
+
input.task ? `Task type: ${input.task}` : "",
|
|
1715
|
+
input.tone ? `Tone: ${input.tone}` : "",
|
|
1716
|
+
input.outputFormat ? `Output format: ${input.outputFormat}` : "",
|
|
1717
|
+
input.maxLength ? `Maximum length: ${input.maxLength}` : "",
|
|
1718
|
+
constraints.length ? `Critical constraints: ${constraints.join("; ")}` : ""
|
|
1719
|
+
].filter(Boolean);
|
|
1720
|
+
}
|
|
1721
|
+
function buildCodeFirstHeuristicPrompt(input, constraints) {
|
|
1722
|
+
const deliverables = inferCodeDeliverables(input.prompt);
|
|
1723
|
+
return [
|
|
1724
|
+
`Goal: ${summarizeCodeGoal(input.prompt)}`,
|
|
1725
|
+
input.tone ? `Tone: ${input.tone}` : "",
|
|
1726
|
+
deliverables.length ? `Deliverables:
|
|
1727
|
+
- ${deliverables.join("\n- ")}` : "",
|
|
1728
|
+
constraints.length ? `Constraints: ${constraints.join("; ")}` : "",
|
|
1729
|
+
"Use a Karpathy loop: inspect, plan, act, test, reflect, repeat."
|
|
1730
|
+
].filter(Boolean);
|
|
1731
|
+
}
|
|
1732
|
+
function summarizePrompt(prompt, maxLength) {
|
|
1733
|
+
const normalized = normalizeWhitespace(prompt);
|
|
1734
|
+
if (normalized.length <= maxLength) {
|
|
1735
|
+
return normalized;
|
|
1736
|
+
}
|
|
1737
|
+
return `${normalized.slice(0, maxLength - 1).trim()}\u2026`;
|
|
1738
|
+
}
|
|
1739
|
+
function summarizeCodeGoal(prompt) {
|
|
1740
|
+
const normalized = summarizePrompt(prompt, 220);
|
|
1741
|
+
const lowered = prompt.toLowerCase();
|
|
1742
|
+
if (/auth|authentication|login|token/.test(lowered)) {
|
|
1743
|
+
return "Inspect the codebase, understand the authentication flow, and produce a safe incremental refactor plan.";
|
|
1744
|
+
}
|
|
1745
|
+
if (/ci|debug|failing|failure|test/.test(lowered)) {
|
|
1746
|
+
return "Inspect the codebase and failing signals, identify root causes, and produce a practical debugging plan.";
|
|
1747
|
+
}
|
|
1748
|
+
if (/refactor/.test(lowered)) {
|
|
1749
|
+
return "Inspect the codebase and produce a phased refactor plan with minimal-risk execution steps.";
|
|
1750
|
+
}
|
|
1751
|
+
return normalized;
|
|
1752
|
+
}
|
|
1753
|
+
function inferCodeDeliverables(prompt) {
|
|
1754
|
+
const lowered = prompt.toLowerCase();
|
|
1755
|
+
const deliverables = [];
|
|
1756
|
+
if (/inspect|codebase|repo|repository/.test(lowered)) {
|
|
1757
|
+
deliverables.push("Summarize the relevant modules, ownership boundaries, and current behavior.");
|
|
1758
|
+
}
|
|
1759
|
+
if (/shared abstraction|shared abstractions|duplicate|duplicated/.test(lowered)) {
|
|
1760
|
+
deliverables.push("Identify duplicated logic and the best shared abstractions to extract.");
|
|
1761
|
+
}
|
|
1762
|
+
if (/incremental|phase|phased|rollout|step/.test(lowered)) {
|
|
1763
|
+
deliverables.push("Propose an incremental plan with small, reversible steps.");
|
|
1764
|
+
}
|
|
1765
|
+
if (/risk|migration|compatibility|backward/.test(lowered)) {
|
|
1766
|
+
deliverables.push("Call out migration risks, compatibility concerns, and rollback points.");
|
|
1767
|
+
}
|
|
1768
|
+
if (/test|tests/.test(lowered)) {
|
|
1769
|
+
deliverables.push("List the tests or validation needed before and after each phase.");
|
|
1770
|
+
}
|
|
1771
|
+
if (/avoid hand-wavy|practical|concrete/.test(lowered)) {
|
|
1772
|
+
deliverables.push("Keep the recommendations concrete, implementation-oriented, and free of vague architecture advice.");
|
|
1773
|
+
}
|
|
1774
|
+
if (deliverables.length === 0) {
|
|
1775
|
+
deliverables.push("Produce a compact, execution-ready plan for the coding task.");
|
|
1776
|
+
}
|
|
1777
|
+
return deliverables.slice(0, 6);
|
|
1778
|
+
}
|
|
1598
1779
|
|
|
1599
1780
|
// src/index.ts
|
|
1600
1781
|
function createOptimizer(config = {}) {
|
|
1601
1782
|
return new PromptOptimizer(config);
|
|
1602
1783
|
}
|
|
1603
1784
|
|
|
1785
|
+
// src/cliWelcome.ts
|
|
1786
|
+
import { basename } from "path";
|
|
1787
|
+
var MIN_WIDE_COLUMNS = 84;
|
|
1788
|
+
function renderWelcomeScreen(options) {
|
|
1789
|
+
const columns = Math.max(60, options.columns ?? 100);
|
|
1790
|
+
const color = options.color ?? false;
|
|
1791
|
+
const user = options.user?.trim() || "pilot";
|
|
1792
|
+
return columns >= MIN_WIDE_COLUMNS ? renderWideWelcome({ ...options, columns, color, user }) : renderCompactWelcome({ ...options, columns, color, user });
|
|
1793
|
+
}
|
|
1794
|
+
function renderWideWelcome(options) {
|
|
1795
|
+
const width = clamp(options.columns - 5, 82, 109);
|
|
1796
|
+
const innerWidth = width - 2;
|
|
1797
|
+
const leftWidth = 28;
|
|
1798
|
+
const rightWidth = innerWidth - leftWidth - 5;
|
|
1799
|
+
const leftLines = [
|
|
1800
|
+
style(`Welcome back, ${options.user}`, "bold", options.color),
|
|
1801
|
+
"",
|
|
1802
|
+
...paintSprite(options.color),
|
|
1803
|
+
"",
|
|
1804
|
+
style(`${options.user} \u2022 ${basename(options.cwd)}`, "dim", options.color),
|
|
1805
|
+
style(options.cwd, "dim", options.color)
|
|
1806
|
+
];
|
|
1807
|
+
const rightLines = [
|
|
1808
|
+
style("Launchpad", "accent", options.color),
|
|
1809
|
+
"Run " + style('promptpilot optimize "fix this CI failure" --task code --plain', "bold", options.color),
|
|
1810
|
+
"Pipe directly into Claude with " + style("| claude", "bold", options.color),
|
|
1811
|
+
"",
|
|
1812
|
+
style("Custom local model", "accent", options.color),
|
|
1813
|
+
"Use " + style("--model promptpilot-compressor", "bold", options.color) + " for text-only local compression",
|
|
1814
|
+
"",
|
|
1815
|
+
style("Commands", "accent", options.color),
|
|
1816
|
+
"optimize optimize, compress, and route prompts",
|
|
1817
|
+
"--help show the full CLI reference"
|
|
1818
|
+
];
|
|
1819
|
+
const rowCount = Math.max(leftLines.length, rightLines.length);
|
|
1820
|
+
const header = `${style(" PromptPilot ", "accent", options.color)} ${style(`v${options.version}`, "dim", options.color)}`;
|
|
1821
|
+
const topRule = `${style("\u250C", "accent", options.color)}${style("\u2500".repeat(innerWidth), "accent", options.color)}${style("\u2510", "accent", options.color)}`;
|
|
1822
|
+
const bottomRule = `${style("\u2514", "accent", options.color)}${style("\u2500".repeat(innerWidth), "accent", options.color)}${style("\u2518", "accent", options.color)}`;
|
|
1823
|
+
const body = new Array(rowCount).fill(null).map((_, index) => {
|
|
1824
|
+
const left = padVisible(leftLines[index] ?? "", leftWidth);
|
|
1825
|
+
const right = padVisible(rightLines[index] ?? "", rightWidth);
|
|
1826
|
+
return `${style("\u2502", "accent", options.color)} ${left} ${style("\u2502", "accent", options.color)} ${right} ${style("\u2502", "accent", options.color)}`;
|
|
1827
|
+
});
|
|
1828
|
+
const footer = [
|
|
1829
|
+
"",
|
|
1830
|
+
style("Ready when you are.", "dim", options.color),
|
|
1831
|
+
`Run ${style("promptpilot --help", "bold", options.color)} for the full option list.`
|
|
1832
|
+
];
|
|
1833
|
+
return [header, topRule, ...body, bottomRule, ...footer].join("\n");
|
|
1834
|
+
}
|
|
1835
|
+
function renderCompactWelcome(options) {
|
|
1836
|
+
const width = clamp(options.columns - 2, 58, 78);
|
|
1837
|
+
const innerWidth = width - 2;
|
|
1838
|
+
const lines = [
|
|
1839
|
+
`${style("PromptPilot", "accent", options.color)} ${style(`v${options.version}`, "dim", options.color)}`,
|
|
1840
|
+
style(`Welcome back, ${options.user}.`, "bold", options.color),
|
|
1841
|
+
...paintSprite(options.color),
|
|
1842
|
+
style(options.cwd, "dim", options.color),
|
|
1843
|
+
"",
|
|
1844
|
+
style("Quick start", "accent", options.color),
|
|
1845
|
+
'promptpilot optimize "fix this CI failure" --task code --plain',
|
|
1846
|
+
'promptpilot optimize "..." --model promptpilot-compressor',
|
|
1847
|
+
"",
|
|
1848
|
+
style("Help", "accent", options.color),
|
|
1849
|
+
"promptpilot --help"
|
|
1850
|
+
];
|
|
1851
|
+
return [
|
|
1852
|
+
`${style("\u250C", "accent", options.color)}${style("\u2500".repeat(innerWidth), "accent", options.color)}${style("\u2510", "accent", options.color)}`,
|
|
1853
|
+
...lines.map((line) => `${style("\u2502", "accent", options.color)} ${padVisible(line, innerWidth - 1)}${style("\u2502", "accent", options.color)}`),
|
|
1854
|
+
`${style("\u2514", "accent", options.color)}${style("\u2500".repeat(innerWidth), "accent", options.color)}${style("\u2518", "accent", options.color)}`
|
|
1855
|
+
].join("\n");
|
|
1856
|
+
}
|
|
1857
|
+
function paintSprite(color) {
|
|
1858
|
+
const ink = color ? "\x1B[38;5;215m" : "";
|
|
1859
|
+
const reset = color ? "\x1B[0m" : "";
|
|
1860
|
+
return [
|
|
1861
|
+
`${ink} .-''''-.${reset}`,
|
|
1862
|
+
`${ink} .' .--. '.${reset}`,
|
|
1863
|
+
`${ink} / / oo \\ \\${reset}`,
|
|
1864
|
+
`${ink} | \\_==_/ |${reset}`,
|
|
1865
|
+
`${ink} | .-.__.-. |${reset}`,
|
|
1866
|
+
`${ink} \\ \\_/ \\_/ /${reset}`,
|
|
1867
|
+
`${ink} '._/|__|\\_.'${reset}`,
|
|
1868
|
+
`${ink} /_/ \\_\\${reset}`
|
|
1869
|
+
];
|
|
1870
|
+
}
|
|
1871
|
+
function style(text, tone, color) {
|
|
1872
|
+
if (!color) {
|
|
1873
|
+
return text;
|
|
1874
|
+
}
|
|
1875
|
+
switch (tone) {
|
|
1876
|
+
case "accent":
|
|
1877
|
+
return `\x1B[38;5;215m${text}\x1B[0m`;
|
|
1878
|
+
case "bold":
|
|
1879
|
+
return `\x1B[1m${text}\x1B[0m`;
|
|
1880
|
+
case "dim":
|
|
1881
|
+
return `\x1B[38;5;245m${text}\x1B[0m`;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
function padVisible(text, targetWidth) {
|
|
1885
|
+
const truncated = truncateVisible(text, targetWidth);
|
|
1886
|
+
const padding = Math.max(0, targetWidth - visibleWidth(truncated));
|
|
1887
|
+
return `${truncated}${" ".repeat(padding)}`;
|
|
1888
|
+
}
|
|
1889
|
+
function truncateVisible(text, targetWidth) {
|
|
1890
|
+
if (visibleWidth(text) <= targetWidth) {
|
|
1891
|
+
return text;
|
|
1892
|
+
}
|
|
1893
|
+
let visible = 0;
|
|
1894
|
+
let result = "";
|
|
1895
|
+
let inEscape = false;
|
|
1896
|
+
for (const char of text) {
|
|
1897
|
+
result += char;
|
|
1898
|
+
if (char === "\x1B") {
|
|
1899
|
+
inEscape = true;
|
|
1900
|
+
continue;
|
|
1901
|
+
}
|
|
1902
|
+
if (inEscape) {
|
|
1903
|
+
if (char === "m") {
|
|
1904
|
+
inEscape = false;
|
|
1905
|
+
}
|
|
1906
|
+
continue;
|
|
1907
|
+
}
|
|
1908
|
+
visible += 1;
|
|
1909
|
+
if (visible >= Math.max(0, targetWidth - 1)) {
|
|
1910
|
+
break;
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
return `${result}\u2026`;
|
|
1914
|
+
}
|
|
1915
|
+
function visibleWidth(text) {
|
|
1916
|
+
return text.replace(/\u001b\[[0-9;]*m/g, "").length;
|
|
1917
|
+
}
|
|
1918
|
+
function clamp(value, min, max) {
|
|
1919
|
+
return Math.max(min, Math.min(max, value));
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1604
1922
|
// src/cli.ts
|
|
1605
|
-
async function runCli(argv, io = { stdout: process.stdout, stderr: process.stderr, stdin: process.stdin }, dependencies = { createOptimizer, readStdin }) {
|
|
1923
|
+
async function runCli(argv, io = { stdout: process.stdout, stderr: process.stderr, stdin: process.stdin }, dependencies = { createOptimizer, readStdin, getCliInfo }) {
|
|
1606
1924
|
const [command, ...rest] = argv;
|
|
1607
|
-
if (!command
|
|
1925
|
+
if (!command) {
|
|
1926
|
+
const info = (dependencies.getCliInfo ?? getCliInfo)(io.stdout);
|
|
1927
|
+
if (io.stdout.isTTY) {
|
|
1928
|
+
io.stdout.write(`${renderWelcomeScreen(info)}
|
|
1929
|
+
`);
|
|
1930
|
+
return 0;
|
|
1931
|
+
}
|
|
1932
|
+
io.stdout.write(`${getHelpText()}
|
|
1933
|
+
`);
|
|
1934
|
+
return 0;
|
|
1935
|
+
}
|
|
1936
|
+
if (command === "--help" || command === "-h" || command === "help") {
|
|
1608
1937
|
io.stdout.write(`${getHelpText()}
|
|
1609
1938
|
`);
|
|
1610
1939
|
return 0;
|
|
@@ -1905,6 +2234,36 @@ async function readStdin(stdin = process.stdin) {
|
|
|
1905
2234
|
stdin.on("error", reject);
|
|
1906
2235
|
});
|
|
1907
2236
|
}
|
|
2237
|
+
function getCliInfo(stdout) {
|
|
2238
|
+
return {
|
|
2239
|
+
cwd: process.cwd(),
|
|
2240
|
+
version: readPackageVersion(),
|
|
2241
|
+
color: shouldUseColor(stdout),
|
|
2242
|
+
columns: stdout.columns,
|
|
2243
|
+
user: process.env.USER ?? process.env.USERNAME
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
function shouldUseColor(stdout) {
|
|
2247
|
+
if (!stdout.isTTY) {
|
|
2248
|
+
return false;
|
|
2249
|
+
}
|
|
2250
|
+
if (process.env.NO_COLOR) {
|
|
2251
|
+
return false;
|
|
2252
|
+
}
|
|
2253
|
+
if (process.env.TERM === "dumb") {
|
|
2254
|
+
return false;
|
|
2255
|
+
}
|
|
2256
|
+
return true;
|
|
2257
|
+
}
|
|
2258
|
+
function readPackageVersion() {
|
|
2259
|
+
try {
|
|
2260
|
+
const packageJson = readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
2261
|
+
const parsed = JSON.parse(packageJson);
|
|
2262
|
+
return parsed.version ?? "dev";
|
|
2263
|
+
} catch {
|
|
2264
|
+
return "dev";
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
1908
2267
|
if (isMainModule()) {
|
|
1909
2268
|
runCli(process.argv.slice(2)).then(
|
|
1910
2269
|
(code) => {
|