@gaberrb/polypus 0.4.7 → 0.4.9
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 +437 -126
- 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 pc15 from "picocolors";
|
|
6
6
|
|
|
7
7
|
// src/cli/commands/add-agent.ts
|
|
8
8
|
import pc from "picocolors";
|
|
@@ -111,6 +111,11 @@ var en = {
|
|
|
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
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",
|
|
116
|
+
"cli.cmd.sessions": "List saved sessions that can be resumed",
|
|
117
|
+
"cli.opt.continue": "resume the most recent saved session",
|
|
118
|
+
"cli.opt.resume": "resume a specific saved session by id",
|
|
114
119
|
"cli.arg.swarmTask": "high-level task to split across agents",
|
|
115
120
|
"cli.opt.agents": "comma-separated agent names (default: all configured)",
|
|
116
121
|
"cli.opt.maxSubtasks": "maximum number of parallel subtasks",
|
|
@@ -147,6 +152,20 @@ var en = {
|
|
|
147
152
|
"verify.passed": "verification passed",
|
|
148
153
|
"verify.failed": "{n} check(s) failed \u2014 handing the output back to the agent (attempt {attempt})",
|
|
149
154
|
"verify.giveUp": "{n} check(s) still failing after the retry budget \u2014 stopping",
|
|
155
|
+
"budget.session": "session spend: {spent} / budget {budget}",
|
|
156
|
+
"budget.hit": "\u25A0 stopped: estimated cost reached the budget of {budget}",
|
|
157
|
+
// usage analytics
|
|
158
|
+
"usage.header": "Usage (tokens / estimated cost) per day:",
|
|
159
|
+
"usage.empty": "No usage recorded yet. Run a task to start tracking.",
|
|
160
|
+
"usage.total": "total",
|
|
161
|
+
"usage.runs": "runs",
|
|
162
|
+
// sessions
|
|
163
|
+
"sessions.header": "Saved sessions (most recent first):",
|
|
164
|
+
"sessions.empty": "No saved sessions yet.",
|
|
165
|
+
"sessions.hint": "Resume with `polypus run --continue` or `polypus run --resume <id>`.",
|
|
166
|
+
"sessions.notFound": 'No saved session with id "{id}".',
|
|
167
|
+
"sessions.noneToContinue": "No previous session to continue \u2014 starting fresh.",
|
|
168
|
+
"sessions.resumed": "\u21BA resumed session {id} ({n} messages)",
|
|
150
169
|
// repl
|
|
151
170
|
"repl.welcome": "Polypus interactive session.",
|
|
152
171
|
"repl.welcomeHint": " Type /help for commands, /exit to quit.",
|
|
@@ -173,6 +192,8 @@ var en = {
|
|
|
173
192
|
" /allow <glob> add a path glob to the allow-list",
|
|
174
193
|
" /allow show the current allow-list and mode",
|
|
175
194
|
" /reset clear the conversation history",
|
|
195
|
+
" /sessions list saved sessions you can resume",
|
|
196
|
+
" /resume <id> resume a saved session",
|
|
176
197
|
" /help show this help",
|
|
177
198
|
" /exit quit",
|
|
178
199
|
"Anything else is sent to the agent as a task."
|
|
@@ -351,6 +372,11 @@ var ptBR = {
|
|
|
351
372
|
"cli.opt.maxSteps": "n\xFAmero m\xE1ximo de passos do agente",
|
|
352
373
|
"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",
|
|
353
374
|
"cli.opt.verify": "ap\xF3s o agente terminar, roda as checagens do projeto (typecheck/build/test) e itera at\xE9 passar",
|
|
375
|
+
"cli.opt.budget": "interrompe a execu\xE7\xE3o quando o custo estimado da sess\xE3o atingir este valor em USD (pre\xE7os do OpenRouter)",
|
|
376
|
+
"cli.cmd.usage": "Mostra analytics de tokens/custo agregados por dia",
|
|
377
|
+
"cli.cmd.sessions": "Lista as sess\xF5es salvas que podem ser retomadas",
|
|
378
|
+
"cli.opt.continue": "retoma a sess\xE3o salva mais recente",
|
|
379
|
+
"cli.opt.resume": "retoma uma sess\xE3o salva espec\xEDfica pelo id",
|
|
354
380
|
"cli.arg.swarmTask": "tarefa de alto n\xEDvel para dividir entre os agentes",
|
|
355
381
|
"cli.opt.agents": "nomes de agentes separados por v\xEDrgula (padr\xE3o: todos)",
|
|
356
382
|
"cli.opt.maxSubtasks": "n\xFAmero m\xE1ximo de subtarefas paralelas",
|
|
@@ -385,6 +411,20 @@ var ptBR = {
|
|
|
385
411
|
"verify.passed": "verifica\xE7\xE3o passou",
|
|
386
412
|
"verify.failed": "{n} checagem(ns) falharam \u2014 devolvendo a sa\xEDda ao agente (tentativa {attempt})",
|
|
387
413
|
"verify.giveUp": "{n} checagem(ns) ainda falhando ap\xF3s o limite de tentativas \u2014 parando",
|
|
414
|
+
"budget.session": "gasto da sess\xE3o: {spent} / or\xE7amento {budget}",
|
|
415
|
+
"budget.hit": "\u25A0 interrompido: o custo estimado atingiu o or\xE7amento de {budget}",
|
|
416
|
+
// usage analytics
|
|
417
|
+
"usage.header": "Uso (tokens / custo estimado) por dia:",
|
|
418
|
+
"usage.empty": "Nenhum uso registrado ainda. Rode uma tarefa para come\xE7ar a medir.",
|
|
419
|
+
"usage.total": "total",
|
|
420
|
+
"usage.runs": "execu\xE7\xF5es",
|
|
421
|
+
// sessions
|
|
422
|
+
"sessions.header": "Sess\xF5es salvas (mais recentes primeiro):",
|
|
423
|
+
"sessions.empty": "Nenhuma sess\xE3o salva ainda.",
|
|
424
|
+
"sessions.hint": "Retome com `polypus run --continue` ou `polypus run --resume <id>`.",
|
|
425
|
+
"sessions.notFound": 'Nenhuma sess\xE3o salva com id "{id}".',
|
|
426
|
+
"sessions.noneToContinue": "Nenhuma sess\xE3o anterior para continuar \u2014 come\xE7ando do zero.",
|
|
427
|
+
"sessions.resumed": "\u21BA sess\xE3o {id} retomada ({n} mensagens)",
|
|
388
428
|
"repl.welcome": "Sess\xE3o interativa do Polypus.",
|
|
389
429
|
"repl.welcomeHint": " Digite /help para comandos, /exit para sair.",
|
|
390
430
|
"repl.modeChanged": "modo \u2192 {mode}",
|
|
@@ -410,6 +450,8 @@ var ptBR = {
|
|
|
410
450
|
" /allow <glob> adiciona um glob de caminho \xE0 allow-list",
|
|
411
451
|
" /allow mostra a allow-list e o modo atuais",
|
|
412
452
|
" /reset limpa o hist\xF3rico da conversa",
|
|
453
|
+
" /sessions lista as sess\xF5es salvas que voc\xEA pode retomar",
|
|
454
|
+
" /resume <id> retoma uma sess\xE3o salva",
|
|
413
455
|
" /help mostra esta ajuda",
|
|
414
456
|
" /exit sair",
|
|
415
457
|
"Qualquer outra coisa \xE9 enviada ao agente como tarefa."
|
|
@@ -1068,6 +1110,13 @@ var SECRET_PATTERNS = [
|
|
|
1068
1110
|
{ re: /\bsk-[A-Za-z0-9]{32,}\b/, kind: "OpenAI-style secret key" },
|
|
1069
1111
|
{ re: /\bAIza[0-9A-Za-z_-]{35}\b/, kind: "Google API key" }
|
|
1070
1112
|
];
|
|
1113
|
+
function redactSecrets(text2) {
|
|
1114
|
+
let out = text2;
|
|
1115
|
+
for (const { re } of SECRET_PATTERNS) {
|
|
1116
|
+
out = out.replace(new RegExp(re.source, re.flags.includes("g") ? re.flags : re.flags + "g"), "[redacted]");
|
|
1117
|
+
}
|
|
1118
|
+
return out;
|
|
1119
|
+
}
|
|
1071
1120
|
function scanSecrets(text2) {
|
|
1072
1121
|
const findings = [];
|
|
1073
1122
|
const lines = text2.split("\n");
|
|
@@ -2096,9 +2145,9 @@ async function runAgent(opts) {
|
|
|
2096
2145
|
let failStreak = 0;
|
|
2097
2146
|
const maxToolRetries = opts.maxToolRetries ?? 3;
|
|
2098
2147
|
const autoCorrect = opts.autoCorrect ?? true;
|
|
2099
|
-
const
|
|
2148
|
+
const usage2 = { promptTokens: 0, completionTokens: 0 };
|
|
2100
2149
|
for (let step = 1; step <= maxSteps; step++) {
|
|
2101
|
-
if (opts.signal?.aborted) return { finished: false, reason: "cancelled", steps: step - 1, messages, usage };
|
|
2150
|
+
if (opts.signal?.aborted) return { finished: false, reason: "cancelled", steps: step - 1, messages, usage: usage2 };
|
|
2102
2151
|
events?.onStep?.(step);
|
|
2103
2152
|
let response;
|
|
2104
2153
|
try {
|
|
@@ -2109,12 +2158,12 @@ async function runAgent(opts) {
|
|
|
2109
2158
|
signal: opts.signal
|
|
2110
2159
|
});
|
|
2111
2160
|
} catch (err) {
|
|
2112
|
-
if (opts.signal?.aborted) return { finished: false, reason: "cancelled", steps: step, messages, usage };
|
|
2161
|
+
if (opts.signal?.aborted) return { finished: false, reason: "cancelled", steps: step, messages, usage: usage2 };
|
|
2113
2162
|
throw err;
|
|
2114
2163
|
}
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
events?.onUsage?.(
|
|
2164
|
+
usage2.promptTokens += response.usage?.promptTokens ?? 0;
|
|
2165
|
+
usage2.completionTokens += response.usage?.completionTokens ?? 0;
|
|
2166
|
+
events?.onUsage?.(usage2);
|
|
2118
2167
|
const { toolCalls, text: text2 } = driver.parse(response);
|
|
2119
2168
|
messages.push(driver.assistantMessage(response, toolCalls));
|
|
2120
2169
|
if (text2) events?.onAssistantText?.(text2);
|
|
@@ -2128,7 +2177,7 @@ async function runAgent(opts) {
|
|
|
2128
2177
|
messages.push({ role: "user", content: guidance });
|
|
2129
2178
|
continue;
|
|
2130
2179
|
}
|
|
2131
|
-
return { finished: false, reason: "stalled", steps: step, messages, usage };
|
|
2180
|
+
return { finished: false, reason: "stalled", steps: step, messages, usage: usage2 };
|
|
2132
2181
|
}
|
|
2133
2182
|
const stalled = text2.trim().length === 0 || looksLikeStall(text2);
|
|
2134
2183
|
if (stalled) {
|
|
@@ -2138,17 +2187,17 @@ async function runAgent(opts) {
|
|
|
2138
2187
|
messages.push(driver.repromptMessage());
|
|
2139
2188
|
continue;
|
|
2140
2189
|
}
|
|
2141
|
-
return { finished: false, reason: "stalled", steps: step, messages, usage };
|
|
2190
|
+
return { finished: false, reason: "stalled", steps: step, messages, usage: usage2 };
|
|
2142
2191
|
}
|
|
2143
|
-
return { finished: false, reason: "reply", steps: step, messages, usage };
|
|
2192
|
+
return { finished: false, reason: "reply", steps: step, messages, usage: usage2 };
|
|
2144
2193
|
}
|
|
2145
2194
|
consecutiveNoTool = 0;
|
|
2146
2195
|
for (const call of toolCalls) {
|
|
2147
|
-
if (opts.signal?.aborted) return { finished: false, reason: "cancelled", steps: step, messages, usage };
|
|
2196
|
+
if (opts.signal?.aborted) return { finished: false, reason: "cancelled", steps: step, messages, usage: usage2 };
|
|
2148
2197
|
events?.onToolCall?.(call);
|
|
2149
2198
|
if (call.name === "finish") {
|
|
2150
2199
|
const summary = String(call.arguments.summary ?? "").trim();
|
|
2151
|
-
return { finished: true, reason: "finished", summary, steps: step, messages, usage };
|
|
2200
|
+
return { finished: true, reason: "finished", summary, steps: step, messages, usage: usage2 };
|
|
2152
2201
|
}
|
|
2153
2202
|
const tool = getTool(call.name);
|
|
2154
2203
|
const result = tool ? await tool.run(call.arguments, ctx) : { ok: false, output: `Unknown tool "${call.name}". Available: ${toolSpecs().map((t2) => t2.name).join(", ")}` };
|
|
@@ -2186,12 +2235,12 @@ ${guidance}`;
|
|
|
2186
2235
|
failStreak = sig === lastFailSig ? failStreak + 1 : 1;
|
|
2187
2236
|
lastFailSig = sig;
|
|
2188
2237
|
if (failStreak >= maxToolRetries) {
|
|
2189
|
-
return { finished: false, reason: "stalled", steps: step, messages, usage };
|
|
2238
|
+
return { finished: false, reason: "stalled", steps: step, messages, usage: usage2 };
|
|
2190
2239
|
}
|
|
2191
2240
|
}
|
|
2192
2241
|
}
|
|
2193
2242
|
}
|
|
2194
|
-
return { finished: false, reason: "maxsteps", steps: maxSteps, messages, usage };
|
|
2243
|
+
return { finished: false, reason: "maxsteps", steps: maxSteps, messages, usage: usage2 };
|
|
2195
2244
|
}
|
|
2196
2245
|
|
|
2197
2246
|
// src/core/context/mentions.ts
|
|
@@ -2296,6 +2345,216 @@ function clamp2(s) {
|
|
|
2296
2345
|
return s.length > MAX_OUTPUT3 ? s.slice(-MAX_OUTPUT3) : s;
|
|
2297
2346
|
}
|
|
2298
2347
|
|
|
2348
|
+
// src/core/agent/usage.ts
|
|
2349
|
+
import { appendFile, mkdir as mkdir3, readFile as readFile10 } from "fs/promises";
|
|
2350
|
+
import { join as join4 } from "path";
|
|
2351
|
+
|
|
2352
|
+
// src/core/providers/openrouter.ts
|
|
2353
|
+
var MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
2354
|
+
async function listOpenRouterModels(apiKey, timeoutMs = 8e3) {
|
|
2355
|
+
const controller = new AbortController();
|
|
2356
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2357
|
+
try {
|
|
2358
|
+
const res = await fetch(MODELS_URL, {
|
|
2359
|
+
signal: controller.signal,
|
|
2360
|
+
headers: apiKey ? { authorization: `Bearer ${apiKey}` } : {}
|
|
2361
|
+
});
|
|
2362
|
+
if (!res.ok) throw new Error(`OpenRouter ${res.status}: ${await res.text().catch(() => "")}`);
|
|
2363
|
+
const data = await res.json();
|
|
2364
|
+
return (data.data ?? []).map(normalize).filter((m) => m.id.length > 0);
|
|
2365
|
+
} finally {
|
|
2366
|
+
clearTimeout(timer);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
function normalize(m) {
|
|
2370
|
+
const promptPrice = toPerMillion(m.pricing?.prompt);
|
|
2371
|
+
const completionPrice = toPerMillion(m.pricing?.completion);
|
|
2372
|
+
return {
|
|
2373
|
+
id: m.id ?? "",
|
|
2374
|
+
name: m.name ?? m.id ?? "",
|
|
2375
|
+
promptPrice,
|
|
2376
|
+
completionPrice,
|
|
2377
|
+
contextLength: m.context_length ?? 0,
|
|
2378
|
+
supportsTools: (m.supported_parameters ?? []).includes("tools"),
|
|
2379
|
+
free: promptPrice === 0 && completionPrice === 0
|
|
2380
|
+
};
|
|
2381
|
+
}
|
|
2382
|
+
function toPerMillion(price) {
|
|
2383
|
+
const n = Number(price ?? "0");
|
|
2384
|
+
return Number.isFinite(n) ? n * 1e6 : 0;
|
|
2385
|
+
}
|
|
2386
|
+
function filterModels(models2, f) {
|
|
2387
|
+
const term = f.search?.trim().toLowerCase();
|
|
2388
|
+
let out = models2.filter((m) => {
|
|
2389
|
+
if (term && !m.id.toLowerCase().includes(term) && !m.name.toLowerCase().includes(term)) {
|
|
2390
|
+
return false;
|
|
2391
|
+
}
|
|
2392
|
+
if (f.tools === "tools" && !m.supportsTools) return false;
|
|
2393
|
+
if (f.tools === "no-tools" && m.supportsTools) return false;
|
|
2394
|
+
if (f.freeOnly && !m.free) return false;
|
|
2395
|
+
if (f.maxPrice !== void 0 && (m.promptPrice < 0 || m.promptPrice > f.maxPrice)) return false;
|
|
2396
|
+
return true;
|
|
2397
|
+
});
|
|
2398
|
+
const key = (m) => m.promptPrice < 0 ? Number.POSITIVE_INFINITY : m.promptPrice;
|
|
2399
|
+
const sort = f.sort ?? "price";
|
|
2400
|
+
out = out.sort((a, b) => {
|
|
2401
|
+
switch (sort) {
|
|
2402
|
+
case "price-desc":
|
|
2403
|
+
return key(b) - key(a);
|
|
2404
|
+
case "context":
|
|
2405
|
+
return b.contextLength - a.contextLength;
|
|
2406
|
+
case "name":
|
|
2407
|
+
return a.id.localeCompare(b.id);
|
|
2408
|
+
default:
|
|
2409
|
+
return key(a) - key(b);
|
|
2410
|
+
}
|
|
2411
|
+
});
|
|
2412
|
+
return out;
|
|
2413
|
+
}
|
|
2414
|
+
function fmtPrice(perMillion) {
|
|
2415
|
+
if (perMillion < 0) return "var";
|
|
2416
|
+
if (perMillion === 0) return "free";
|
|
2417
|
+
if (perMillion < 1) return `$${perMillion.toFixed(2)}`;
|
|
2418
|
+
if (perMillion < 100) return `$${perMillion.toFixed(perMillion % 1 ? 1 : 0)}`;
|
|
2419
|
+
return `$${Math.round(perMillion)}`;
|
|
2420
|
+
}
|
|
2421
|
+
function fmtContext(n) {
|
|
2422
|
+
if (n >= 1e6) return `${Math.round(n / 1e5) / 10}M`;
|
|
2423
|
+
if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
|
|
2424
|
+
return String(n);
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
// src/core/agent/usage.ts
|
|
2428
|
+
var catalogCache;
|
|
2429
|
+
async function resolveModelPricing(agent) {
|
|
2430
|
+
if (agent.provider !== "openrouter") return void 0;
|
|
2431
|
+
try {
|
|
2432
|
+
catalogCache ??= listOpenRouterModels(resolveSecret(agent.apiKey));
|
|
2433
|
+
const models2 = await catalogCache;
|
|
2434
|
+
const m = models2.find((x) => x.id === agent.model);
|
|
2435
|
+
if (!m || m.promptPrice < 0 || m.completionPrice < 0) return void 0;
|
|
2436
|
+
return { promptPrice: m.promptPrice, completionPrice: m.completionPrice };
|
|
2437
|
+
} catch {
|
|
2438
|
+
return void 0;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
function estimateCost(usage2, pricing) {
|
|
2442
|
+
return usage2.promptTokens / 1e6 * pricing.promptPrice + usage2.completionTokens / 1e6 * pricing.completionPrice;
|
|
2443
|
+
}
|
|
2444
|
+
function fmtUsd(n) {
|
|
2445
|
+
if (n <= 0) return "US$0.00";
|
|
2446
|
+
if (n < 0.01) return `US$${n.toFixed(4)}`;
|
|
2447
|
+
return `US$${n.toFixed(2)}`;
|
|
2448
|
+
}
|
|
2449
|
+
function usagePath() {
|
|
2450
|
+
return join4(configDir(), "usage.jsonl");
|
|
2451
|
+
}
|
|
2452
|
+
async function recordUsage(entry) {
|
|
2453
|
+
try {
|
|
2454
|
+
await mkdir3(configDir(), { recursive: true });
|
|
2455
|
+
await appendFile(usagePath(), JSON.stringify(entry) + "\n", "utf8");
|
|
2456
|
+
} catch {
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
async function aggregateUsage() {
|
|
2460
|
+
let text2 = "";
|
|
2461
|
+
try {
|
|
2462
|
+
text2 = await readFile10(usagePath(), "utf8");
|
|
2463
|
+
} catch {
|
|
2464
|
+
return { days: [], total: emptyBucket("total") };
|
|
2465
|
+
}
|
|
2466
|
+
const byDay = /* @__PURE__ */ new Map();
|
|
2467
|
+
const total = emptyBucket("total");
|
|
2468
|
+
for (const line of text2.split("\n")) {
|
|
2469
|
+
if (!line.trim()) continue;
|
|
2470
|
+
let e;
|
|
2471
|
+
try {
|
|
2472
|
+
e = JSON.parse(line);
|
|
2473
|
+
} catch {
|
|
2474
|
+
continue;
|
|
2475
|
+
}
|
|
2476
|
+
const date = (e.ts ?? "").slice(0, 10) || "unknown";
|
|
2477
|
+
const bucket = byDay.get(date) ?? emptyBucket(date);
|
|
2478
|
+
accumulate(bucket, e);
|
|
2479
|
+
byDay.set(date, bucket);
|
|
2480
|
+
accumulate(total, e);
|
|
2481
|
+
}
|
|
2482
|
+
const days = [...byDay.values()].sort((a, b) => a.date.localeCompare(b.date));
|
|
2483
|
+
return { days, total };
|
|
2484
|
+
}
|
|
2485
|
+
function emptyBucket(date) {
|
|
2486
|
+
return { date, promptTokens: 0, completionTokens: 0, costUsd: 0, runs: 0 };
|
|
2487
|
+
}
|
|
2488
|
+
function accumulate(bucket, e) {
|
|
2489
|
+
bucket.promptTokens += e.promptTokens ?? 0;
|
|
2490
|
+
bucket.completionTokens += e.completionTokens ?? 0;
|
|
2491
|
+
bucket.costUsd += e.costUsd ?? 0;
|
|
2492
|
+
bucket.runs += 1;
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
// src/core/agent/session-store.ts
|
|
2496
|
+
import { mkdir as mkdir4, readFile as readFile11, readdir as readdir5, writeFile as writeFile4 } from "fs/promises";
|
|
2497
|
+
import { join as join5 } from "path";
|
|
2498
|
+
function sessionsDir() {
|
|
2499
|
+
return join5(configDir(), "sessions");
|
|
2500
|
+
}
|
|
2501
|
+
function newSessionId() {
|
|
2502
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2503
|
+
const rand = Math.random().toString(36).slice(2, 6);
|
|
2504
|
+
return `${stamp}-${rand}`;
|
|
2505
|
+
}
|
|
2506
|
+
function sessionPath(id) {
|
|
2507
|
+
return join5(sessionsDir(), `${id}.json`);
|
|
2508
|
+
}
|
|
2509
|
+
async function saveSession(record) {
|
|
2510
|
+
await mkdir4(sessionsDir(), { recursive: true });
|
|
2511
|
+
const safe = {
|
|
2512
|
+
...record,
|
|
2513
|
+
messages: record.messages.map((m) => ({ ...m, content: redactSecrets(m.content) }))
|
|
2514
|
+
};
|
|
2515
|
+
await writeFile4(sessionPath(record.id), JSON.stringify(safe, null, 2) + "\n", "utf8");
|
|
2516
|
+
}
|
|
2517
|
+
async function loadSession(id) {
|
|
2518
|
+
try {
|
|
2519
|
+
return JSON.parse(await readFile11(sessionPath(id), "utf8"));
|
|
2520
|
+
} catch {
|
|
2521
|
+
return void 0;
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
async function listSessions() {
|
|
2525
|
+
let files;
|
|
2526
|
+
try {
|
|
2527
|
+
files = (await readdir5(sessionsDir())).filter((f) => f.endsWith(".json"));
|
|
2528
|
+
} catch {
|
|
2529
|
+
return [];
|
|
2530
|
+
}
|
|
2531
|
+
const summaries = [];
|
|
2532
|
+
for (const f of files) {
|
|
2533
|
+
try {
|
|
2534
|
+
const r = JSON.parse(await readFile11(join5(sessionsDir(), f), "utf8"));
|
|
2535
|
+
summaries.push({
|
|
2536
|
+
id: r.id,
|
|
2537
|
+
updatedAt: r.updatedAt,
|
|
2538
|
+
title: r.title,
|
|
2539
|
+
agentName: r.agentName,
|
|
2540
|
+
mode: r.mode,
|
|
2541
|
+
messageCount: r.messages?.length ?? 0
|
|
2542
|
+
});
|
|
2543
|
+
} catch {
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
return summaries.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
2547
|
+
}
|
|
2548
|
+
async function latestSession() {
|
|
2549
|
+
const [latest] = await listSessions();
|
|
2550
|
+
return latest ? loadSession(latest.id) : void 0;
|
|
2551
|
+
}
|
|
2552
|
+
function deriveTitle(messages) {
|
|
2553
|
+
const firstUser = messages.find((m) => m.role === "user");
|
|
2554
|
+
const text2 = (firstUser?.content ?? "").replace(/\s+/g, " ").trim();
|
|
2555
|
+
return text2.length > 60 ? text2.slice(0, 60) + "\u2026" : text2 || "(untitled)";
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2299
2558
|
// src/cli/commands/json-output.ts
|
|
2300
2559
|
var OUTPUT_PREVIEW = 500;
|
|
2301
2560
|
function createJsonCollector() {
|
|
@@ -2381,81 +2640,6 @@ async function listOllamaModels(host = ollamaHost(), timeoutMs = 2e3) {
|
|
|
2381
2640
|
}
|
|
2382
2641
|
}
|
|
2383
2642
|
|
|
2384
|
-
// src/core/providers/openrouter.ts
|
|
2385
|
-
var MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
2386
|
-
async function listOpenRouterModels(apiKey, timeoutMs = 8e3) {
|
|
2387
|
-
const controller = new AbortController();
|
|
2388
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2389
|
-
try {
|
|
2390
|
-
const res = await fetch(MODELS_URL, {
|
|
2391
|
-
signal: controller.signal,
|
|
2392
|
-
headers: apiKey ? { authorization: `Bearer ${apiKey}` } : {}
|
|
2393
|
-
});
|
|
2394
|
-
if (!res.ok) throw new Error(`OpenRouter ${res.status}: ${await res.text().catch(() => "")}`);
|
|
2395
|
-
const data = await res.json();
|
|
2396
|
-
return (data.data ?? []).map(normalize).filter((m) => m.id.length > 0);
|
|
2397
|
-
} finally {
|
|
2398
|
-
clearTimeout(timer);
|
|
2399
|
-
}
|
|
2400
|
-
}
|
|
2401
|
-
function normalize(m) {
|
|
2402
|
-
const promptPrice = toPerMillion(m.pricing?.prompt);
|
|
2403
|
-
const completionPrice = toPerMillion(m.pricing?.completion);
|
|
2404
|
-
return {
|
|
2405
|
-
id: m.id ?? "",
|
|
2406
|
-
name: m.name ?? m.id ?? "",
|
|
2407
|
-
promptPrice,
|
|
2408
|
-
completionPrice,
|
|
2409
|
-
contextLength: m.context_length ?? 0,
|
|
2410
|
-
supportsTools: (m.supported_parameters ?? []).includes("tools"),
|
|
2411
|
-
free: promptPrice === 0 && completionPrice === 0
|
|
2412
|
-
};
|
|
2413
|
-
}
|
|
2414
|
-
function toPerMillion(price) {
|
|
2415
|
-
const n = Number(price ?? "0");
|
|
2416
|
-
return Number.isFinite(n) ? n * 1e6 : 0;
|
|
2417
|
-
}
|
|
2418
|
-
function filterModels(models2, f) {
|
|
2419
|
-
const term = f.search?.trim().toLowerCase();
|
|
2420
|
-
let out = models2.filter((m) => {
|
|
2421
|
-
if (term && !m.id.toLowerCase().includes(term) && !m.name.toLowerCase().includes(term)) {
|
|
2422
|
-
return false;
|
|
2423
|
-
}
|
|
2424
|
-
if (f.tools === "tools" && !m.supportsTools) return false;
|
|
2425
|
-
if (f.tools === "no-tools" && m.supportsTools) return false;
|
|
2426
|
-
if (f.freeOnly && !m.free) return false;
|
|
2427
|
-
if (f.maxPrice !== void 0 && (m.promptPrice < 0 || m.promptPrice > f.maxPrice)) return false;
|
|
2428
|
-
return true;
|
|
2429
|
-
});
|
|
2430
|
-
const key = (m) => m.promptPrice < 0 ? Number.POSITIVE_INFINITY : m.promptPrice;
|
|
2431
|
-
const sort = f.sort ?? "price";
|
|
2432
|
-
out = out.sort((a, b) => {
|
|
2433
|
-
switch (sort) {
|
|
2434
|
-
case "price-desc":
|
|
2435
|
-
return key(b) - key(a);
|
|
2436
|
-
case "context":
|
|
2437
|
-
return b.contextLength - a.contextLength;
|
|
2438
|
-
case "name":
|
|
2439
|
-
return a.id.localeCompare(b.id);
|
|
2440
|
-
default:
|
|
2441
|
-
return key(a) - key(b);
|
|
2442
|
-
}
|
|
2443
|
-
});
|
|
2444
|
-
return out;
|
|
2445
|
-
}
|
|
2446
|
-
function fmtPrice(perMillion) {
|
|
2447
|
-
if (perMillion < 0) return "var";
|
|
2448
|
-
if (perMillion === 0) return "free";
|
|
2449
|
-
if (perMillion < 1) return `$${perMillion.toFixed(2)}`;
|
|
2450
|
-
if (perMillion < 100) return `$${perMillion.toFixed(perMillion % 1 ? 1 : 0)}`;
|
|
2451
|
-
return `$${Math.round(perMillion)}`;
|
|
2452
|
-
}
|
|
2453
|
-
function fmtContext(n) {
|
|
2454
|
-
if (n >= 1e6) return `${Math.round(n / 1e5) / 10}M`;
|
|
2455
|
-
if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
|
|
2456
|
-
return String(n);
|
|
2457
|
-
}
|
|
2458
|
-
|
|
2459
2643
|
// src/ui/wizard.ts
|
|
2460
2644
|
function bail(value) {
|
|
2461
2645
|
if (p.isCancel(value)) {
|
|
@@ -3055,6 +3239,36 @@ async function handleCommand(cmd, arg, ctx) {
|
|
|
3055
3239
|
session.history = [];
|
|
3056
3240
|
console.log(pc6.dim(t("repl.historyCleared")));
|
|
3057
3241
|
return;
|
|
3242
|
+
case "sessions": {
|
|
3243
|
+
const all = await listSessions();
|
|
3244
|
+
if (all.length === 0) {
|
|
3245
|
+
console.log(pc6.yellow(t("sessions.empty")));
|
|
3246
|
+
return;
|
|
3247
|
+
}
|
|
3248
|
+
console.log(pc6.bold(t("sessions.header")));
|
|
3249
|
+
for (const s of all) {
|
|
3250
|
+
console.log(` ${pc6.cyan(s.id)} ${pc6.dim(`[${s.messageCount} msgs]`)} ${s.title}`);
|
|
3251
|
+
}
|
|
3252
|
+
return;
|
|
3253
|
+
}
|
|
3254
|
+
case "resume": {
|
|
3255
|
+
if (!arg) {
|
|
3256
|
+
console.log(pc6.yellow(t("repl.needName", { usage: "/resume <id>" })));
|
|
3257
|
+
return;
|
|
3258
|
+
}
|
|
3259
|
+
const record = await loadSession(arg);
|
|
3260
|
+
if (!record) {
|
|
3261
|
+
console.log(pc6.red(t("sessions.notFound", { id: arg })));
|
|
3262
|
+
return;
|
|
3263
|
+
}
|
|
3264
|
+
session.id = record.id;
|
|
3265
|
+
session.title = record.title;
|
|
3266
|
+
session.agentName = record.agentName;
|
|
3267
|
+
session.mode = record.mode;
|
|
3268
|
+
session.history = record.messages;
|
|
3269
|
+
console.log(pc6.green(t("sessions.resumed", { id: record.id, n: record.messages.length })));
|
|
3270
|
+
return;
|
|
3271
|
+
}
|
|
3058
3272
|
case "swarm": {
|
|
3059
3273
|
if (!arg) {
|
|
3060
3274
|
console.log(pc6.yellow(t("repl.needName", { usage: "/swarm <task>" })));
|
|
@@ -3136,7 +3350,7 @@ import pc7 from "picocolors";
|
|
|
3136
3350
|
// src/core/git/worktree.ts
|
|
3137
3351
|
import { mkdtemp } from "fs/promises";
|
|
3138
3352
|
import { tmpdir } from "os";
|
|
3139
|
-
import { join as
|
|
3353
|
+
import { join as join6 } from "path";
|
|
3140
3354
|
import { simpleGit } from "simple-git";
|
|
3141
3355
|
async function ensureRepo(workspace) {
|
|
3142
3356
|
const git = simpleGit(workspace);
|
|
@@ -3157,7 +3371,7 @@ async function identityArgs(git) {
|
|
|
3157
3371
|
}
|
|
3158
3372
|
async function createWorktree(git, label) {
|
|
3159
3373
|
const branch = `polypus/${label}-${Date.now().toString(36)}`;
|
|
3160
|
-
const path = await mkdtemp(
|
|
3374
|
+
const path = await mkdtemp(join6(tmpdir(), "polypus-wt-"));
|
|
3161
3375
|
await git.raw(["worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3162
3376
|
return { path, branch };
|
|
3163
3377
|
}
|
|
@@ -3588,17 +3802,32 @@ var Spinner = class {
|
|
|
3588
3802
|
var MAX_VERIFY_FIXES = 3;
|
|
3589
3803
|
async function run(task, opts) {
|
|
3590
3804
|
let config = await loadConfig();
|
|
3591
|
-
const agentConfig = resolveAgent(config, opts.agent);
|
|
3592
3805
|
const workspace = process.cwd();
|
|
3806
|
+
let seeded;
|
|
3807
|
+
if (opts.resume) {
|
|
3808
|
+
seeded = await loadSession(opts.resume);
|
|
3809
|
+
if (!seeded) throw new Error(t("sessions.notFound", { id: opts.resume }));
|
|
3810
|
+
} else if (opts.continue) {
|
|
3811
|
+
seeded = await latestSession();
|
|
3812
|
+
if (!seeded && !opts.json) console.log(pc8.dim(t("sessions.noneToContinue")));
|
|
3813
|
+
}
|
|
3814
|
+
const agentConfig = resolveAgent(config, opts.agent ?? seeded?.agentName);
|
|
3593
3815
|
const session = {
|
|
3816
|
+
id: seeded?.id ?? newSessionId(),
|
|
3817
|
+
title: seeded?.title ?? "",
|
|
3594
3818
|
agentName: agentConfig.name,
|
|
3595
|
-
mode: opts.mode ?? config.permissions.mode,
|
|
3819
|
+
mode: opts.mode ?? seeded?.mode ?? config.permissions.mode,
|
|
3596
3820
|
allow: config.permissions.allow,
|
|
3597
3821
|
deny: config.permissions.deny,
|
|
3598
3822
|
allowedCommands: config.permissions.allowedCommands,
|
|
3599
3823
|
maxSteps: opts.maxSteps ? Number(opts.maxSteps) : void 0,
|
|
3600
|
-
history: []
|
|
3824
|
+
history: seeded?.messages ?? [],
|
|
3825
|
+
budget: opts.budget ? Number(opts.budget) : void 0,
|
|
3826
|
+
costUsd: 0
|
|
3601
3827
|
};
|
|
3828
|
+
if (seeded && !opts.json) {
|
|
3829
|
+
console.log(pc8.dim(t("sessions.resumed", { id: seeded.id, n: seeded.messages.length })));
|
|
3830
|
+
}
|
|
3602
3831
|
const runTask = async (taskText) => {
|
|
3603
3832
|
const active = resolveAgent(config, session.agentName);
|
|
3604
3833
|
const resolved2 = createProvider(active);
|
|
@@ -3621,6 +3850,9 @@ async function run(task, opts) {
|
|
|
3621
3850
|
);
|
|
3622
3851
|
}
|
|
3623
3852
|
await executeTask(task, resolved2, workspace, session, opts.json ?? false, opts.verify ?? false);
|
|
3853
|
+
if (session.budget !== void 0 && !opts.json) {
|
|
3854
|
+
console.log(pc8.dim(t("budget.session", { spent: fmtUsd(session.costUsd), budget: fmtUsd(session.budget) })));
|
|
3855
|
+
}
|
|
3624
3856
|
return;
|
|
3625
3857
|
}
|
|
3626
3858
|
const resolved = createProvider(agentConfig);
|
|
@@ -3657,6 +3889,21 @@ async function executeTask(task, resolved, workspace, session, json = false, ver
|
|
|
3657
3889
|
const controller = new AbortController();
|
|
3658
3890
|
const cancel2 = listenForCancel(controller);
|
|
3659
3891
|
const collector = json ? createJsonCollector() : void 0;
|
|
3892
|
+
const pricing = await resolveModelPricing(resolved.config);
|
|
3893
|
+
let budgetHit = false;
|
|
3894
|
+
const baseEvents = collector ? collector.events : renderEvents(spinner3);
|
|
3895
|
+
const events = {
|
|
3896
|
+
...baseEvents,
|
|
3897
|
+
onUsage(u) {
|
|
3898
|
+
baseEvents.onUsage?.(u);
|
|
3899
|
+
if (session.budget !== void 0 && pricing && !controller.signal.aborted) {
|
|
3900
|
+
if (session.costUsd + estimateCost(u, pricing) >= session.budget) {
|
|
3901
|
+
budgetHit = true;
|
|
3902
|
+
controller.abort();
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
};
|
|
3660
3907
|
const permissions = new PermissionEngine({
|
|
3661
3908
|
mode: session.mode,
|
|
3662
3909
|
policy: { workspace, allow: session.allow, deny: session.deny },
|
|
@@ -3679,7 +3926,7 @@ async function executeTask(task, resolved, workspace, session, json = false, ver
|
|
|
3679
3926
|
history: session.history,
|
|
3680
3927
|
maxSteps: session.maxSteps,
|
|
3681
3928
|
signal: controller.signal,
|
|
3682
|
-
events
|
|
3929
|
+
events
|
|
3683
3930
|
});
|
|
3684
3931
|
if (!json) spinner3.start(t("ui.thinking"));
|
|
3685
3932
|
let result;
|
|
@@ -3693,10 +3940,34 @@ async function executeTask(task, resolved, workspace, session, json = false, ver
|
|
|
3693
3940
|
spinner3.stop();
|
|
3694
3941
|
cancel2.dispose();
|
|
3695
3942
|
}
|
|
3943
|
+
if (!session.title) session.title = deriveTitle(session.history);
|
|
3944
|
+
await saveSession({
|
|
3945
|
+
id: session.id,
|
|
3946
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3947
|
+
title: session.title,
|
|
3948
|
+
agentName: session.agentName,
|
|
3949
|
+
mode: session.mode,
|
|
3950
|
+
messages: session.history
|
|
3951
|
+
}).catch(() => {
|
|
3952
|
+
});
|
|
3953
|
+
const runCost = pricing ? estimateCost(result.usage, pricing) : 0;
|
|
3954
|
+
session.costUsd += runCost;
|
|
3955
|
+
await recordUsage({
|
|
3956
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3957
|
+
agent: resolved.config.name,
|
|
3958
|
+
provider: resolved.config.provider,
|
|
3959
|
+
model: resolved.config.model,
|
|
3960
|
+
promptTokens: result.usage.promptTokens,
|
|
3961
|
+
completionTokens: result.usage.completionTokens,
|
|
3962
|
+
costUsd: runCost
|
|
3963
|
+
});
|
|
3696
3964
|
if (collector) {
|
|
3697
3965
|
process.stdout.write(JSON.stringify(collector.build(result)) + "\n");
|
|
3698
3966
|
return;
|
|
3699
3967
|
}
|
|
3968
|
+
if (budgetHit) {
|
|
3969
|
+
console.log(pc8.yellow("\n" + t("budget.hit", { budget: fmtUsd(session.budget ?? 0) })));
|
|
3970
|
+
}
|
|
3700
3971
|
if (result.reason === "finished") {
|
|
3701
3972
|
console.log(pc8.green("\n" + t("run.done", { steps: result.steps })) + (result.summary ? ` ${result.summary}` : ""));
|
|
3702
3973
|
} else if (result.reason === "cancelled") {
|
|
@@ -3706,15 +3977,13 @@ async function executeTask(task, resolved, workspace, session, json = false, ver
|
|
|
3706
3977
|
}
|
|
3707
3978
|
if (result.usage.promptTokens || result.usage.completionTokens) {
|
|
3708
3979
|
const total = result.usage.promptTokens + result.usage.completionTokens;
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
)
|
|
3717
|
-
);
|
|
3980
|
+
const tokensLine = t("ui.tokens", {
|
|
3981
|
+
total: fmtTokens(total),
|
|
3982
|
+
in: fmtTokens(result.usage.promptTokens),
|
|
3983
|
+
out: fmtTokens(result.usage.completionTokens)
|
|
3984
|
+
});
|
|
3985
|
+
const cost = pricing ? ` \xB7 ~${fmtUsd(runCost)}` : "";
|
|
3986
|
+
console.log(pc8.dim("\u21B3 " + tokensLine + cost));
|
|
3718
3987
|
}
|
|
3719
3988
|
}
|
|
3720
3989
|
function fmtTokens(n) {
|
|
@@ -3816,8 +4085,8 @@ function renderEvents(spinner3) {
|
|
|
3816
4085
|
onStep() {
|
|
3817
4086
|
spinner3.start(t("ui.thinking"));
|
|
3818
4087
|
},
|
|
3819
|
-
onUsage(
|
|
3820
|
-
const total =
|
|
4088
|
+
onUsage(usage2) {
|
|
4089
|
+
const total = usage2.promptTokens + usage2.completionTokens;
|
|
3821
4090
|
if (total > 0) spinner3.setSuffix(t("ui.tokensShort", { total: fmtTokens(total) }));
|
|
3822
4091
|
},
|
|
3823
4092
|
onAssistantText(text2) {
|
|
@@ -3855,8 +4124,8 @@ async function setup() {
|
|
|
3855
4124
|
import pc9 from "picocolors";
|
|
3856
4125
|
|
|
3857
4126
|
// src/core/scaffold/init.ts
|
|
3858
|
-
import { mkdir as
|
|
3859
|
-
import { dirname as dirname3, join as
|
|
4127
|
+
import { mkdir as mkdir5, writeFile as writeFile5, access } from "fs/promises";
|
|
4128
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
3860
4129
|
|
|
3861
4130
|
// src/core/scaffold/templates.ts
|
|
3862
4131
|
function polyTemplates(locale) {
|
|
@@ -4099,13 +4368,13 @@ async function scaffoldPoly(workspace, opts) {
|
|
|
4099
4368
|
const skipped = [];
|
|
4100
4369
|
for (const [rel, content] of Object.entries(templates)) {
|
|
4101
4370
|
const display = `.poly/${rel}`;
|
|
4102
|
-
const abs =
|
|
4371
|
+
const abs = join7(workspace, ".poly", ...rel.split("/"));
|
|
4103
4372
|
if (!opts.force && await exists(abs)) {
|
|
4104
4373
|
skipped.push(display);
|
|
4105
4374
|
continue;
|
|
4106
4375
|
}
|
|
4107
|
-
await
|
|
4108
|
-
await
|
|
4376
|
+
await mkdir5(dirname3(abs), { recursive: true });
|
|
4377
|
+
await writeFile5(abs, content, "utf8");
|
|
4109
4378
|
created.push(display);
|
|
4110
4379
|
}
|
|
4111
4380
|
return { created, skipped };
|
|
@@ -4198,11 +4467,51 @@ async function resolveOpenRouterKey() {
|
|
|
4198
4467
|
}
|
|
4199
4468
|
}
|
|
4200
4469
|
|
|
4470
|
+
// src/cli/commands/usage.ts
|
|
4471
|
+
import pc11 from "picocolors";
|
|
4472
|
+
async function usage() {
|
|
4473
|
+
const { days, total } = await aggregateUsage();
|
|
4474
|
+
if (days.length === 0) {
|
|
4475
|
+
console.log(pc11.yellow(t("usage.empty")));
|
|
4476
|
+
return;
|
|
4477
|
+
}
|
|
4478
|
+
console.log(pc11.bold(t("usage.header")));
|
|
4479
|
+
for (const d of days) console.log(" " + formatRow(d));
|
|
4480
|
+
console.log(pc11.dim(" " + "\u2500".repeat(40)));
|
|
4481
|
+
console.log(" " + pc11.bold(formatRow({ ...total, date: t("usage.total") })));
|
|
4482
|
+
}
|
|
4483
|
+
function formatRow(b) {
|
|
4484
|
+
const tokens2 = fmtTokens2(b.promptTokens + b.completionTokens);
|
|
4485
|
+
return `${b.date.padEnd(12)} ${tokens2.padStart(7)} tok ${fmtUsd(b.costUsd).padStart(10)} (${b.runs} ${t("usage.runs")})`;
|
|
4486
|
+
}
|
|
4487
|
+
function fmtTokens2(n) {
|
|
4488
|
+
return n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n);
|
|
4489
|
+
}
|
|
4490
|
+
|
|
4491
|
+
// src/cli/commands/sessions.ts
|
|
4492
|
+
import pc12 from "picocolors";
|
|
4493
|
+
async function sessions() {
|
|
4494
|
+
const all = await listSessions();
|
|
4495
|
+
if (all.length === 0) {
|
|
4496
|
+
console.log(pc12.yellow(t("sessions.empty")));
|
|
4497
|
+
return;
|
|
4498
|
+
}
|
|
4499
|
+
console.log(pc12.bold(t("sessions.header")));
|
|
4500
|
+
for (const s of all) {
|
|
4501
|
+
const when = s.updatedAt.replace("T", " ").slice(0, 16);
|
|
4502
|
+
console.log(
|
|
4503
|
+
` ${pc12.cyan(s.id)} ${pc12.dim(when)} ${pc12.dim(`[${s.agentName} \xB7 ${s.mode} \xB7 ${s.messageCount} msgs]`)}`
|
|
4504
|
+
);
|
|
4505
|
+
console.log(` ${s.title}`);
|
|
4506
|
+
}
|
|
4507
|
+
console.log(pc12.dim("\n" + t("sessions.hint")));
|
|
4508
|
+
}
|
|
4509
|
+
|
|
4201
4510
|
// src/cli/commands/prd.ts
|
|
4202
|
-
import { writeFile as
|
|
4511
|
+
import { writeFile as writeFile6, readFile as readFile12 } from "fs/promises";
|
|
4203
4512
|
import { execFile } from "child_process";
|
|
4204
4513
|
import { promisify as promisify3 } from "util";
|
|
4205
|
-
import
|
|
4514
|
+
import pc13 from "picocolors";
|
|
4206
4515
|
|
|
4207
4516
|
// src/core/agent/prd.ts
|
|
4208
4517
|
var SYSTEM = [
|
|
@@ -4330,15 +4639,15 @@ async function prd(issueRef, opts) {
|
|
|
4330
4639
|
const guide = readProjectGuide(["context.md"]);
|
|
4331
4640
|
const markdown = await withRetry(() => generatePrd(issue, provider, guide));
|
|
4332
4641
|
if (opts.out) {
|
|
4333
|
-
await
|
|
4334
|
-
console.error(
|
|
4642
|
+
await writeFile6(opts.out, markdown + "\n", "utf8");
|
|
4643
|
+
console.error(pc13.green(t("prd.wrote", { path: opts.out })));
|
|
4335
4644
|
} else {
|
|
4336
4645
|
process.stdout.write(markdown + "\n");
|
|
4337
4646
|
}
|
|
4338
4647
|
}
|
|
4339
4648
|
async function loadIssue(issueRef, input) {
|
|
4340
4649
|
if (input) {
|
|
4341
|
-
const raw = input === "-" ? await readStdin() : await
|
|
4650
|
+
const raw = input === "-" ? await readStdin() : await readFile12(input, "utf8");
|
|
4342
4651
|
return normalize2(JSON.parse(stripBom(raw)));
|
|
4343
4652
|
}
|
|
4344
4653
|
const num = numericRef(issueRef);
|
|
@@ -4357,10 +4666,10 @@ function normalize2(raw) {
|
|
|
4357
4666
|
}
|
|
4358
4667
|
|
|
4359
4668
|
// src/cli/commands/review.ts
|
|
4360
|
-
import { writeFile as
|
|
4669
|
+
import { writeFile as writeFile7, readFile as readFile13 } from "fs/promises";
|
|
4361
4670
|
import { execFile as execFile2 } from "child_process";
|
|
4362
4671
|
import { promisify as promisify4 } from "util";
|
|
4363
|
-
import
|
|
4672
|
+
import pc14 from "picocolors";
|
|
4364
4673
|
|
|
4365
4674
|
// src/core/agent/review.ts
|
|
4366
4675
|
var MAX_DIFF_CHARS = Number(process.env.POLYPUS_MAX_DIFF_CHARS) || 6e4;
|
|
@@ -4426,14 +4735,14 @@ async function review(prRef, opts) {
|
|
|
4426
4735
|
const guide = readProjectGuide(["rules.md", "context.md"]);
|
|
4427
4736
|
const markdown = await withRetry(() => reviewDiff(diff, meta, provider, guide));
|
|
4428
4737
|
if (opts.out) {
|
|
4429
|
-
await
|
|
4430
|
-
console.error(
|
|
4738
|
+
await writeFile7(opts.out, markdown + "\n", "utf8");
|
|
4739
|
+
console.error(pc14.green(t("review.wrote", { path: opts.out })));
|
|
4431
4740
|
} else {
|
|
4432
4741
|
process.stdout.write(markdown + "\n");
|
|
4433
4742
|
}
|
|
4434
4743
|
}
|
|
4435
4744
|
async function loadDiff(num, input) {
|
|
4436
|
-
if (input) return input === "-" ? readStdin() :
|
|
4745
|
+
if (input) return input === "-" ? readStdin() : readFile13(input, "utf8");
|
|
4437
4746
|
const { stdout: stdout2 } = await exec4("gh", ["pr", "diff", num]);
|
|
4438
4747
|
return stdout2;
|
|
4439
4748
|
}
|
|
@@ -4445,7 +4754,7 @@ async function loadMeta(num, input) {
|
|
|
4445
4754
|
}
|
|
4446
4755
|
|
|
4447
4756
|
// src/cli/index.ts
|
|
4448
|
-
import { join as
|
|
4757
|
+
import { join as join8 } from "path";
|
|
4449
4758
|
|
|
4450
4759
|
// src/core/config/dotenv.ts
|
|
4451
4760
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
@@ -4478,7 +4787,7 @@ async function launchInteractive() {
|
|
|
4478
4787
|
const config = await loadConfig();
|
|
4479
4788
|
if (config.agents.length === 0) {
|
|
4480
4789
|
console.log(banner());
|
|
4481
|
-
console.log(" " +
|
|
4790
|
+
console.log(" " + pc15.yellow(t("welcome.firstRun")) + "\n");
|
|
4482
4791
|
await setup();
|
|
4483
4792
|
}
|
|
4484
4793
|
await run(void 0, {});
|
|
@@ -4505,20 +4814,22 @@ function buildProgram() {
|
|
|
4505
4814
|
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));
|
|
4506
4815
|
program.command("remove-agent").argument("<name>", t("cli.arg.removeAgentName")).description(t("cli.cmd.removeAgent")).action((name) => removeAgent(name));
|
|
4507
4816
|
program.command("list-agents").alias("agents").description(t("cli.cmd.listAgents")).action(() => listAgents());
|
|
4508
|
-
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")).description(t("cli.cmd.run")).action((task, opts) => run(task, opts));
|
|
4817
|
+
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")).option("--continue", t("cli.opt.continue")).option("--resume <id>", t("cli.opt.resume")).description(t("cli.cmd.run")).action((task, opts) => run(task, opts));
|
|
4509
4818
|
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));
|
|
4510
4819
|
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));
|
|
4820
|
+
program.command("usage").description(t("cli.cmd.usage")).action(() => usage());
|
|
4821
|
+
program.command("sessions").description(t("cli.cmd.sessions")).action(() => sessions());
|
|
4511
4822
|
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));
|
|
4512
4823
|
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));
|
|
4513
4824
|
return program;
|
|
4514
4825
|
}
|
|
4515
4826
|
async function main() {
|
|
4516
4827
|
try {
|
|
4517
|
-
loadDotenv([
|
|
4828
|
+
loadDotenv([join8(configDir(), ".env"), join8(process.cwd(), ".env")]);
|
|
4518
4829
|
await resolveLocale();
|
|
4519
4830
|
await buildProgram().parseAsync(process.argv);
|
|
4520
4831
|
} catch (err) {
|
|
4521
|
-
console.error(
|
|
4832
|
+
console.error(pc15.red(`\u2717 ${err.message}`));
|
|
4522
4833
|
process.exitCode = 1;
|
|
4523
4834
|
}
|
|
4524
4835
|
}
|