@gaberrb/polypus 0.4.6 → 0.4.8
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/index.js +390 -147
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import pc14 from "picocolors";
|
|
6
6
|
|
|
7
7
|
// src/cli/commands/add-agent.ts
|
|
8
8
|
import pc from "picocolors";
|
|
@@ -110,6 +110,9 @@ var en = {
|
|
|
110
110
|
"cli.opt.mode": "plan | review | bypass (overrides config)",
|
|
111
111
|
"cli.opt.maxSteps": "maximum agent steps",
|
|
112
112
|
"cli.opt.json": "headless mode: emit a single JSON object (steps, tool calls, files changed, usage) instead of the TUI \u2014 use with --mode bypass",
|
|
113
|
+
"cli.opt.verify": "after the agent finishes, run project checks (typecheck/build/test) and iterate until they pass",
|
|
114
|
+
"cli.opt.budget": "stop the run when the estimated session cost reaches this USD amount (OpenRouter pricing)",
|
|
115
|
+
"cli.cmd.usage": "Show token/cost analytics aggregated per day",
|
|
113
116
|
"cli.arg.swarmTask": "high-level task to split across agents",
|
|
114
117
|
"cli.opt.agents": "comma-separated agent names (default: all configured)",
|
|
115
118
|
"cli.opt.maxSubtasks": "maximum number of parallel subtasks",
|
|
@@ -141,6 +144,18 @@ var en = {
|
|
|
141
144
|
"review.reject": "reject",
|
|
142
145
|
"review.pickHunks": "pick hunks\u2026",
|
|
143
146
|
"review.selectHunks": "Select the hunks to apply (space to toggle, enter to confirm)",
|
|
147
|
+
"verify.running": "verifying (running project checks)",
|
|
148
|
+
"verify.noChecks": "no verification checks detected (no package.json scripts) \u2014 skipping",
|
|
149
|
+
"verify.passed": "verification passed",
|
|
150
|
+
"verify.failed": "{n} check(s) failed \u2014 handing the output back to the agent (attempt {attempt})",
|
|
151
|
+
"verify.giveUp": "{n} check(s) still failing after the retry budget \u2014 stopping",
|
|
152
|
+
"budget.session": "session spend: {spent} / budget {budget}",
|
|
153
|
+
"budget.hit": "\u25A0 stopped: estimated cost reached the budget of {budget}",
|
|
154
|
+
// usage analytics
|
|
155
|
+
"usage.header": "Usage (tokens / estimated cost) per day:",
|
|
156
|
+
"usage.empty": "No usage recorded yet. Run a task to start tracking.",
|
|
157
|
+
"usage.total": "total",
|
|
158
|
+
"usage.runs": "runs",
|
|
144
159
|
// repl
|
|
145
160
|
"repl.welcome": "Polypus interactive session.",
|
|
146
161
|
"repl.welcomeHint": " Type /help for commands, /exit to quit.",
|
|
@@ -344,6 +359,9 @@ var ptBR = {
|
|
|
344
359
|
"cli.opt.mode": "plan | review | bypass (sobrescreve a config)",
|
|
345
360
|
"cli.opt.maxSteps": "n\xFAmero m\xE1ximo de passos do agente",
|
|
346
361
|
"cli.opt.json": "modo headless: emite um \xFAnico objeto JSON (passos, tool calls, arquivos alterados, uso) em vez da TUI \u2014 use com --mode bypass",
|
|
362
|
+
"cli.opt.verify": "ap\xF3s o agente terminar, roda as checagens do projeto (typecheck/build/test) e itera at\xE9 passar",
|
|
363
|
+
"cli.opt.budget": "interrompe a execu\xE7\xE3o quando o custo estimado da sess\xE3o atingir este valor em USD (pre\xE7os do OpenRouter)",
|
|
364
|
+
"cli.cmd.usage": "Mostra analytics de tokens/custo agregados por dia",
|
|
347
365
|
"cli.arg.swarmTask": "tarefa de alto n\xEDvel para dividir entre os agentes",
|
|
348
366
|
"cli.opt.agents": "nomes de agentes separados por v\xEDrgula (padr\xE3o: todos)",
|
|
349
367
|
"cli.opt.maxSubtasks": "n\xFAmero m\xE1ximo de subtarefas paralelas",
|
|
@@ -373,6 +391,18 @@ var ptBR = {
|
|
|
373
391
|
"review.reject": "rejeitar",
|
|
374
392
|
"review.pickHunks": "escolher hunks\u2026",
|
|
375
393
|
"review.selectHunks": "Selecione os hunks a aplicar (espa\xE7o alterna, enter confirma)",
|
|
394
|
+
"verify.running": "verificando (rodando as checagens do projeto)",
|
|
395
|
+
"verify.noChecks": "nenhuma checagem detectada (sem scripts no package.json) \u2014 pulando",
|
|
396
|
+
"verify.passed": "verifica\xE7\xE3o passou",
|
|
397
|
+
"verify.failed": "{n} checagem(ns) falharam \u2014 devolvendo a sa\xEDda ao agente (tentativa {attempt})",
|
|
398
|
+
"verify.giveUp": "{n} checagem(ns) ainda falhando ap\xF3s o limite de tentativas \u2014 parando",
|
|
399
|
+
"budget.session": "gasto da sess\xE3o: {spent} / or\xE7amento {budget}",
|
|
400
|
+
"budget.hit": "\u25A0 interrompido: o custo estimado atingiu o or\xE7amento de {budget}",
|
|
401
|
+
// usage analytics
|
|
402
|
+
"usage.header": "Uso (tokens / custo estimado) por dia:",
|
|
403
|
+
"usage.empty": "Nenhum uso registrado ainda. Rode uma tarefa para come\xE7ar a medir.",
|
|
404
|
+
"usage.total": "total",
|
|
405
|
+
"usage.runs": "execu\xE7\xF5es",
|
|
376
406
|
"repl.welcome": "Sess\xE3o interativa do Polypus.",
|
|
377
407
|
"repl.welcomeHint": " Digite /help para comandos, /exit para sair.",
|
|
378
408
|
"repl.modeChanged": "modo \u2192 {mode}",
|
|
@@ -2084,9 +2114,9 @@ async function runAgent(opts) {
|
|
|
2084
2114
|
let failStreak = 0;
|
|
2085
2115
|
const maxToolRetries = opts.maxToolRetries ?? 3;
|
|
2086
2116
|
const autoCorrect = opts.autoCorrect ?? true;
|
|
2087
|
-
const
|
|
2117
|
+
const usage2 = { promptTokens: 0, completionTokens: 0 };
|
|
2088
2118
|
for (let step = 1; step <= maxSteps; step++) {
|
|
2089
|
-
if (opts.signal?.aborted) return { finished: false, reason: "cancelled", steps: step - 1, messages, usage };
|
|
2119
|
+
if (opts.signal?.aborted) return { finished: false, reason: "cancelled", steps: step - 1, messages, usage: usage2 };
|
|
2090
2120
|
events?.onStep?.(step);
|
|
2091
2121
|
let response;
|
|
2092
2122
|
try {
|
|
@@ -2097,12 +2127,12 @@ async function runAgent(opts) {
|
|
|
2097
2127
|
signal: opts.signal
|
|
2098
2128
|
});
|
|
2099
2129
|
} catch (err) {
|
|
2100
|
-
if (opts.signal?.aborted) return { finished: false, reason: "cancelled", steps: step, messages, usage };
|
|
2130
|
+
if (opts.signal?.aborted) return { finished: false, reason: "cancelled", steps: step, messages, usage: usage2 };
|
|
2101
2131
|
throw err;
|
|
2102
2132
|
}
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
events?.onUsage?.(
|
|
2133
|
+
usage2.promptTokens += response.usage?.promptTokens ?? 0;
|
|
2134
|
+
usage2.completionTokens += response.usage?.completionTokens ?? 0;
|
|
2135
|
+
events?.onUsage?.(usage2);
|
|
2106
2136
|
const { toolCalls, text: text2 } = driver.parse(response);
|
|
2107
2137
|
messages.push(driver.assistantMessage(response, toolCalls));
|
|
2108
2138
|
if (text2) events?.onAssistantText?.(text2);
|
|
@@ -2116,7 +2146,7 @@ async function runAgent(opts) {
|
|
|
2116
2146
|
messages.push({ role: "user", content: guidance });
|
|
2117
2147
|
continue;
|
|
2118
2148
|
}
|
|
2119
|
-
return { finished: false, reason: "stalled", steps: step, messages, usage };
|
|
2149
|
+
return { finished: false, reason: "stalled", steps: step, messages, usage: usage2 };
|
|
2120
2150
|
}
|
|
2121
2151
|
const stalled = text2.trim().length === 0 || looksLikeStall(text2);
|
|
2122
2152
|
if (stalled) {
|
|
@@ -2126,17 +2156,17 @@ async function runAgent(opts) {
|
|
|
2126
2156
|
messages.push(driver.repromptMessage());
|
|
2127
2157
|
continue;
|
|
2128
2158
|
}
|
|
2129
|
-
return { finished: false, reason: "stalled", steps: step, messages, usage };
|
|
2159
|
+
return { finished: false, reason: "stalled", steps: step, messages, usage: usage2 };
|
|
2130
2160
|
}
|
|
2131
|
-
return { finished: false, reason: "reply", steps: step, messages, usage };
|
|
2161
|
+
return { finished: false, reason: "reply", steps: step, messages, usage: usage2 };
|
|
2132
2162
|
}
|
|
2133
2163
|
consecutiveNoTool = 0;
|
|
2134
2164
|
for (const call of toolCalls) {
|
|
2135
|
-
if (opts.signal?.aborted) return { finished: false, reason: "cancelled", steps: step, messages, usage };
|
|
2165
|
+
if (opts.signal?.aborted) return { finished: false, reason: "cancelled", steps: step, messages, usage: usage2 };
|
|
2136
2166
|
events?.onToolCall?.(call);
|
|
2137
2167
|
if (call.name === "finish") {
|
|
2138
2168
|
const summary = String(call.arguments.summary ?? "").trim();
|
|
2139
|
-
return { finished: true, reason: "finished", summary, steps: step, messages, usage };
|
|
2169
|
+
return { finished: true, reason: "finished", summary, steps: step, messages, usage: usage2 };
|
|
2140
2170
|
}
|
|
2141
2171
|
const tool = getTool(call.name);
|
|
2142
2172
|
const result = tool ? await tool.run(call.arguments, ctx) : { ok: false, output: `Unknown tool "${call.name}". Available: ${toolSpecs().map((t2) => t2.name).join(", ")}` };
|
|
@@ -2174,12 +2204,12 @@ ${guidance}`;
|
|
|
2174
2204
|
failStreak = sig === lastFailSig ? failStreak + 1 : 1;
|
|
2175
2205
|
lastFailSig = sig;
|
|
2176
2206
|
if (failStreak >= maxToolRetries) {
|
|
2177
|
-
return { finished: false, reason: "stalled", steps: step, messages, usage };
|
|
2207
|
+
return { finished: false, reason: "stalled", steps: step, messages, usage: usage2 };
|
|
2178
2208
|
}
|
|
2179
2209
|
}
|
|
2180
2210
|
}
|
|
2181
2211
|
}
|
|
2182
|
-
return { finished: false, reason: "maxsteps", steps: maxSteps, messages, usage };
|
|
2212
|
+
return { finished: false, reason: "maxsteps", steps: maxSteps, messages, usage: usage2 };
|
|
2183
2213
|
}
|
|
2184
2214
|
|
|
2185
2215
|
// src/core/context/mentions.ts
|
|
@@ -2232,6 +2262,205 @@ ${blocks.join("\n\n")}`;
|
|
|
2232
2262
|
return { task: augmented, injected };
|
|
2233
2263
|
}
|
|
2234
2264
|
|
|
2265
|
+
// src/core/agent/verify.ts
|
|
2266
|
+
import { exec as exec2 } from "child_process";
|
|
2267
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
2268
|
+
import { resolve as resolve10 } from "path";
|
|
2269
|
+
import { promisify as promisify2 } from "util";
|
|
2270
|
+
var execAsync2 = promisify2(exec2);
|
|
2271
|
+
var MAX_OUTPUT3 = 8e3;
|
|
2272
|
+
var CHECK_SCRIPTS = ["typecheck", "build", "test"];
|
|
2273
|
+
async function detectChecks(workspace) {
|
|
2274
|
+
try {
|
|
2275
|
+
const raw = await readFile9(resolve10(workspace, "package.json"), "utf8");
|
|
2276
|
+
const scripts = JSON.parse(raw).scripts ?? {};
|
|
2277
|
+
return CHECK_SCRIPTS.filter((s) => typeof scripts[s] === "string").map((s) => `npm run ${s}`);
|
|
2278
|
+
} catch {
|
|
2279
|
+
return [];
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
async function runChecks(workspace, commands) {
|
|
2283
|
+
const results = [];
|
|
2284
|
+
for (const command of commands) {
|
|
2285
|
+
try {
|
|
2286
|
+
const { stdout: stdout2, stderr } = await execAsync2(command, {
|
|
2287
|
+
cwd: workspace,
|
|
2288
|
+
timeout: 3e5,
|
|
2289
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
2290
|
+
windowsHide: true
|
|
2291
|
+
});
|
|
2292
|
+
results.push({ command, ok: true, output: clamp2(`${stdout2}${stderr ? `
|
|
2293
|
+
${stderr}` : ""}`.trim()) });
|
|
2294
|
+
} catch (err) {
|
|
2295
|
+
const e = err;
|
|
2296
|
+
const body = `${e.stdout ?? ""}${e.stderr ?? ""}`.trim() || e.message;
|
|
2297
|
+
results.push({ command, ok: false, output: clamp2(body) });
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
return results;
|
|
2301
|
+
}
|
|
2302
|
+
function buildVerifyFeedback(failed) {
|
|
2303
|
+
const blocks = failed.map((f) => `$ ${f.command}
|
|
2304
|
+
${f.output}`).join("\n\n");
|
|
2305
|
+
return [
|
|
2306
|
+
"Verification failed. The following checks did not pass:",
|
|
2307
|
+
"",
|
|
2308
|
+
blocks,
|
|
2309
|
+
"",
|
|
2310
|
+
"Fix the underlying problem in the code (do not disable or skip the checks), then call `finish`."
|
|
2311
|
+
].join("\n");
|
|
2312
|
+
}
|
|
2313
|
+
function clamp2(s) {
|
|
2314
|
+
return s.length > MAX_OUTPUT3 ? s.slice(-MAX_OUTPUT3) : s;
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
// src/core/agent/usage.ts
|
|
2318
|
+
import { appendFile, mkdir as mkdir3, readFile as readFile10 } from "fs/promises";
|
|
2319
|
+
import { join as join4 } from "path";
|
|
2320
|
+
|
|
2321
|
+
// src/core/providers/openrouter.ts
|
|
2322
|
+
var MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
2323
|
+
async function listOpenRouterModels(apiKey, timeoutMs = 8e3) {
|
|
2324
|
+
const controller = new AbortController();
|
|
2325
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2326
|
+
try {
|
|
2327
|
+
const res = await fetch(MODELS_URL, {
|
|
2328
|
+
signal: controller.signal,
|
|
2329
|
+
headers: apiKey ? { authorization: `Bearer ${apiKey}` } : {}
|
|
2330
|
+
});
|
|
2331
|
+
if (!res.ok) throw new Error(`OpenRouter ${res.status}: ${await res.text().catch(() => "")}`);
|
|
2332
|
+
const data = await res.json();
|
|
2333
|
+
return (data.data ?? []).map(normalize).filter((m) => m.id.length > 0);
|
|
2334
|
+
} finally {
|
|
2335
|
+
clearTimeout(timer);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
function normalize(m) {
|
|
2339
|
+
const promptPrice = toPerMillion(m.pricing?.prompt);
|
|
2340
|
+
const completionPrice = toPerMillion(m.pricing?.completion);
|
|
2341
|
+
return {
|
|
2342
|
+
id: m.id ?? "",
|
|
2343
|
+
name: m.name ?? m.id ?? "",
|
|
2344
|
+
promptPrice,
|
|
2345
|
+
completionPrice,
|
|
2346
|
+
contextLength: m.context_length ?? 0,
|
|
2347
|
+
supportsTools: (m.supported_parameters ?? []).includes("tools"),
|
|
2348
|
+
free: promptPrice === 0 && completionPrice === 0
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
function toPerMillion(price) {
|
|
2352
|
+
const n = Number(price ?? "0");
|
|
2353
|
+
return Number.isFinite(n) ? n * 1e6 : 0;
|
|
2354
|
+
}
|
|
2355
|
+
function filterModels(models2, f) {
|
|
2356
|
+
const term = f.search?.trim().toLowerCase();
|
|
2357
|
+
let out = models2.filter((m) => {
|
|
2358
|
+
if (term && !m.id.toLowerCase().includes(term) && !m.name.toLowerCase().includes(term)) {
|
|
2359
|
+
return false;
|
|
2360
|
+
}
|
|
2361
|
+
if (f.tools === "tools" && !m.supportsTools) return false;
|
|
2362
|
+
if (f.tools === "no-tools" && m.supportsTools) return false;
|
|
2363
|
+
if (f.freeOnly && !m.free) return false;
|
|
2364
|
+
if (f.maxPrice !== void 0 && (m.promptPrice < 0 || m.promptPrice > f.maxPrice)) return false;
|
|
2365
|
+
return true;
|
|
2366
|
+
});
|
|
2367
|
+
const key = (m) => m.promptPrice < 0 ? Number.POSITIVE_INFINITY : m.promptPrice;
|
|
2368
|
+
const sort = f.sort ?? "price";
|
|
2369
|
+
out = out.sort((a, b) => {
|
|
2370
|
+
switch (sort) {
|
|
2371
|
+
case "price-desc":
|
|
2372
|
+
return key(b) - key(a);
|
|
2373
|
+
case "context":
|
|
2374
|
+
return b.contextLength - a.contextLength;
|
|
2375
|
+
case "name":
|
|
2376
|
+
return a.id.localeCompare(b.id);
|
|
2377
|
+
default:
|
|
2378
|
+
return key(a) - key(b);
|
|
2379
|
+
}
|
|
2380
|
+
});
|
|
2381
|
+
return out;
|
|
2382
|
+
}
|
|
2383
|
+
function fmtPrice(perMillion) {
|
|
2384
|
+
if (perMillion < 0) return "var";
|
|
2385
|
+
if (perMillion === 0) return "free";
|
|
2386
|
+
if (perMillion < 1) return `$${perMillion.toFixed(2)}`;
|
|
2387
|
+
if (perMillion < 100) return `$${perMillion.toFixed(perMillion % 1 ? 1 : 0)}`;
|
|
2388
|
+
return `$${Math.round(perMillion)}`;
|
|
2389
|
+
}
|
|
2390
|
+
function fmtContext(n) {
|
|
2391
|
+
if (n >= 1e6) return `${Math.round(n / 1e5) / 10}M`;
|
|
2392
|
+
if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
|
|
2393
|
+
return String(n);
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
// src/core/agent/usage.ts
|
|
2397
|
+
var catalogCache;
|
|
2398
|
+
async function resolveModelPricing(agent) {
|
|
2399
|
+
if (agent.provider !== "openrouter") return void 0;
|
|
2400
|
+
try {
|
|
2401
|
+
catalogCache ??= listOpenRouterModels(resolveSecret(agent.apiKey));
|
|
2402
|
+
const models2 = await catalogCache;
|
|
2403
|
+
const m = models2.find((x) => x.id === agent.model);
|
|
2404
|
+
if (!m || m.promptPrice < 0 || m.completionPrice < 0) return void 0;
|
|
2405
|
+
return { promptPrice: m.promptPrice, completionPrice: m.completionPrice };
|
|
2406
|
+
} catch {
|
|
2407
|
+
return void 0;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
function estimateCost(usage2, pricing) {
|
|
2411
|
+
return usage2.promptTokens / 1e6 * pricing.promptPrice + usage2.completionTokens / 1e6 * pricing.completionPrice;
|
|
2412
|
+
}
|
|
2413
|
+
function fmtUsd(n) {
|
|
2414
|
+
if (n <= 0) return "US$0.00";
|
|
2415
|
+
if (n < 0.01) return `US$${n.toFixed(4)}`;
|
|
2416
|
+
return `US$${n.toFixed(2)}`;
|
|
2417
|
+
}
|
|
2418
|
+
function usagePath() {
|
|
2419
|
+
return join4(configDir(), "usage.jsonl");
|
|
2420
|
+
}
|
|
2421
|
+
async function recordUsage(entry) {
|
|
2422
|
+
try {
|
|
2423
|
+
await mkdir3(configDir(), { recursive: true });
|
|
2424
|
+
await appendFile(usagePath(), JSON.stringify(entry) + "\n", "utf8");
|
|
2425
|
+
} catch {
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
async function aggregateUsage() {
|
|
2429
|
+
let text2 = "";
|
|
2430
|
+
try {
|
|
2431
|
+
text2 = await readFile10(usagePath(), "utf8");
|
|
2432
|
+
} catch {
|
|
2433
|
+
return { days: [], total: emptyBucket("total") };
|
|
2434
|
+
}
|
|
2435
|
+
const byDay = /* @__PURE__ */ new Map();
|
|
2436
|
+
const total = emptyBucket("total");
|
|
2437
|
+
for (const line of text2.split("\n")) {
|
|
2438
|
+
if (!line.trim()) continue;
|
|
2439
|
+
let e;
|
|
2440
|
+
try {
|
|
2441
|
+
e = JSON.parse(line);
|
|
2442
|
+
} catch {
|
|
2443
|
+
continue;
|
|
2444
|
+
}
|
|
2445
|
+
const date = (e.ts ?? "").slice(0, 10) || "unknown";
|
|
2446
|
+
const bucket = byDay.get(date) ?? emptyBucket(date);
|
|
2447
|
+
accumulate(bucket, e);
|
|
2448
|
+
byDay.set(date, bucket);
|
|
2449
|
+
accumulate(total, e);
|
|
2450
|
+
}
|
|
2451
|
+
const days = [...byDay.values()].sort((a, b) => a.date.localeCompare(b.date));
|
|
2452
|
+
return { days, total };
|
|
2453
|
+
}
|
|
2454
|
+
function emptyBucket(date) {
|
|
2455
|
+
return { date, promptTokens: 0, completionTokens: 0, costUsd: 0, runs: 0 };
|
|
2456
|
+
}
|
|
2457
|
+
function accumulate(bucket, e) {
|
|
2458
|
+
bucket.promptTokens += e.promptTokens ?? 0;
|
|
2459
|
+
bucket.completionTokens += e.completionTokens ?? 0;
|
|
2460
|
+
bucket.costUsd += e.costUsd ?? 0;
|
|
2461
|
+
bucket.runs += 1;
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2235
2464
|
// src/cli/commands/json-output.ts
|
|
2236
2465
|
var OUTPUT_PREVIEW = 500;
|
|
2237
2466
|
function createJsonCollector() {
|
|
@@ -2317,81 +2546,6 @@ async function listOllamaModels(host = ollamaHost(), timeoutMs = 2e3) {
|
|
|
2317
2546
|
}
|
|
2318
2547
|
}
|
|
2319
2548
|
|
|
2320
|
-
// src/core/providers/openrouter.ts
|
|
2321
|
-
var MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
2322
|
-
async function listOpenRouterModels(apiKey, timeoutMs = 8e3) {
|
|
2323
|
-
const controller = new AbortController();
|
|
2324
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2325
|
-
try {
|
|
2326
|
-
const res = await fetch(MODELS_URL, {
|
|
2327
|
-
signal: controller.signal,
|
|
2328
|
-
headers: apiKey ? { authorization: `Bearer ${apiKey}` } : {}
|
|
2329
|
-
});
|
|
2330
|
-
if (!res.ok) throw new Error(`OpenRouter ${res.status}: ${await res.text().catch(() => "")}`);
|
|
2331
|
-
const data = await res.json();
|
|
2332
|
-
return (data.data ?? []).map(normalize).filter((m) => m.id.length > 0);
|
|
2333
|
-
} finally {
|
|
2334
|
-
clearTimeout(timer);
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
2337
|
-
function normalize(m) {
|
|
2338
|
-
const promptPrice = toPerMillion(m.pricing?.prompt);
|
|
2339
|
-
const completionPrice = toPerMillion(m.pricing?.completion);
|
|
2340
|
-
return {
|
|
2341
|
-
id: m.id ?? "",
|
|
2342
|
-
name: m.name ?? m.id ?? "",
|
|
2343
|
-
promptPrice,
|
|
2344
|
-
completionPrice,
|
|
2345
|
-
contextLength: m.context_length ?? 0,
|
|
2346
|
-
supportsTools: (m.supported_parameters ?? []).includes("tools"),
|
|
2347
|
-
free: promptPrice === 0 && completionPrice === 0
|
|
2348
|
-
};
|
|
2349
|
-
}
|
|
2350
|
-
function toPerMillion(price) {
|
|
2351
|
-
const n = Number(price ?? "0");
|
|
2352
|
-
return Number.isFinite(n) ? n * 1e6 : 0;
|
|
2353
|
-
}
|
|
2354
|
-
function filterModels(models2, f) {
|
|
2355
|
-
const term = f.search?.trim().toLowerCase();
|
|
2356
|
-
let out = models2.filter((m) => {
|
|
2357
|
-
if (term && !m.id.toLowerCase().includes(term) && !m.name.toLowerCase().includes(term)) {
|
|
2358
|
-
return false;
|
|
2359
|
-
}
|
|
2360
|
-
if (f.tools === "tools" && !m.supportsTools) return false;
|
|
2361
|
-
if (f.tools === "no-tools" && m.supportsTools) return false;
|
|
2362
|
-
if (f.freeOnly && !m.free) return false;
|
|
2363
|
-
if (f.maxPrice !== void 0 && (m.promptPrice < 0 || m.promptPrice > f.maxPrice)) return false;
|
|
2364
|
-
return true;
|
|
2365
|
-
});
|
|
2366
|
-
const key = (m) => m.promptPrice < 0 ? Number.POSITIVE_INFINITY : m.promptPrice;
|
|
2367
|
-
const sort = f.sort ?? "price";
|
|
2368
|
-
out = out.sort((a, b) => {
|
|
2369
|
-
switch (sort) {
|
|
2370
|
-
case "price-desc":
|
|
2371
|
-
return key(b) - key(a);
|
|
2372
|
-
case "context":
|
|
2373
|
-
return b.contextLength - a.contextLength;
|
|
2374
|
-
case "name":
|
|
2375
|
-
return a.id.localeCompare(b.id);
|
|
2376
|
-
default:
|
|
2377
|
-
return key(a) - key(b);
|
|
2378
|
-
}
|
|
2379
|
-
});
|
|
2380
|
-
return out;
|
|
2381
|
-
}
|
|
2382
|
-
function fmtPrice(perMillion) {
|
|
2383
|
-
if (perMillion < 0) return "var";
|
|
2384
|
-
if (perMillion === 0) return "free";
|
|
2385
|
-
if (perMillion < 1) return `$${perMillion.toFixed(2)}`;
|
|
2386
|
-
if (perMillion < 100) return `$${perMillion.toFixed(perMillion % 1 ? 1 : 0)}`;
|
|
2387
|
-
return `$${Math.round(perMillion)}`;
|
|
2388
|
-
}
|
|
2389
|
-
function fmtContext(n) {
|
|
2390
|
-
if (n >= 1e6) return `${Math.round(n / 1e5) / 10}M`;
|
|
2391
|
-
if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
|
|
2392
|
-
return String(n);
|
|
2393
|
-
}
|
|
2394
|
-
|
|
2395
2549
|
// src/ui/wizard.ts
|
|
2396
2550
|
function bail(value) {
|
|
2397
2551
|
if (p.isCancel(value)) {
|
|
@@ -2924,10 +3078,10 @@ async function readLineTTY(prompt) {
|
|
|
2924
3078
|
stdin.resume();
|
|
2925
3079
|
stdin.on("data", onData);
|
|
2926
3080
|
try {
|
|
2927
|
-
const line = await new Promise((
|
|
2928
|
-
rl.question(prompt).then(
|
|
2929
|
-
rl.on("SIGINT", () =>
|
|
2930
|
-
rl.on("close", () =>
|
|
3081
|
+
const line = await new Promise((resolve12) => {
|
|
3082
|
+
rl.question(prompt).then(resolve12, () => resolve12(null));
|
|
3083
|
+
rl.on("SIGINT", () => resolve12(null));
|
|
3084
|
+
rl.on("close", () => resolve12(null));
|
|
2931
3085
|
});
|
|
2932
3086
|
return line === null ? null : store.expand(line);
|
|
2933
3087
|
} finally {
|
|
@@ -3072,7 +3226,7 @@ import pc7 from "picocolors";
|
|
|
3072
3226
|
// src/core/git/worktree.ts
|
|
3073
3227
|
import { mkdtemp } from "fs/promises";
|
|
3074
3228
|
import { tmpdir } from "os";
|
|
3075
|
-
import { join as
|
|
3229
|
+
import { join as join5 } from "path";
|
|
3076
3230
|
import { simpleGit } from "simple-git";
|
|
3077
3231
|
async function ensureRepo(workspace) {
|
|
3078
3232
|
const git = simpleGit(workspace);
|
|
@@ -3093,7 +3247,7 @@ async function identityArgs(git) {
|
|
|
3093
3247
|
}
|
|
3094
3248
|
async function createWorktree(git, label) {
|
|
3095
3249
|
const branch = `polypus/${label}-${Date.now().toString(36)}`;
|
|
3096
|
-
const path = await mkdtemp(
|
|
3250
|
+
const path = await mkdtemp(join5(tmpdir(), "polypus-wt-"));
|
|
3097
3251
|
await git.raw(["worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3098
3252
|
return { path, branch };
|
|
3099
3253
|
}
|
|
@@ -3521,6 +3675,7 @@ var Spinner = class {
|
|
|
3521
3675
|
};
|
|
3522
3676
|
|
|
3523
3677
|
// src/cli/commands/run.ts
|
|
3678
|
+
var MAX_VERIFY_FIXES = 3;
|
|
3524
3679
|
async function run(task, opts) {
|
|
3525
3680
|
let config = await loadConfig();
|
|
3526
3681
|
const agentConfig = resolveAgent(config, opts.agent);
|
|
@@ -3532,7 +3687,9 @@ async function run(task, opts) {
|
|
|
3532
3687
|
deny: config.permissions.deny,
|
|
3533
3688
|
allowedCommands: config.permissions.allowedCommands,
|
|
3534
3689
|
maxSteps: opts.maxSteps ? Number(opts.maxSteps) : void 0,
|
|
3535
|
-
history: []
|
|
3690
|
+
history: [],
|
|
3691
|
+
budget: opts.budget ? Number(opts.budget) : void 0,
|
|
3692
|
+
costUsd: 0
|
|
3536
3693
|
};
|
|
3537
3694
|
const runTask = async (taskText) => {
|
|
3538
3695
|
const active = resolveAgent(config, session.agentName);
|
|
@@ -3555,7 +3712,10 @@ async function run(task, opts) {
|
|
|
3555
3712
|
)
|
|
3556
3713
|
);
|
|
3557
3714
|
}
|
|
3558
|
-
await executeTask(task, resolved2, workspace, session, opts.json ?? false);
|
|
3715
|
+
await executeTask(task, resolved2, workspace, session, opts.json ?? false, opts.verify ?? false);
|
|
3716
|
+
if (session.budget !== void 0 && !opts.json) {
|
|
3717
|
+
console.log(pc8.dim(t("budget.session", { spent: fmtUsd(session.costUsd), budget: fmtUsd(session.budget) })));
|
|
3718
|
+
}
|
|
3559
3719
|
return;
|
|
3560
3720
|
}
|
|
3561
3721
|
const resolved = createProvider(agentConfig);
|
|
@@ -3578,7 +3738,7 @@ async function run(task, opts) {
|
|
|
3578
3738
|
};
|
|
3579
3739
|
await startRepl(ctx);
|
|
3580
3740
|
}
|
|
3581
|
-
async function executeTask(task, resolved, workspace, session, json = false) {
|
|
3741
|
+
async function executeTask(task, resolved, workspace, session, json = false, verify = false) {
|
|
3582
3742
|
const mention = await resolveMentions(task, {
|
|
3583
3743
|
workspace,
|
|
3584
3744
|
allow: session.allow,
|
|
@@ -3592,6 +3752,21 @@ async function executeTask(task, resolved, workspace, session, json = false) {
|
|
|
3592
3752
|
const controller = new AbortController();
|
|
3593
3753
|
const cancel2 = listenForCancel(controller);
|
|
3594
3754
|
const collector = json ? createJsonCollector() : void 0;
|
|
3755
|
+
const pricing = await resolveModelPricing(resolved.config);
|
|
3756
|
+
let budgetHit = false;
|
|
3757
|
+
const baseEvents = collector ? collector.events : renderEvents(spinner3);
|
|
3758
|
+
const events = {
|
|
3759
|
+
...baseEvents,
|
|
3760
|
+
onUsage(u) {
|
|
3761
|
+
baseEvents.onUsage?.(u);
|
|
3762
|
+
if (session.budget !== void 0 && pricing && !controller.signal.aborted) {
|
|
3763
|
+
if (session.costUsd + estimateCost(u, pricing) >= session.budget) {
|
|
3764
|
+
budgetHit = true;
|
|
3765
|
+
controller.abort();
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
};
|
|
3595
3770
|
const permissions = new PermissionEngine({
|
|
3596
3771
|
mode: session.mode,
|
|
3597
3772
|
policy: { workspace, allow: session.allow, deny: session.deny },
|
|
@@ -3605,29 +3780,47 @@ async function executeTask(task, resolved, workspace, session, json = false) {
|
|
|
3605
3780
|
return ok;
|
|
3606
3781
|
}
|
|
3607
3782
|
});
|
|
3783
|
+
const runOnce = (taskText) => runAgent({
|
|
3784
|
+
task: taskText,
|
|
3785
|
+
workspace,
|
|
3786
|
+
agent: resolved,
|
|
3787
|
+
permissions,
|
|
3788
|
+
promptContext: { workspace, mode: session.mode, allow: session.allow },
|
|
3789
|
+
history: session.history,
|
|
3790
|
+
maxSteps: session.maxSteps,
|
|
3791
|
+
signal: controller.signal,
|
|
3792
|
+
events
|
|
3793
|
+
});
|
|
3608
3794
|
if (!json) spinner3.start(t("ui.thinking"));
|
|
3609
3795
|
let result;
|
|
3610
3796
|
try {
|
|
3611
|
-
result = await
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
promptContext: { workspace, mode: session.mode, allow: session.allow },
|
|
3617
|
-
history: session.history,
|
|
3618
|
-
maxSteps: session.maxSteps,
|
|
3619
|
-
signal: controller.signal,
|
|
3620
|
-
events: collector ? collector.events : renderEvents(spinner3)
|
|
3621
|
-
});
|
|
3797
|
+
result = await runOnce(task);
|
|
3798
|
+
session.history = result.messages;
|
|
3799
|
+
if (verify && result.reason === "finished" && !controller.signal.aborted) {
|
|
3800
|
+
result = await runVerification(runOnce, workspace, session, spinner3, json, controller.signal, result);
|
|
3801
|
+
}
|
|
3622
3802
|
} finally {
|
|
3623
3803
|
spinner3.stop();
|
|
3624
3804
|
cancel2.dispose();
|
|
3625
3805
|
}
|
|
3626
|
-
|
|
3806
|
+
const runCost = pricing ? estimateCost(result.usage, pricing) : 0;
|
|
3807
|
+
session.costUsd += runCost;
|
|
3808
|
+
await recordUsage({
|
|
3809
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3810
|
+
agent: resolved.config.name,
|
|
3811
|
+
provider: resolved.config.provider,
|
|
3812
|
+
model: resolved.config.model,
|
|
3813
|
+
promptTokens: result.usage.promptTokens,
|
|
3814
|
+
completionTokens: result.usage.completionTokens,
|
|
3815
|
+
costUsd: runCost
|
|
3816
|
+
});
|
|
3627
3817
|
if (collector) {
|
|
3628
3818
|
process.stdout.write(JSON.stringify(collector.build(result)) + "\n");
|
|
3629
3819
|
return;
|
|
3630
3820
|
}
|
|
3821
|
+
if (budgetHit) {
|
|
3822
|
+
console.log(pc8.yellow("\n" + t("budget.hit", { budget: fmtUsd(session.budget ?? 0) })));
|
|
3823
|
+
}
|
|
3631
3824
|
if (result.reason === "finished") {
|
|
3632
3825
|
console.log(pc8.green("\n" + t("run.done", { steps: result.steps })) + (result.summary ? ` ${result.summary}` : ""));
|
|
3633
3826
|
} else if (result.reason === "cancelled") {
|
|
@@ -3637,15 +3830,13 @@ async function executeTask(task, resolved, workspace, session, json = false) {
|
|
|
3637
3830
|
}
|
|
3638
3831
|
if (result.usage.promptTokens || result.usage.completionTokens) {
|
|
3639
3832
|
const total = result.usage.promptTokens + result.usage.completionTokens;
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
)
|
|
3648
|
-
);
|
|
3833
|
+
const tokensLine = t("ui.tokens", {
|
|
3834
|
+
total: fmtTokens(total),
|
|
3835
|
+
in: fmtTokens(result.usage.promptTokens),
|
|
3836
|
+
out: fmtTokens(result.usage.completionTokens)
|
|
3837
|
+
});
|
|
3838
|
+
const cost = pricing ? ` \xB7 ~${fmtUsd(runCost)}` : "";
|
|
3839
|
+
console.log(pc8.dim("\u21B3 " + tokensLine + cost));
|
|
3649
3840
|
}
|
|
3650
3841
|
}
|
|
3651
3842
|
function fmtTokens(n) {
|
|
@@ -3702,6 +3893,36 @@ async function confirmAction(req) {
|
|
|
3702
3893
|
if (p2.isCancel(answer)) return false;
|
|
3703
3894
|
return answer === true;
|
|
3704
3895
|
}
|
|
3896
|
+
async function runVerification(runOnce, workspace, session, spinner3, json, signal, initial) {
|
|
3897
|
+
const checks = await detectChecks(workspace);
|
|
3898
|
+
if (checks.length === 0) {
|
|
3899
|
+
if (!json) console.log(pc8.dim(t("verify.noChecks")));
|
|
3900
|
+
return initial;
|
|
3901
|
+
}
|
|
3902
|
+
let result = initial;
|
|
3903
|
+
for (let fix = 0; ; fix++) {
|
|
3904
|
+
if (signal.aborted) return result;
|
|
3905
|
+
if (!json) spinner3.start(t("verify.running"));
|
|
3906
|
+
const results = await runChecks(workspace, checks);
|
|
3907
|
+
spinner3.stop();
|
|
3908
|
+
const failed = results.filter((r) => !r.ok);
|
|
3909
|
+
if (failed.length === 0) {
|
|
3910
|
+
if (!json) console.log(pc8.green("\u2713 " + t("verify.passed")));
|
|
3911
|
+
return result;
|
|
3912
|
+
}
|
|
3913
|
+
if (fix >= MAX_VERIFY_FIXES) {
|
|
3914
|
+
if (!json) console.log(pc8.yellow("\u26A0 " + t("verify.giveUp", { n: failed.length })));
|
|
3915
|
+
return result;
|
|
3916
|
+
}
|
|
3917
|
+
if (!json) {
|
|
3918
|
+
console.log(pc8.yellow("\u2717 " + t("verify.failed", { n: failed.length, attempt: fix + 1 })));
|
|
3919
|
+
}
|
|
3920
|
+
if (!json) spinner3.start(t("ui.thinking"));
|
|
3921
|
+
result = await runOnce(buildVerifyFeedback(failed));
|
|
3922
|
+
spinner3.stop();
|
|
3923
|
+
session.history = result.messages;
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3705
3926
|
function renderDiff(hunks) {
|
|
3706
3927
|
for (const h of hunks) {
|
|
3707
3928
|
console.log(pc8.cyan(`@@ -${h.oldStart + 1},${h.oldCount} +${h.newStart + 1},${h.newCount} @@`));
|
|
@@ -3717,8 +3938,8 @@ function renderEvents(spinner3) {
|
|
|
3717
3938
|
onStep() {
|
|
3718
3939
|
spinner3.start(t("ui.thinking"));
|
|
3719
3940
|
},
|
|
3720
|
-
onUsage(
|
|
3721
|
-
const total =
|
|
3941
|
+
onUsage(usage2) {
|
|
3942
|
+
const total = usage2.promptTokens + usage2.completionTokens;
|
|
3722
3943
|
if (total > 0) spinner3.setSuffix(t("ui.tokensShort", { total: fmtTokens(total) }));
|
|
3723
3944
|
},
|
|
3724
3945
|
onAssistantText(text2) {
|
|
@@ -3756,8 +3977,8 @@ async function setup() {
|
|
|
3756
3977
|
import pc9 from "picocolors";
|
|
3757
3978
|
|
|
3758
3979
|
// src/core/scaffold/init.ts
|
|
3759
|
-
import { mkdir as
|
|
3760
|
-
import { dirname as dirname3, join as
|
|
3980
|
+
import { mkdir as mkdir4, writeFile as writeFile4, access } from "fs/promises";
|
|
3981
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
3761
3982
|
|
|
3762
3983
|
// src/core/scaffold/templates.ts
|
|
3763
3984
|
function polyTemplates(locale) {
|
|
@@ -4000,12 +4221,12 @@ async function scaffoldPoly(workspace, opts) {
|
|
|
4000
4221
|
const skipped = [];
|
|
4001
4222
|
for (const [rel, content] of Object.entries(templates)) {
|
|
4002
4223
|
const display = `.poly/${rel}`;
|
|
4003
|
-
const abs =
|
|
4224
|
+
const abs = join6(workspace, ".poly", ...rel.split("/"));
|
|
4004
4225
|
if (!opts.force && await exists(abs)) {
|
|
4005
4226
|
skipped.push(display);
|
|
4006
4227
|
continue;
|
|
4007
4228
|
}
|
|
4008
|
-
await
|
|
4229
|
+
await mkdir4(dirname3(abs), { recursive: true });
|
|
4009
4230
|
await writeFile4(abs, content, "utf8");
|
|
4010
4231
|
created.push(display);
|
|
4011
4232
|
}
|
|
@@ -4099,11 +4320,32 @@ async function resolveOpenRouterKey() {
|
|
|
4099
4320
|
}
|
|
4100
4321
|
}
|
|
4101
4322
|
|
|
4323
|
+
// src/cli/commands/usage.ts
|
|
4324
|
+
import pc11 from "picocolors";
|
|
4325
|
+
async function usage() {
|
|
4326
|
+
const { days, total } = await aggregateUsage();
|
|
4327
|
+
if (days.length === 0) {
|
|
4328
|
+
console.log(pc11.yellow(t("usage.empty")));
|
|
4329
|
+
return;
|
|
4330
|
+
}
|
|
4331
|
+
console.log(pc11.bold(t("usage.header")));
|
|
4332
|
+
for (const d of days) console.log(" " + formatRow(d));
|
|
4333
|
+
console.log(pc11.dim(" " + "\u2500".repeat(40)));
|
|
4334
|
+
console.log(" " + pc11.bold(formatRow({ ...total, date: t("usage.total") })));
|
|
4335
|
+
}
|
|
4336
|
+
function formatRow(b) {
|
|
4337
|
+
const tokens2 = fmtTokens2(b.promptTokens + b.completionTokens);
|
|
4338
|
+
return `${b.date.padEnd(12)} ${tokens2.padStart(7)} tok ${fmtUsd(b.costUsd).padStart(10)} (${b.runs} ${t("usage.runs")})`;
|
|
4339
|
+
}
|
|
4340
|
+
function fmtTokens2(n) {
|
|
4341
|
+
return n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n);
|
|
4342
|
+
}
|
|
4343
|
+
|
|
4102
4344
|
// src/cli/commands/prd.ts
|
|
4103
|
-
import { writeFile as writeFile5, readFile as
|
|
4345
|
+
import { writeFile as writeFile5, readFile as readFile11 } from "fs/promises";
|
|
4104
4346
|
import { execFile } from "child_process";
|
|
4105
|
-
import { promisify as
|
|
4106
|
-
import
|
|
4347
|
+
import { promisify as promisify3 } from "util";
|
|
4348
|
+
import pc12 from "picocolors";
|
|
4107
4349
|
|
|
4108
4350
|
// src/core/agent/prd.ts
|
|
4109
4351
|
var SYSTEM = [
|
|
@@ -4192,13 +4434,13 @@ async function withRetry(fn, opts = {}) {
|
|
|
4192
4434
|
|
|
4193
4435
|
// src/cli/commands/cli-io.ts
|
|
4194
4436
|
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
4195
|
-
import { resolve as
|
|
4437
|
+
import { resolve as resolve11 } from "path";
|
|
4196
4438
|
var GUIDE_MAX = 12e3;
|
|
4197
4439
|
function readProjectGuide(files) {
|
|
4198
4440
|
const parts = [];
|
|
4199
4441
|
for (const file of files) {
|
|
4200
4442
|
try {
|
|
4201
|
-
const path =
|
|
4443
|
+
const path = resolve11(process.cwd(), file);
|
|
4202
4444
|
if (existsSync2(path)) parts.push(`# ${file}
|
|
4203
4445
|
${readFileSync(path, "utf8").trim()}`);
|
|
4204
4446
|
} catch {
|
|
@@ -4224,7 +4466,7 @@ function stripBom(s) {
|
|
|
4224
4466
|
}
|
|
4225
4467
|
|
|
4226
4468
|
// src/cli/commands/prd.ts
|
|
4227
|
-
var
|
|
4469
|
+
var exec3 = promisify3(execFile);
|
|
4228
4470
|
async function prd(issueRef, opts) {
|
|
4229
4471
|
const issue = await loadIssue(issueRef, opts.input);
|
|
4230
4472
|
const { provider } = resolveFreeProvider(opts.model ?? DEFAULT_PRD_MODEL);
|
|
@@ -4232,18 +4474,18 @@ async function prd(issueRef, opts) {
|
|
|
4232
4474
|
const markdown = await withRetry(() => generatePrd(issue, provider, guide));
|
|
4233
4475
|
if (opts.out) {
|
|
4234
4476
|
await writeFile5(opts.out, markdown + "\n", "utf8");
|
|
4235
|
-
console.error(
|
|
4477
|
+
console.error(pc12.green(t("prd.wrote", { path: opts.out })));
|
|
4236
4478
|
} else {
|
|
4237
4479
|
process.stdout.write(markdown + "\n");
|
|
4238
4480
|
}
|
|
4239
4481
|
}
|
|
4240
4482
|
async function loadIssue(issueRef, input) {
|
|
4241
4483
|
if (input) {
|
|
4242
|
-
const raw = input === "-" ? await readStdin() : await
|
|
4484
|
+
const raw = input === "-" ? await readStdin() : await readFile11(input, "utf8");
|
|
4243
4485
|
return normalize2(JSON.parse(stripBom(raw)));
|
|
4244
4486
|
}
|
|
4245
4487
|
const num = numericRef(issueRef);
|
|
4246
|
-
const { stdout: stdout2 } = await
|
|
4488
|
+
const { stdout: stdout2 } = await exec3("gh", ["issue", "view", num, "--json", "number,title,body,comments"]);
|
|
4247
4489
|
const data = normalize2(JSON.parse(stdout2));
|
|
4248
4490
|
data.number ??= Number(num);
|
|
4249
4491
|
return data;
|
|
@@ -4258,10 +4500,10 @@ function normalize2(raw) {
|
|
|
4258
4500
|
}
|
|
4259
4501
|
|
|
4260
4502
|
// src/cli/commands/review.ts
|
|
4261
|
-
import { writeFile as writeFile6, readFile as
|
|
4503
|
+
import { writeFile as writeFile6, readFile as readFile12 } from "fs/promises";
|
|
4262
4504
|
import { execFile as execFile2 } from "child_process";
|
|
4263
|
-
import { promisify as
|
|
4264
|
-
import
|
|
4505
|
+
import { promisify as promisify4 } from "util";
|
|
4506
|
+
import pc13 from "picocolors";
|
|
4265
4507
|
|
|
4266
4508
|
// src/core/agent/review.ts
|
|
4267
4509
|
var MAX_DIFF_CHARS = Number(process.env.POLYPUS_MAX_DIFF_CHARS) || 6e4;
|
|
@@ -4318,7 +4560,7 @@ ${projectGuide}`
|
|
|
4318
4560
|
}
|
|
4319
4561
|
|
|
4320
4562
|
// src/cli/commands/review.ts
|
|
4321
|
-
var
|
|
4563
|
+
var exec4 = promisify4(execFile2);
|
|
4322
4564
|
async function review(prRef, opts) {
|
|
4323
4565
|
const num = opts.input ? prRef.replace(/^#/, "") : numericRef(prRef);
|
|
4324
4566
|
const diff = await loadDiff(num, opts.input);
|
|
@@ -4328,25 +4570,25 @@ async function review(prRef, opts) {
|
|
|
4328
4570
|
const markdown = await withRetry(() => reviewDiff(diff, meta, provider, guide));
|
|
4329
4571
|
if (opts.out) {
|
|
4330
4572
|
await writeFile6(opts.out, markdown + "\n", "utf8");
|
|
4331
|
-
console.error(
|
|
4573
|
+
console.error(pc13.green(t("review.wrote", { path: opts.out })));
|
|
4332
4574
|
} else {
|
|
4333
4575
|
process.stdout.write(markdown + "\n");
|
|
4334
4576
|
}
|
|
4335
4577
|
}
|
|
4336
4578
|
async function loadDiff(num, input) {
|
|
4337
|
-
if (input) return input === "-" ? readStdin() :
|
|
4338
|
-
const { stdout: stdout2 } = await
|
|
4579
|
+
if (input) return input === "-" ? readStdin() : readFile12(input, "utf8");
|
|
4580
|
+
const { stdout: stdout2 } = await exec4("gh", ["pr", "diff", num]);
|
|
4339
4581
|
return stdout2;
|
|
4340
4582
|
}
|
|
4341
4583
|
async function loadMeta(num, input) {
|
|
4342
4584
|
if (input) return { number: Number(num) || void 0, title: `PR ${num}`, body: "" };
|
|
4343
|
-
const { stdout: stdout2 } = await
|
|
4585
|
+
const { stdout: stdout2 } = await exec4("gh", ["pr", "view", num, "--json", "number,title,body"]);
|
|
4344
4586
|
const raw = JSON.parse(stdout2);
|
|
4345
4587
|
return { number: raw.number, title: raw.title ?? "", body: raw.body ?? "" };
|
|
4346
4588
|
}
|
|
4347
4589
|
|
|
4348
4590
|
// src/cli/index.ts
|
|
4349
|
-
import { join as
|
|
4591
|
+
import { join as join7 } from "path";
|
|
4350
4592
|
|
|
4351
4593
|
// src/core/config/dotenv.ts
|
|
4352
4594
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
@@ -4379,7 +4621,7 @@ async function launchInteractive() {
|
|
|
4379
4621
|
const config = await loadConfig();
|
|
4380
4622
|
if (config.agents.length === 0) {
|
|
4381
4623
|
console.log(banner());
|
|
4382
|
-
console.log(" " +
|
|
4624
|
+
console.log(" " + pc14.yellow(t("welcome.firstRun")) + "\n");
|
|
4383
4625
|
await setup();
|
|
4384
4626
|
}
|
|
4385
4627
|
await run(void 0, {});
|
|
@@ -4406,20 +4648,21 @@ function buildProgram() {
|
|
|
4406
4648
|
program.command("add-agent").argument("<name>", t("cli.arg.addAgentName")).requiredOption("--provider <provider>", t("cli.opt.provider")).requiredOption("--model <model>", t("cli.opt.model")).option("--api-key <key>", t("cli.opt.apiKey")).option("--base-url <url>", t("cli.opt.baseUrl")).option("--tool-mode <mode>", t("cli.opt.toolMode"), "auto").option("--set-default", t("cli.opt.setDefault")).description(t("cli.cmd.addAgent")).action((name, opts) => addAgent(name, opts));
|
|
4407
4649
|
program.command("remove-agent").argument("<name>", t("cli.arg.removeAgentName")).description(t("cli.cmd.removeAgent")).action((name) => removeAgent(name));
|
|
4408
4650
|
program.command("list-agents").alias("agents").description(t("cli.cmd.listAgents")).action(() => listAgents());
|
|
4409
|
-
program.command("run").argument("[task]", t("cli.arg.runTask")).option("--agent <name>", t("cli.opt.agent")).option("--mode <mode>", t("cli.opt.mode")).option("--max-steps <n>", t("cli.opt.maxSteps")).option("--json", t("cli.opt.json")).description(t("cli.cmd.run")).action((task, opts) => run(task, opts));
|
|
4651
|
+
program.command("run").argument("[task]", t("cli.arg.runTask")).option("--agent <name>", t("cli.opt.agent")).option("--mode <mode>", t("cli.opt.mode")).option("--max-steps <n>", t("cli.opt.maxSteps")).option("--json", t("cli.opt.json")).option("--verify", t("cli.opt.verify")).option("--budget <usd>", t("cli.opt.budget")).description(t("cli.cmd.run")).action((task, opts) => run(task, opts));
|
|
4410
4652
|
program.command("swarm").argument("<task>", t("cli.arg.swarmTask")).option("--agents <names>", t("cli.opt.agents")).option("--max-subtasks <n>", t("cli.opt.maxSubtasks")).description(t("cli.cmd.swarm")).action((task, opts) => swarm(task, opts));
|
|
4411
4653
|
program.command("models").option("--search <text>", t("cli.opt.search")).option("--tools", t("cli.opt.toolsOnly")).option("--free", t("cli.opt.free")).option("--max-price <usd>", t("cli.opt.maxPrice")).option("--sort <order>", t("cli.opt.sort")).option("--limit <n>", t("cli.opt.limit")).description(t("cli.cmd.models")).action((opts) => models(opts));
|
|
4654
|
+
program.command("usage").description(t("cli.cmd.usage")).action(() => usage());
|
|
4412
4655
|
program.command("prd").argument("<issue>", t("cli.arg.prdIssue")).option("--out <file>", t("cli.opt.out")).option("--model <model>", t("cli.opt.model")).option("--input <file>", t("cli.opt.input")).description(t("cli.cmd.prd")).action((issue, opts) => prd(issue, opts));
|
|
4413
4656
|
program.command("review").argument("<pr>", t("cli.arg.reviewPr")).option("--out <file>", t("cli.opt.out")).option("--model <model>", t("cli.opt.model")).option("--input <file>", t("cli.opt.input")).description(t("cli.cmd.review")).action((pr, opts) => review(pr, opts));
|
|
4414
4657
|
return program;
|
|
4415
4658
|
}
|
|
4416
4659
|
async function main() {
|
|
4417
4660
|
try {
|
|
4418
|
-
loadDotenv([
|
|
4661
|
+
loadDotenv([join7(configDir(), ".env"), join7(process.cwd(), ".env")]);
|
|
4419
4662
|
await resolveLocale();
|
|
4420
4663
|
await buildProgram().parseAsync(process.argv);
|
|
4421
4664
|
} catch (err) {
|
|
4422
|
-
console.error(
|
|
4665
|
+
console.error(pc14.red(`\u2717 ${err.message}`));
|
|
4423
4666
|
process.exitCode = 1;
|
|
4424
4667
|
}
|
|
4425
4668
|
}
|