@gaberrb/polypus 0.2.0 โ 0.2.2
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 +1 -1
- package/dist/index.js +270 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@gaberrb/polypus)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
**๐ Site:** https://gaberrb.github.io/polypus/ ยท **๐ฆ npm:** [`@gaberrb/polypus`](https://www.npmjs.com/package/@gaberrb/polypus)
|
|
7
|
+
**๐ Site:** https://gaberrb.github.io/polypus/ ยท **๐ฆ npm:** [`@gaberrb/polypus`](https://www.npmjs.com/package/@gaberrb/polypus) ยท **๐ Changelog:** [CHANGELOG.md](https://github.com/GaberRB/polypus/blob/main/CHANGELOG.md)
|
|
8
8
|
|
|
9
9
|
An agentic coding harness that makes **any** AI API generate and apply code โ
|
|
10
10
|
even models **without** native tool-calling. Bring your own keys: OpenRouter,
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
import pc12 from "picocolors";
|
|
6
7
|
|
|
7
8
|
// src/cli/commands/add-agent.ts
|
|
8
9
|
import pc from "picocolors";
|
|
@@ -79,6 +80,16 @@ var en = {
|
|
|
79
80
|
"cli.cmd.run": "Run a coding task with an agent",
|
|
80
81
|
"cli.cmd.swarm": "Split a task across multiple agents working in parallel git worktrees",
|
|
81
82
|
"cli.cmd.models": "Browse OpenRouter models (price, context, tool support)",
|
|
83
|
+
"cli.cmd.prd": "Generate a PRD from a GitHub issue (uses a free OpenRouter model)",
|
|
84
|
+
"cli.arg.prdIssue": "issue number to turn into a PRD",
|
|
85
|
+
"cli.cmd.review": "Review a pull request diff (uses a free OpenRouter model)",
|
|
86
|
+
"cli.arg.reviewPr": "pull request number to review",
|
|
87
|
+
"cli.opt.out": "write output to this file instead of stdout",
|
|
88
|
+
"cli.opt.input": 'read input from a file (or "-" for stdin) instead of calling gh',
|
|
89
|
+
"prd.wrote": "\u2713 PRD written to {path}",
|
|
90
|
+
"review.wrote": "\u2713 Review written to {path}",
|
|
91
|
+
"cli.invalidRef": "Invalid number '{ref}': expected a numeric issue/PR number.",
|
|
92
|
+
"cli.stdinTty": "--input - expects piped stdin, but none was provided.",
|
|
82
93
|
"cli.opt.search": "filter by id/name substring",
|
|
83
94
|
"cli.opt.toolsOnly": "only models that support tool-calling",
|
|
84
95
|
"cli.opt.free": "only free models",
|
|
@@ -269,6 +280,16 @@ var ptBR = {
|
|
|
269
280
|
"cli.cmd.run": "Executa uma tarefa de c\xF3digo com um agente",
|
|
270
281
|
"cli.cmd.swarm": "Divide uma tarefa entre v\xE1rios agentes trabalhando em paralelo em git worktrees",
|
|
271
282
|
"cli.cmd.models": "Explora os modelos do OpenRouter (pre\xE7o, contexto, suporte a tools)",
|
|
283
|
+
"cli.cmd.prd": "Gera um PRD a partir de uma issue do GitHub (usa um modelo gratuito do OpenRouter)",
|
|
284
|
+
"cli.arg.prdIssue": "n\xFAmero da issue para transformar em PRD",
|
|
285
|
+
"cli.cmd.review": "Revisa o diff de um pull request (usa um modelo gratuito do OpenRouter)",
|
|
286
|
+
"cli.arg.reviewPr": "n\xFAmero do pull request a revisar",
|
|
287
|
+
"cli.opt.out": "grava a sa\xEDda neste arquivo em vez do stdout",
|
|
288
|
+
"cli.opt.input": 'l\xEA a entrada de um arquivo (ou "-" para stdin) em vez de chamar o gh',
|
|
289
|
+
"prd.wrote": "\u2713 PRD gravado em {path}",
|
|
290
|
+
"review.wrote": "\u2713 Review gravado em {path}",
|
|
291
|
+
"cli.invalidRef": "N\xFAmero inv\xE1lido '{ref}': esperado um n\xFAmero de issue/PR.",
|
|
292
|
+
"cli.stdinTty": "--input - espera entrada via pipe (stdin), mas nenhuma foi fornecida.",
|
|
272
293
|
"cli.opt.search": "filtra por trecho do id/nome",
|
|
273
294
|
"cli.opt.toolsOnly": "apenas modelos com suporte a tool-calling",
|
|
274
295
|
"cli.opt.free": "apenas modelos gratuitos",
|
|
@@ -1447,6 +1468,14 @@ function getTool(name) {
|
|
|
1447
1468
|
// src/core/agent/correction.ts
|
|
1448
1469
|
import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
|
|
1449
1470
|
import { dirname as dirname2, resolve as resolve6 } from "path";
|
|
1471
|
+
function truncationGuidance(toolName) {
|
|
1472
|
+
const fileHint = toolName === "write_file" || toolName === "edit_file" ? " Write large files in parts: create the file with the first chunk via write_file, then append the rest with edit_file in the next steps." : "";
|
|
1473
|
+
return [
|
|
1474
|
+
"AUTO-CORRECTION \u2014 your previous response was cut off at the output token limit,",
|
|
1475
|
+
"so the tool call was incomplete (e.g. 'content' missing or partial).",
|
|
1476
|
+
"Do NOT resend the same large output \u2014 produce a smaller one this time." + fileHint
|
|
1477
|
+
].join("\n");
|
|
1478
|
+
}
|
|
1450
1479
|
async function buildCorrection(call, output, deps) {
|
|
1451
1480
|
const deterministic = await deterministicCorrection(call, output, deps);
|
|
1452
1481
|
if (deterministic) return deterministic;
|
|
@@ -1454,6 +1483,7 @@ async function buildCorrection(call, output, deps) {
|
|
|
1454
1483
|
return null;
|
|
1455
1484
|
}
|
|
1456
1485
|
async function deterministicCorrection(call, output, deps) {
|
|
1486
|
+
if (deps.truncated) return truncationGuidance(call.name);
|
|
1457
1487
|
const path = typeof call.arguments.path === "string" ? call.arguments.path : void 0;
|
|
1458
1488
|
if (call.name === "edit_file" && /was not found/i.test(output) && path) {
|
|
1459
1489
|
const search = typeof call.arguments.search === "string" ? call.arguments.search : "";
|
|
@@ -1693,7 +1723,18 @@ async function runAgent(opts) {
|
|
|
1693
1723
|
const { toolCalls, text: text2 } = driver.parse(response);
|
|
1694
1724
|
messages.push(driver.assistantMessage(response, toolCalls));
|
|
1695
1725
|
if (text2) events?.onAssistantText?.(text2);
|
|
1726
|
+
const truncated = response.finishReason === "length" || response.finishReason === "max_tokens";
|
|
1696
1727
|
if (toolCalls.length === 0) {
|
|
1728
|
+
if (truncated && autoCorrect) {
|
|
1729
|
+
if (consecutiveNoTool < maxReprompts) {
|
|
1730
|
+
consecutiveNoTool++;
|
|
1731
|
+
const guidance = truncationGuidance();
|
|
1732
|
+
events?.onCorrection?.({ id: "trunc", name: "", arguments: {} }, guidance);
|
|
1733
|
+
messages.push({ role: "user", content: guidance });
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
return { finished: false, reason: "stalled", steps: step, messages, usage };
|
|
1737
|
+
}
|
|
1697
1738
|
const stalled = text2.trim().length === 0 || looksLikeStall(text2);
|
|
1698
1739
|
if (stalled) {
|
|
1699
1740
|
if (consecutiveNoTool < maxReprompts) {
|
|
@@ -1724,6 +1765,7 @@ async function runAgent(opts) {
|
|
|
1724
1765
|
workspace: opts.workspace,
|
|
1725
1766
|
allow: opts.promptContext.allow,
|
|
1726
1767
|
toolSpec: tool?.spec,
|
|
1768
|
+
truncated,
|
|
1727
1769
|
// Only spend a fixer-LLM call once the model has already repeated a
|
|
1728
1770
|
// failing call โ the first failure gets deterministic help for free.
|
|
1729
1771
|
escalate: sig === lastFailSig ? makeLLMEscalator(agent.provider) : void 0
|
|
@@ -1734,6 +1776,12 @@ async function runAgent(opts) {
|
|
|
1734
1776
|
${guidance}`;
|
|
1735
1777
|
events?.onCorrection?.(call, guidance);
|
|
1736
1778
|
}
|
|
1779
|
+
} else if (result.ok && truncated && autoCorrect) {
|
|
1780
|
+
const guidance = truncationGuidance(call.name);
|
|
1781
|
+
resultText = `${result.output}
|
|
1782
|
+
|
|
1783
|
+
${guidance}`;
|
|
1784
|
+
events?.onCorrection?.(call, guidance);
|
|
1737
1785
|
}
|
|
1738
1786
|
messages.push(driver.toolResultMessage(call, resultText));
|
|
1739
1787
|
if (result.ok) {
|
|
@@ -2907,6 +2955,221 @@ async function resolveOpenRouterKey() {
|
|
|
2907
2955
|
}
|
|
2908
2956
|
}
|
|
2909
2957
|
|
|
2958
|
+
// src/cli/commands/prd.ts
|
|
2959
|
+
import { writeFile as writeFile4, readFile as readFile5 } from "fs/promises";
|
|
2960
|
+
import { execFile } from "child_process";
|
|
2961
|
+
import { promisify as promisify2 } from "util";
|
|
2962
|
+
import pc10 from "picocolors";
|
|
2963
|
+
|
|
2964
|
+
// src/core/agent/prd.ts
|
|
2965
|
+
var SYSTEM = [
|
|
2966
|
+
"You are a product analyst. You turn a GitHub issue into a concise, structured PRD",
|
|
2967
|
+
"(Product Requirements Document) in Markdown.",
|
|
2968
|
+
"Rules:",
|
|
2969
|
+
"- Use ONLY information present in the issue and its comments. Do NOT invent scope,",
|
|
2970
|
+
" numbers, deadlines, or stakeholders. If something is unknown, write 'A definir'.",
|
|
2971
|
+
"- Be objective and short; prefer bullet points over prose.",
|
|
2972
|
+
"- Write in the same language as the issue (Portuguese if the issue is in Portuguese)."
|
|
2973
|
+
].join("\n");
|
|
2974
|
+
function buildPrdPrompt(issue) {
|
|
2975
|
+
const comments = (issue.comments ?? []).map((c, i) => `Coment\xE1rio ${i + 1}${c.author ? ` (@${c.author})` : ""}:
|
|
2976
|
+
${c.body}`).join("\n\n");
|
|
2977
|
+
return [
|
|
2978
|
+
`Issue${issue.number ? ` #${issue.number}` : ""}: ${issue.title}`,
|
|
2979
|
+
"",
|
|
2980
|
+
"Corpo:",
|
|
2981
|
+
issue.body || "(vazio)",
|
|
2982
|
+
comments ? `
|
|
2983
|
+
${comments}` : "",
|
|
2984
|
+
"",
|
|
2985
|
+
"Gere um PRD com EXATAMENTE estas se\xE7\xF5es (H2 com ##):",
|
|
2986
|
+
"## Contexto / Problema",
|
|
2987
|
+
"## Objetivo",
|
|
2988
|
+
"## Escopo (in / out)",
|
|
2989
|
+
"## Requisitos funcionais",
|
|
2990
|
+
"## Crit\xE9rios de aceite",
|
|
2991
|
+
"## Riscos e alternativas"
|
|
2992
|
+
].join("\n");
|
|
2993
|
+
}
|
|
2994
|
+
async function generatePrd(issue, provider) {
|
|
2995
|
+
const res = await provider.chat({
|
|
2996
|
+
messages: [
|
|
2997
|
+
{ role: "system", content: SYSTEM },
|
|
2998
|
+
{ role: "user", content: buildPrdPrompt(issue) }
|
|
2999
|
+
],
|
|
3000
|
+
params: { maxTokens: 2e3, temperature: 0.2 }
|
|
3001
|
+
});
|
|
3002
|
+
const text2 = res.content.trim();
|
|
3003
|
+
if (!text2) throw new Error("The model returned an empty PRD.");
|
|
3004
|
+
return text2;
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
// src/core/agent/free-provider.ts
|
|
3008
|
+
function resolveFreeProvider(model) {
|
|
3009
|
+
if (!process.env.OPENROUTER_API_KEY) {
|
|
3010
|
+
throw new Error(
|
|
3011
|
+
"OPENROUTER_API_KEY is not set. Export it (or add it as a repo secret in CI) before running this command."
|
|
3012
|
+
);
|
|
3013
|
+
}
|
|
3014
|
+
const config = {
|
|
3015
|
+
name: "openrouter-free",
|
|
3016
|
+
provider: "openrouter",
|
|
3017
|
+
model,
|
|
3018
|
+
apiKey: "${OPENROUTER_API_KEY}",
|
|
3019
|
+
toolMode: "native"
|
|
3020
|
+
};
|
|
3021
|
+
return createProvider(config);
|
|
3022
|
+
}
|
|
3023
|
+
var DEFAULT_PRD_MODEL = process.env.POLYPUS_PRD_MODEL ?? "openai/gpt-oss-120b:free";
|
|
3024
|
+
var DEFAULT_REVIEW_MODEL = process.env.POLYPUS_REVIEW_MODEL ?? "openai/gpt-oss-120b:free";
|
|
3025
|
+
async function withRetry(fn, opts = {}) {
|
|
3026
|
+
const attempts = opts.attempts ?? 4;
|
|
3027
|
+
const baseMs = opts.baseMs ?? 2e3;
|
|
3028
|
+
let lastErr;
|
|
3029
|
+
for (let i = 0; i < attempts; i++) {
|
|
3030
|
+
try {
|
|
3031
|
+
return await fn();
|
|
3032
|
+
} catch (err) {
|
|
3033
|
+
lastErr = err;
|
|
3034
|
+
const msg = err?.message ?? "";
|
|
3035
|
+
const transient = /\b429\b|\b5\d\d\b|rate|timeout|ETIMEDOUT|ECONNRESET|EAI_AGAIN/i.test(msg);
|
|
3036
|
+
if (!transient || i === attempts - 1) throw err;
|
|
3037
|
+
await new Promise((r) => setTimeout(r, baseMs * 2 ** i));
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
throw lastErr;
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
// src/cli/commands/cli-io.ts
|
|
3044
|
+
function numericRef(ref) {
|
|
3045
|
+
const num = ref.replace(/^#/, "");
|
|
3046
|
+
if (!/^\d+$/.test(num)) throw new Error(t("cli.invalidRef", { ref }));
|
|
3047
|
+
return num;
|
|
3048
|
+
}
|
|
3049
|
+
async function readStdin() {
|
|
3050
|
+
if (process.stdin.isTTY) throw new Error(t("cli.stdinTty"));
|
|
3051
|
+
const chunks = [];
|
|
3052
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
3053
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
3054
|
+
}
|
|
3055
|
+
function stripBom(s) {
|
|
3056
|
+
return s.charCodeAt(0) === 65279 ? s.slice(1) : s;
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
// src/cli/commands/prd.ts
|
|
3060
|
+
var exec2 = promisify2(execFile);
|
|
3061
|
+
async function prd(issueRef, opts) {
|
|
3062
|
+
const issue = await loadIssue(issueRef, opts.input);
|
|
3063
|
+
const { provider } = resolveFreeProvider(opts.model ?? DEFAULT_PRD_MODEL);
|
|
3064
|
+
const markdown = await withRetry(() => generatePrd(issue, provider));
|
|
3065
|
+
if (opts.out) {
|
|
3066
|
+
await writeFile4(opts.out, markdown + "\n", "utf8");
|
|
3067
|
+
console.error(pc10.green(t("prd.wrote", { path: opts.out })));
|
|
3068
|
+
} else {
|
|
3069
|
+
process.stdout.write(markdown + "\n");
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
async function loadIssue(issueRef, input) {
|
|
3073
|
+
if (input) {
|
|
3074
|
+
const raw = input === "-" ? await readStdin() : await readFile5(input, "utf8");
|
|
3075
|
+
return normalize2(JSON.parse(stripBom(raw)));
|
|
3076
|
+
}
|
|
3077
|
+
const num = numericRef(issueRef);
|
|
3078
|
+
const { stdout: stdout2 } = await exec2("gh", ["issue", "view", num, "--json", "number,title,body,comments"]);
|
|
3079
|
+
const data = normalize2(JSON.parse(stdout2));
|
|
3080
|
+
data.number ??= Number(num);
|
|
3081
|
+
return data;
|
|
3082
|
+
}
|
|
3083
|
+
function normalize2(raw) {
|
|
3084
|
+
return {
|
|
3085
|
+
number: raw.number,
|
|
3086
|
+
title: raw.title ?? "",
|
|
3087
|
+
body: raw.body ?? "",
|
|
3088
|
+
comments: (raw.comments ?? []).map((c) => ({ author: c.author?.login, body: c.body ?? "" }))
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
// src/cli/commands/review.ts
|
|
3093
|
+
import { writeFile as writeFile5, readFile as readFile6 } from "fs/promises";
|
|
3094
|
+
import { execFile as execFile2 } from "child_process";
|
|
3095
|
+
import { promisify as promisify3 } from "util";
|
|
3096
|
+
import pc11 from "picocolors";
|
|
3097
|
+
|
|
3098
|
+
// src/core/agent/review.ts
|
|
3099
|
+
var MAX_DIFF_CHARS = Number(process.env.POLYPUS_MAX_DIFF_CHARS) || 6e4;
|
|
3100
|
+
var SYSTEM2 = [
|
|
3101
|
+
"You are a senior code reviewer. Review the pull request diff below and report",
|
|
3102
|
+
"concrete findings in Markdown.",
|
|
3103
|
+
"Rules:",
|
|
3104
|
+
"- Focus on correctness bugs, security risks, and clear, actionable improvements.",
|
|
3105
|
+
"- Reference file paths (and line hints when visible) so findings are easy to locate.",
|
|
3106
|
+
"- Be concise. Skip style nitpicks unless they hide a real problem.",
|
|
3107
|
+
"- If there are no relevant problems, say so briefly instead of inventing issues.",
|
|
3108
|
+
"- Write in the same language as the PR description (Portuguese if it is in Portuguese)."
|
|
3109
|
+
].join("\n");
|
|
3110
|
+
function clampDiff(diff, max = MAX_DIFF_CHARS) {
|
|
3111
|
+
if (diff.length <= max) return diff;
|
|
3112
|
+
const dropped = diff.length - max;
|
|
3113
|
+
return `${diff.slice(0, max)}
|
|
3114
|
+
|
|
3115
|
+
\u2026 [diff truncado: ${dropped} caracteres omitidos para caber no contexto do modelo]`;
|
|
3116
|
+
}
|
|
3117
|
+
function buildReviewPrompt(diff, meta) {
|
|
3118
|
+
return [
|
|
3119
|
+
`PR${meta.number ? ` #${meta.number}` : ""}: ${meta.title}`,
|
|
3120
|
+
"",
|
|
3121
|
+
"Descri\xE7\xE3o:",
|
|
3122
|
+
meta.body || "(vazia)",
|
|
3123
|
+
"",
|
|
3124
|
+
"Diff:",
|
|
3125
|
+
"```diff",
|
|
3126
|
+
clampDiff(diff),
|
|
3127
|
+
"```",
|
|
3128
|
+
"",
|
|
3129
|
+
"Liste os achados agrupados por severidade (\u{1F534} bug, \u{1F7E1} aten\xE7\xE3o, \u{1F7E2} sugest\xE3o)."
|
|
3130
|
+
].join("\n");
|
|
3131
|
+
}
|
|
3132
|
+
async function reviewDiff(diff, meta, provider) {
|
|
3133
|
+
if (!diff.trim()) return "_Sem altera\xE7\xF5es no diff para revisar._";
|
|
3134
|
+
const res = await provider.chat({
|
|
3135
|
+
messages: [
|
|
3136
|
+
{ role: "system", content: SYSTEM2 },
|
|
3137
|
+
{ role: "user", content: buildReviewPrompt(diff, meta) }
|
|
3138
|
+
],
|
|
3139
|
+
params: { maxTokens: 1500, temperature: 0.2 }
|
|
3140
|
+
});
|
|
3141
|
+
const text2 = res.content.trim();
|
|
3142
|
+
if (!text2) throw new Error("The model returned an empty review.");
|
|
3143
|
+
return text2;
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
// src/cli/commands/review.ts
|
|
3147
|
+
var exec3 = promisify3(execFile2);
|
|
3148
|
+
async function review(prRef, opts) {
|
|
3149
|
+
const num = opts.input ? prRef.replace(/^#/, "") : numericRef(prRef);
|
|
3150
|
+
const diff = await loadDiff(num, opts.input);
|
|
3151
|
+
const meta = await loadMeta(num, opts.input);
|
|
3152
|
+
const { provider } = resolveFreeProvider(opts.model ?? DEFAULT_REVIEW_MODEL);
|
|
3153
|
+
const markdown = await withRetry(() => reviewDiff(diff, meta, provider));
|
|
3154
|
+
if (opts.out) {
|
|
3155
|
+
await writeFile5(opts.out, markdown + "\n", "utf8");
|
|
3156
|
+
console.error(pc11.green(t("review.wrote", { path: opts.out })));
|
|
3157
|
+
} else {
|
|
3158
|
+
process.stdout.write(markdown + "\n");
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
async function loadDiff(num, input) {
|
|
3162
|
+
if (input) return input === "-" ? readStdin() : readFile6(input, "utf8");
|
|
3163
|
+
const { stdout: stdout2 } = await exec3("gh", ["pr", "diff", num]);
|
|
3164
|
+
return stdout2;
|
|
3165
|
+
}
|
|
3166
|
+
async function loadMeta(num, input) {
|
|
3167
|
+
if (input) return { number: Number(num) || void 0, title: `PR ${num}`, body: "" };
|
|
3168
|
+
const { stdout: stdout2 } = await exec3("gh", ["pr", "view", num, "--json", "number,title,body"]);
|
|
3169
|
+
const raw = JSON.parse(stdout2);
|
|
3170
|
+
return { number: raw.number, title: raw.title ?? "", body: raw.body ?? "" };
|
|
3171
|
+
}
|
|
3172
|
+
|
|
2910
3173
|
// src/cli/index.ts
|
|
2911
3174
|
import { join as join3 } from "path";
|
|
2912
3175
|
|
|
@@ -2937,11 +3200,12 @@ function loadDotenv(paths) {
|
|
|
2937
3200
|
}
|
|
2938
3201
|
|
|
2939
3202
|
// src/cli/index.ts
|
|
3203
|
+
var { version: pkgVersion } = createRequire(import.meta.url)("../package.json");
|
|
2940
3204
|
async function launchInteractive() {
|
|
2941
3205
|
const config = await loadConfig();
|
|
2942
3206
|
if (config.agents.length === 0) {
|
|
2943
3207
|
console.log(banner());
|
|
2944
|
-
console.log(" " +
|
|
3208
|
+
console.log(" " + pc12.yellow(t("welcome.firstRun")) + "\n");
|
|
2945
3209
|
await setup();
|
|
2946
3210
|
}
|
|
2947
3211
|
await run(void 0, {});
|
|
@@ -2962,7 +3226,7 @@ async function resolveLocale() {
|
|
|
2962
3226
|
}
|
|
2963
3227
|
function buildProgram() {
|
|
2964
3228
|
const program = new Command();
|
|
2965
|
-
program.name("polypus").description(t("cli.description")).version(
|
|
3229
|
+
program.name("polypus").description(t("cli.description")).version(pkgVersion).option("--lang <locale>", t("cli.opt.lang")).action(() => launchInteractive());
|
|
2966
3230
|
program.command("setup").description(t("cli.cmd.setup")).action(() => setup());
|
|
2967
3231
|
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));
|
|
2968
3232
|
program.command("remove-agent").argument("<name>", t("cli.arg.removeAgentName")).description(t("cli.cmd.removeAgent")).action((name) => removeAgent(name));
|
|
@@ -2970,6 +3234,8 @@ function buildProgram() {
|
|
|
2970
3234
|
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")).description(t("cli.cmd.run")).action((task, opts) => run(task, opts));
|
|
2971
3235
|
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));
|
|
2972
3236
|
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));
|
|
3237
|
+
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));
|
|
3238
|
+
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));
|
|
2973
3239
|
return program;
|
|
2974
3240
|
}
|
|
2975
3241
|
async function main() {
|
|
@@ -2978,7 +3244,7 @@ async function main() {
|
|
|
2978
3244
|
await resolveLocale();
|
|
2979
3245
|
await buildProgram().parseAsync(process.argv);
|
|
2980
3246
|
} catch (err) {
|
|
2981
|
-
console.error(
|
|
3247
|
+
console.error(pc12.red(`\u2717 ${err.message}`));
|
|
2982
3248
|
process.exitCode = 1;
|
|
2983
3249
|
}
|
|
2984
3250
|
}
|