promptpilot 0.1.4 → 0.1.6
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 +37 -0
- package/dist/cli.js +197 -49
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +73 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,6 +75,43 @@ ollama pull qwen2.5:3b
|
|
|
75
75
|
ollama pull phi3:mini
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
## Custom local compressor model
|
|
79
|
+
|
|
80
|
+
PromptPilot ships a `Modelfile` that defines `promptpilot-compressor`, a text-only compression model built on top of `qwen2.5:3b`. It is tuned to output only the compressed prompt with no reasoning, analysis, or commentary.
|
|
81
|
+
|
|
82
|
+
Build and verify it:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
ollama pull qwen2.5:3b
|
|
86
|
+
ollama create promptpilot-compressor -f ./Modelfile
|
|
87
|
+
ollama run promptpilot-compressor "explain recursion simply"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Use it via the CLI after installing from npm:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Plain output — pipe directly into Claude
|
|
94
|
+
promptpilot optimize "help me refactor this auth middleware" \
|
|
95
|
+
--model promptpilot-compressor \
|
|
96
|
+
--preset code \
|
|
97
|
+
--plain
|
|
98
|
+
|
|
99
|
+
# JSON output with debug info
|
|
100
|
+
promptpilot optimize "help me refactor this auth middleware" \
|
|
101
|
+
--model promptpilot-compressor \
|
|
102
|
+
--preset code \
|
|
103
|
+
--json --debug
|
|
104
|
+
|
|
105
|
+
# With session memory, piped into Claude
|
|
106
|
+
promptpilot optimize "continue the refactor" \
|
|
107
|
+
--model promptpilot-compressor \
|
|
108
|
+
--session repo-refactor \
|
|
109
|
+
--save-context \
|
|
110
|
+
--plain | claude
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`promptpilot-compressor` outputs plain text rather than JSON. PromptPilot detects this automatically and falls back to text-only mode, stripping any reasoning leakage before using the output. Explicit `--model` always takes priority over automatic local model selection.
|
|
114
|
+
|
|
78
115
|
## Core behavior
|
|
79
116
|
|
|
80
117
|
PromptPilot has two distinct routing layers.
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +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
7
|
|
|
7
8
|
// src/errors.ts
|
|
8
9
|
var InvalidPromptError = class extends Error {
|
|
@@ -353,6 +354,17 @@ var OllamaClient = class {
|
|
|
353
354
|
}
|
|
354
355
|
throw new OllamaUnavailableError("Ollama returned JSON that could not be parsed.");
|
|
355
356
|
}
|
|
357
|
+
async generateJsonWithTextFallback(options, textFallbackHandler) {
|
|
358
|
+
try {
|
|
359
|
+
return await this.generateJson(options);
|
|
360
|
+
} catch {
|
|
361
|
+
const raw = await this.generate({
|
|
362
|
+
...options,
|
|
363
|
+
format: void 0
|
|
364
|
+
});
|
|
365
|
+
return textFallbackHandler(raw);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
356
368
|
};
|
|
357
369
|
|
|
358
370
|
// src/core/systemPrompt.ts
|
|
@@ -1046,6 +1058,11 @@ var PromptOptimizer = class {
|
|
|
1046
1058
|
contextSummary: relevantContext.summary
|
|
1047
1059
|
});
|
|
1048
1060
|
}
|
|
1061
|
+
const originalPromptTokens = this.estimator.estimateText(originalPrompt);
|
|
1062
|
+
const promptCompressionSavings = Math.max(0, originalPromptTokens - estimatedTokensAfter.prompt);
|
|
1063
|
+
const contextCompressionSavings = Math.max(0, estimatedTokensBefore.context - estimatedTokensAfter.context);
|
|
1064
|
+
const wrapperOverhead = Math.max(0, estimatedTokensAfter.total - (estimatedTokensAfter.prompt + estimatedTokensAfter.context));
|
|
1065
|
+
const tokenSavings = promptCompressionSavings + contextCompressionSavings;
|
|
1049
1066
|
return {
|
|
1050
1067
|
originalPrompt,
|
|
1051
1068
|
optimizedPrompt,
|
|
@@ -1054,7 +1071,10 @@ var PromptOptimizer = class {
|
|
|
1054
1071
|
contextSummary: relevantContext.summary,
|
|
1055
1072
|
estimatedTokensBefore,
|
|
1056
1073
|
estimatedTokensAfter,
|
|
1057
|
-
tokenSavings
|
|
1074
|
+
tokenSavings,
|
|
1075
|
+
promptCompressionSavings,
|
|
1076
|
+
contextCompressionSavings,
|
|
1077
|
+
wrapperOverhead,
|
|
1058
1078
|
mode,
|
|
1059
1079
|
provider,
|
|
1060
1080
|
model,
|
|
@@ -1121,29 +1141,52 @@ Mode: Ultra compression. Minimize tokens aggressively.` : getOptimizationSystemP
|
|
|
1121
1141
|
let optimizedPrompt = "";
|
|
1122
1142
|
let responseChanges = [];
|
|
1123
1143
|
let responseWarnings = [];
|
|
1124
|
-
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1144
|
+
const generateWithFallback = this.client.generateJsonWithTextFallback ? async () => {
|
|
1145
|
+
const response2 = await this.client.generateJsonWithTextFallback(
|
|
1146
|
+
{
|
|
1147
|
+
systemPrompt,
|
|
1148
|
+
prompt: optimizationPrompt,
|
|
1149
|
+
timeoutMs,
|
|
1150
|
+
model: options.model,
|
|
1151
|
+
temperature: this.config.temperature,
|
|
1152
|
+
format: "json"
|
|
1153
|
+
},
|
|
1154
|
+
(text) => ({
|
|
1155
|
+
optimizedPrompt: sanitizeTextOptimizationOutput(text),
|
|
1156
|
+
changes: [`Applied text-only Ollama optimization with ${options.model}.`],
|
|
1157
|
+
warnings: []
|
|
1158
|
+
})
|
|
1159
|
+
);
|
|
1160
|
+
return response2;
|
|
1161
|
+
} : async () => {
|
|
1162
|
+
try {
|
|
1163
|
+
return await this.client.generateJson({
|
|
1164
|
+
systemPrompt,
|
|
1165
|
+
prompt: optimizationPrompt,
|
|
1166
|
+
timeoutMs,
|
|
1167
|
+
model: options.model,
|
|
1168
|
+
temperature: this.config.temperature,
|
|
1169
|
+
format: "json"
|
|
1170
|
+
});
|
|
1171
|
+
} catch {
|
|
1172
|
+
const raw = await this.client.generate({
|
|
1173
|
+
systemPrompt,
|
|
1174
|
+
prompt: optimizationPrompt,
|
|
1175
|
+
timeoutMs,
|
|
1176
|
+
model: options.model,
|
|
1177
|
+
temperature: this.config.temperature
|
|
1178
|
+
});
|
|
1179
|
+
return {
|
|
1180
|
+
optimizedPrompt: sanitizeTextOptimizationOutput(raw),
|
|
1181
|
+
changes: [`Applied text-only Ollama optimization with ${options.model}.`],
|
|
1182
|
+
warnings: []
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
const response = await generateWithFallback();
|
|
1187
|
+
optimizedPrompt = normalizeWhitespace(response.optimizedPrompt ?? "");
|
|
1188
|
+
responseChanges = response.changes ?? [];
|
|
1189
|
+
responseWarnings = response.warnings ?? [];
|
|
1147
1190
|
if (!optimizedPrompt) {
|
|
1148
1191
|
return {
|
|
1149
1192
|
optimizedPrompt: preprocessedPrompt,
|
|
@@ -1685,14 +1728,16 @@ function sanitizeTextOptimizationOutput(raw) {
|
|
|
1685
1728
|
if (!normalized) {
|
|
1686
1729
|
return "";
|
|
1687
1730
|
}
|
|
1688
|
-
|
|
1689
|
-
|
|
1731
|
+
let cleaned = normalized.replace(/<think>[\s\S]*?<\/think>/gi, "").replace(/<reasoning>[\s\S]*?<\/reasoning>/gi, "").replace(/<analysis>[\s\S]*?<\/analysis>/gi, "").replace(/^(thinking|thinking process|analysis|critique|attempt|final decision|role|task|guidelines)[:=]?[\s\S]*?(?=\n\n|\n[A-Z]|$)/gim, "");
|
|
1732
|
+
if (!containsReasoningLeak(cleaned)) {
|
|
1733
|
+
return stripWrappingQuotes(cleaned);
|
|
1690
1734
|
}
|
|
1691
|
-
const candidates =
|
|
1692
|
-
|
|
1735
|
+
const candidates = cleaned.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) => chunk.length > 10);
|
|
1736
|
+
const selected = candidates.reduce((a, b) => a.length > b.length ? a : b, "");
|
|
1737
|
+
return selected || stripWrappingQuotes(normalized);
|
|
1693
1738
|
}
|
|
1694
1739
|
function containsReasoningLeak(text) {
|
|
1695
|
-
return /(thinking process|analyze the request|drafting the optimized prompt|critique \d|attempt \d|final decision)/i.test(text);
|
|
1740
|
+
return /(thinking process|analyze the request|drafting the optimized prompt|critique \d|attempt \d|final decision|^thinking:|^analysis:|<think>|<reasoning>|<analysis>)/i.test(text);
|
|
1696
1741
|
}
|
|
1697
1742
|
function stripWrappingQuotes(text) {
|
|
1698
1743
|
return text.replace(/^["'`]+|["'`]+$/g, "").trim();
|
|
@@ -1784,7 +1829,8 @@ function createOptimizer(config = {}) {
|
|
|
1784
1829
|
|
|
1785
1830
|
// src/cliWelcome.ts
|
|
1786
1831
|
import { basename } from "path";
|
|
1787
|
-
var MIN_WIDE_COLUMNS =
|
|
1832
|
+
var MIN_WIDE_COLUMNS = 76;
|
|
1833
|
+
var PANEL_RULE = "__PANEL_RULE__";
|
|
1788
1834
|
function renderWelcomeScreen(options) {
|
|
1789
1835
|
const columns = Math.max(60, options.columns ?? 100);
|
|
1790
1836
|
const color = options.color ?? false;
|
|
@@ -1792,12 +1838,13 @@ function renderWelcomeScreen(options) {
|
|
|
1792
1838
|
return columns >= MIN_WIDE_COLUMNS ? renderWideWelcome({ ...options, columns, color, user }) : renderCompactWelcome({ ...options, columns, color, user });
|
|
1793
1839
|
}
|
|
1794
1840
|
function renderWideWelcome(options) {
|
|
1795
|
-
const width = clamp(options.columns -
|
|
1841
|
+
const width = clamp(options.columns - 4, 76, 118);
|
|
1796
1842
|
const innerWidth = width - 2;
|
|
1797
|
-
const leftWidth =
|
|
1843
|
+
const leftWidth = 30;
|
|
1798
1844
|
const rightWidth = innerWidth - leftWidth - 5;
|
|
1845
|
+
const title = ` PromptPilot v${options.version} `;
|
|
1799
1846
|
const leftLines = [
|
|
1800
|
-
style(`Welcome back
|
|
1847
|
+
style(`Welcome back ${capitalize(options.user)}!`, "bold", options.color),
|
|
1801
1848
|
"",
|
|
1802
1849
|
...paintSprite(options.color),
|
|
1803
1850
|
"",
|
|
@@ -1805,11 +1852,11 @@ function renderWideWelcome(options) {
|
|
|
1805
1852
|
style(options.cwd, "dim", options.color)
|
|
1806
1853
|
];
|
|
1807
1854
|
const rightLines = [
|
|
1808
|
-
style("
|
|
1855
|
+
style("Tips for getting started", "accent", options.color),
|
|
1809
1856
|
"Run " + style('promptpilot optimize "fix this CI failure" --task code --plain', "bold", options.color),
|
|
1810
1857
|
"Pipe directly into Claude with " + style("| claude", "bold", options.color),
|
|
1811
|
-
|
|
1812
|
-
style("
|
|
1858
|
+
PANEL_RULE,
|
|
1859
|
+
style("Local setup", "accent", options.color),
|
|
1813
1860
|
"Use " + style("--model promptpilot-compressor", "bold", options.color) + " for text-only local compression",
|
|
1814
1861
|
"",
|
|
1815
1862
|
style("Commands", "accent", options.color),
|
|
@@ -1817,12 +1864,12 @@ function renderWideWelcome(options) {
|
|
|
1817
1864
|
"--help show the full CLI reference"
|
|
1818
1865
|
];
|
|
1819
1866
|
const rowCount = Math.max(leftLines.length, rightLines.length);
|
|
1820
|
-
const
|
|
1821
|
-
const
|
|
1822
|
-
const bottomRule = `${style("\u2514", "accent", options.color)}${style("\u2500".repeat(innerWidth), "accent", options.color)}${style("\u2518", "accent", options.color)}`;
|
|
1867
|
+
const topRule = renderTopRule(title, innerWidth, options.color);
|
|
1868
|
+
const bottomRule = `${style("\u2570", "accent", options.color)}${style("\u2500".repeat(innerWidth), "accent", options.color)}${style("\u256F", "accent", options.color)}`;
|
|
1823
1869
|
const body = new Array(rowCount).fill(null).map((_, index) => {
|
|
1824
1870
|
const left = padVisible(leftLines[index] ?? "", leftWidth);
|
|
1825
|
-
const
|
|
1871
|
+
const rightLine = rightLines[index] ?? "";
|
|
1872
|
+
const right = rightLine === PANEL_RULE ? style("\u2500".repeat(rightWidth), "dim", options.color) : padVisible(rightLine, rightWidth);
|
|
1826
1873
|
return `${style("\u2502", "accent", options.color)} ${left} ${style("\u2502", "accent", options.color)} ${right} ${style("\u2502", "accent", options.color)}`;
|
|
1827
1874
|
});
|
|
1828
1875
|
const footer = [
|
|
@@ -1830,18 +1877,20 @@ function renderWideWelcome(options) {
|
|
|
1830
1877
|
style("Ready when you are.", "dim", options.color),
|
|
1831
1878
|
`Run ${style("promptpilot --help", "bold", options.color)} for the full option list.`
|
|
1832
1879
|
];
|
|
1833
|
-
return [
|
|
1880
|
+
return [topRule, ...body, bottomRule, ...footer].join("\n");
|
|
1834
1881
|
}
|
|
1835
1882
|
function renderCompactWelcome(options) {
|
|
1836
|
-
const width = clamp(options.columns - 2, 58,
|
|
1883
|
+
const width = clamp(options.columns - 2, 58, 82);
|
|
1837
1884
|
const innerWidth = width - 2;
|
|
1885
|
+
const title = ` PromptPilot v${options.version} `;
|
|
1838
1886
|
const lines = [
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
...paintSprite(options.color),
|
|
1887
|
+
centerVisible(style(`Welcome back ${capitalize(options.user)}!`, "bold", options.color), innerWidth - 2),
|
|
1888
|
+
"",
|
|
1889
|
+
...paintSprite(options.color).map((line) => centerVisible(line, innerWidth - 2)),
|
|
1890
|
+
"",
|
|
1842
1891
|
style(options.cwd, "dim", options.color),
|
|
1843
1892
|
"",
|
|
1844
|
-
style("
|
|
1893
|
+
style("Tips for getting started", "accent", options.color),
|
|
1845
1894
|
'promptpilot optimize "fix this CI failure" --task code --plain',
|
|
1846
1895
|
'promptpilot optimize "..." --model promptpilot-compressor',
|
|
1847
1896
|
"",
|
|
@@ -1849,9 +1898,9 @@ function renderCompactWelcome(options) {
|
|
|
1849
1898
|
"promptpilot --help"
|
|
1850
1899
|
];
|
|
1851
1900
|
return [
|
|
1852
|
-
|
|
1853
|
-
...lines.map((line) => `${style("\u2502", "accent", options.color)} ${padVisible(line, innerWidth -
|
|
1854
|
-
`${style("\
|
|
1901
|
+
renderTopRule(title, innerWidth, options.color),
|
|
1902
|
+
...lines.map((line) => `${style("\u2502", "accent", options.color)} ${padVisible(line, innerWidth - 2)} ${style("\u2502", "accent", options.color)}`),
|
|
1903
|
+
`${style("\u2570", "accent", options.color)}${style("\u2500".repeat(innerWidth), "accent", options.color)}${style("\u256F", "accent", options.color)}`
|
|
1855
1904
|
].join("\n");
|
|
1856
1905
|
}
|
|
1857
1906
|
function paintSprite(color) {
|
|
@@ -1881,11 +1930,24 @@ function style(text, tone, color) {
|
|
|
1881
1930
|
return `\x1B[38;5;245m${text}\x1B[0m`;
|
|
1882
1931
|
}
|
|
1883
1932
|
}
|
|
1933
|
+
function renderTopRule(title, innerWidth, color) {
|
|
1934
|
+
const titleWidth = visibleWidth(title);
|
|
1935
|
+
const leftRuleWidth = Math.min(3, Math.max(0, innerWidth - titleWidth));
|
|
1936
|
+
const rightRuleWidth = Math.max(0, innerWidth - titleWidth - leftRuleWidth);
|
|
1937
|
+
return `${style("\u256D", "accent", color)}${style("\u2500".repeat(leftRuleWidth), "accent", color)}${style(title, "accent", color)}${style("\u2500".repeat(rightRuleWidth), "accent", color)}${style("\u256E", "accent", color)}`;
|
|
1938
|
+
}
|
|
1884
1939
|
function padVisible(text, targetWidth) {
|
|
1885
1940
|
const truncated = truncateVisible(text, targetWidth);
|
|
1886
1941
|
const padding = Math.max(0, targetWidth - visibleWidth(truncated));
|
|
1887
1942
|
return `${truncated}${" ".repeat(padding)}`;
|
|
1888
1943
|
}
|
|
1944
|
+
function centerVisible(text, targetWidth) {
|
|
1945
|
+
const truncated = truncateVisible(text, targetWidth);
|
|
1946
|
+
const extra = Math.max(0, targetWidth - visibleWidth(truncated));
|
|
1947
|
+
const leftPadding = Math.floor(extra / 2);
|
|
1948
|
+
const rightPadding = extra - leftPadding;
|
|
1949
|
+
return `${" ".repeat(leftPadding)}${truncated}${" ".repeat(rightPadding)}`;
|
|
1950
|
+
}
|
|
1889
1951
|
function truncateVisible(text, targetWidth) {
|
|
1890
1952
|
if (visibleWidth(text) <= targetWidth) {
|
|
1891
1953
|
return text;
|
|
@@ -1918,6 +1980,50 @@ function visibleWidth(text) {
|
|
|
1918
1980
|
function clamp(value, min, max) {
|
|
1919
1981
|
return Math.max(min, Math.min(max, value));
|
|
1920
1982
|
}
|
|
1983
|
+
function capitalize(value) {
|
|
1984
|
+
if (value.length === 0) {
|
|
1985
|
+
return value;
|
|
1986
|
+
}
|
|
1987
|
+
return value[0].toUpperCase() + value.slice(1);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
// src/utils/spinner.ts
|
|
1991
|
+
var Spinner = class {
|
|
1992
|
+
message = "";
|
|
1993
|
+
frame = 0;
|
|
1994
|
+
interval = null;
|
|
1995
|
+
writer;
|
|
1996
|
+
isTTY;
|
|
1997
|
+
frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1998
|
+
constructor(writer, isTTY = false) {
|
|
1999
|
+
this.writer = writer;
|
|
2000
|
+
this.isTTY = isTTY;
|
|
2001
|
+
}
|
|
2002
|
+
start(message) {
|
|
2003
|
+
if (!this.isTTY) {
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
this.message = message;
|
|
2007
|
+
this.frame = 0;
|
|
2008
|
+
this.interval = setInterval(() => {
|
|
2009
|
+
const spinner = this.frames[this.frame % this.frames.length];
|
|
2010
|
+
this.writer.write(`\r${spinner} ${this.message}`);
|
|
2011
|
+
this.frame += 1;
|
|
2012
|
+
}, 80);
|
|
2013
|
+
}
|
|
2014
|
+
stop() {
|
|
2015
|
+
if (this.interval) {
|
|
2016
|
+
clearInterval(this.interval);
|
|
2017
|
+
this.interval = null;
|
|
2018
|
+
}
|
|
2019
|
+
if (this.isTTY) {
|
|
2020
|
+
this.writer.write("\r\x1B[K");
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
};
|
|
2024
|
+
function createSpinner(writer, isTTY = false) {
|
|
2025
|
+
return new Spinner(writer, isTTY);
|
|
2026
|
+
}
|
|
1921
2027
|
|
|
1922
2028
|
// src/cli.ts
|
|
1923
2029
|
async function runCli(argv, io = { stdout: process.stdout, stderr: process.stderr, stdin: process.stdin }, dependencies = { createOptimizer, readStdin, getCliInfo }) {
|
|
@@ -1981,7 +2087,9 @@ async function runCli(argv, io = { stdout: process.stdout, stderr: process.stder
|
|
|
1981
2087
|
io.stderr.write("A prompt is required.\n");
|
|
1982
2088
|
return 1;
|
|
1983
2089
|
}
|
|
2090
|
+
const spinner = createSpinner(io.stderr, io.stderr.isTTY ?? false);
|
|
1984
2091
|
try {
|
|
2092
|
+
spinner.start("optimizing");
|
|
1985
2093
|
const result = await optimizer.optimize({
|
|
1986
2094
|
prompt: parsed.prompt,
|
|
1987
2095
|
task: parsed.task,
|
|
@@ -2010,11 +2118,26 @@ async function runCli(argv, io = { stdout: process.stdout, stderr: process.stder
|
|
|
2010
2118
|
timeoutMs: parsed.timeoutMs,
|
|
2011
2119
|
bypassOptimization: parsed.bypassOptimization
|
|
2012
2120
|
});
|
|
2121
|
+
spinner.stop();
|
|
2013
2122
|
if (parsed.json) {
|
|
2014
2123
|
io.stdout.write(`${toPrettyJson(result)}
|
|
2015
2124
|
`);
|
|
2016
2125
|
return 0;
|
|
2017
2126
|
}
|
|
2127
|
+
if (parsed.clipboard) {
|
|
2128
|
+
const copied = copyToClipboard(result.finalPrompt);
|
|
2129
|
+
if (copied) {
|
|
2130
|
+
io.stderr.write(`\u2713 Copied optimized prompt to clipboard
|
|
2131
|
+
`);
|
|
2132
|
+
return 0;
|
|
2133
|
+
} else {
|
|
2134
|
+
io.stderr.write(`\u2717 Failed to copy to clipboard. Install xclip, xsel, or wl-copy.
|
|
2135
|
+
`);
|
|
2136
|
+
io.stdout.write(`${result.finalPrompt}
|
|
2137
|
+
`);
|
|
2138
|
+
return 1;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2018
2141
|
if (parsed.plain) {
|
|
2019
2142
|
io.stdout.write(`${result.finalPrompt}
|
|
2020
2143
|
`);
|
|
@@ -2024,6 +2147,8 @@ async function runCli(argv, io = { stdout: process.stdout, stderr: process.stder
|
|
|
2024
2147
|
|
|
2025
2148
|
`);
|
|
2026
2149
|
io.stdout.write(`provider=${result.provider} model=${result.model} tokens=${result.estimatedTokensAfter.total} savings=${result.tokenSavings}
|
|
2150
|
+
`);
|
|
2151
|
+
io.stdout.write(` prompt_savings=${result.promptCompressionSavings} context_savings=${result.contextCompressionSavings} wrapper_overhead=${result.wrapperOverhead}
|
|
2027
2152
|
`);
|
|
2028
2153
|
if (result.selectedTarget) {
|
|
2029
2154
|
io.stdout.write(`selected_target=${formatTarget(result.selectedTarget)}
|
|
@@ -2035,6 +2160,7 @@ async function runCli(argv, io = { stdout: process.stdout, stderr: process.stder
|
|
|
2035
2160
|
}
|
|
2036
2161
|
return 0;
|
|
2037
2162
|
} catch (error) {
|
|
2163
|
+
spinner.stop();
|
|
2038
2164
|
const message = error instanceof Error ? error.message : "Unknown CLI error.";
|
|
2039
2165
|
io.stderr.write(`${message}
|
|
2040
2166
|
`);
|
|
@@ -2046,6 +2172,7 @@ function parseOptimizeArgs(args) {
|
|
|
2046
2172
|
plain: false,
|
|
2047
2173
|
json: false,
|
|
2048
2174
|
debug: false,
|
|
2175
|
+
clipboard: false,
|
|
2049
2176
|
clearSession: false,
|
|
2050
2177
|
useContext: true,
|
|
2051
2178
|
bypassOptimization: false,
|
|
@@ -2129,6 +2256,9 @@ function parseOptimizeArgs(args) {
|
|
|
2129
2256
|
case "--json":
|
|
2130
2257
|
parsed.json = true;
|
|
2131
2258
|
break;
|
|
2259
|
+
case "--clipboard":
|
|
2260
|
+
parsed.clipboard = true;
|
|
2261
|
+
break;
|
|
2132
2262
|
case "--debug":
|
|
2133
2263
|
parsed.debug = true;
|
|
2134
2264
|
break;
|
|
@@ -2195,6 +2325,7 @@ function getHelpText() {
|
|
|
2195
2325
|
" --sqlite-path <path>",
|
|
2196
2326
|
" --plain",
|
|
2197
2327
|
" --json",
|
|
2328
|
+
" --clipboard Copy optimized prompt to clipboard",
|
|
2198
2329
|
" --debug",
|
|
2199
2330
|
" --save-context",
|
|
2200
2331
|
" --no-context",
|
|
@@ -2264,6 +2395,23 @@ function readPackageVersion() {
|
|
|
2264
2395
|
return "dev";
|
|
2265
2396
|
}
|
|
2266
2397
|
}
|
|
2398
|
+
function copyToClipboard(text) {
|
|
2399
|
+
const commands = [
|
|
2400
|
+
{ cmd: "xclip", args: ["-selection", "clipboard"], platform: "linux" },
|
|
2401
|
+
{ cmd: "xsel", args: ["-b"], platform: "linux" },
|
|
2402
|
+
{ cmd: "wl-copy", args: [], platform: "linux" },
|
|
2403
|
+
{ cmd: "pbcopy", args: [], platform: "darwin" }
|
|
2404
|
+
];
|
|
2405
|
+
for (const { cmd } of commands) {
|
|
2406
|
+
try {
|
|
2407
|
+
execSync(`which ${cmd} > /dev/null 2>&1`);
|
|
2408
|
+
execSync(cmd, { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
2409
|
+
return true;
|
|
2410
|
+
} catch {
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
return false;
|
|
2414
|
+
}
|
|
2267
2415
|
if (isMainModule()) {
|
|
2268
2416
|
runCli(process.argv.slice(2)).then(
|
|
2269
2417
|
(code) => {
|