@gaberrb/polypus 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -109,6 +109,7 @@ var en = {
109
109
  "cli.opt.agent": "which configured agent to use",
110
110
  "cli.opt.mode": "plan | review | bypass (overrides config)",
111
111
  "cli.opt.maxSteps": "maximum agent steps",
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",
112
113
  "cli.arg.swarmTask": "high-level task to split across agents",
113
114
  "cli.opt.agents": "comma-separated agent names (default: all configured)",
114
115
  "cli.opt.maxSubtasks": "maximum number of parallel subtasks",
@@ -135,6 +136,7 @@ var en = {
135
136
  "run.reprompt": "\u21BB no tool call \u2014 reinforcing instructions (attempt {attempt})",
136
137
  "run.autocorrect": "\u21BB tool failed \u2014 auto-correcting with extra context",
137
138
  "run.cancelled": "\u25A0 cancelled",
139
+ "run.jsonNeedsTask": "--json requires a task argument (headless mode has no interactive REPL).",
138
140
  // repl
139
141
  "repl.welcome": "Polypus interactive session.",
140
142
  "repl.welcomeHint": " Type /help for commands, /exit to quit.",
@@ -285,7 +287,11 @@ var en = {
285
287
  "welcome.firstRun": "No agents configured yet \u2014 let's set you up.",
286
288
  // agent system prompt
287
289
  "prompt.language": "Communicate with the user in {language}.",
288
- "prompt.projectInstructions": "Project-specific operating instructions follow, loaded from `.poly/agents.md`. Treat them as authoritative for how to work in THIS repo. Paths they reference (e.g. skills/*.md, ../context.md, ../rules.md) are relative to the `.poly/` directory \u2014 read those files when relevant before acting:"
290
+ "prompt.projectInstructions": "Project-specific operating instructions follow, loaded from `.poly/agents.md`. Treat them as authoritative for how to work in THIS repo. Paths they reference (e.g. skills/*.md, ../context.md, ../rules.md) are relative to the `.poly/` directory \u2014 read those files when relevant before acting:",
291
+ // @-mentions
292
+ "mentions.injectedHeader": "Referenced files (@-mentions)",
293
+ "mentions.dirHeader": "@{path} (directory listing)",
294
+ "mentions.notFound": "(could not resolve @{path}: not found or outside the allow-list)"
289
295
  };
290
296
  var ptBR = {
291
297
  "common.default": "padr\xE3o",
@@ -330,6 +336,7 @@ var ptBR = {
330
336
  "cli.opt.agent": "qual agente configurado usar",
331
337
  "cli.opt.mode": "plan | review | bypass (sobrescreve a config)",
332
338
  "cli.opt.maxSteps": "n\xFAmero m\xE1ximo de passos do agente",
339
+ "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",
333
340
  "cli.arg.swarmTask": "tarefa de alto n\xEDvel para dividir entre os agentes",
334
341
  "cli.opt.agents": "nomes de agentes separados por v\xEDrgula (padr\xE3o: todos)",
335
342
  "cli.opt.maxSubtasks": "n\xFAmero m\xE1ximo de subtarefas paralelas",
@@ -354,6 +361,7 @@ var ptBR = {
354
361
  "run.reprompt": "\u21BB nenhuma chamada de tool \u2014 refor\xE7ando instru\xE7\xF5es (tentativa {attempt})",
355
362
  "run.autocorrect": "\u21BB tool falhou \u2014 autocorrigindo com contexto extra",
356
363
  "run.cancelled": "\u25A0 cancelado",
364
+ "run.jsonNeedsTask": "--json exige um argumento de tarefa (o modo headless n\xE3o tem REPL interativo).",
357
365
  "repl.welcome": "Sess\xE3o interativa do Polypus.",
358
366
  "repl.welcomeHint": " Digite /help para comandos, /exit para sair.",
359
367
  "repl.modeChanged": "modo \u2192 {mode}",
@@ -478,6 +486,10 @@ var ptBR = {
478
486
  "wizard.keyPrompt": "Chave de API (armazenada em texto puro no arquivo de config)",
479
487
  "prompt.language": "Comunique-se com o usu\xE1rio em {language}.",
480
488
  "prompt.projectInstructions": "Seguem instru\xE7\xF5es operacionais espec\xEDficas do projeto, carregadas de `.poly/agents.md`. Trate-as como autoritativas para trabalhar NESTE reposit\xF3rio. Os caminhos que elas citam (ex.: skills/*.md, ../context.md, ../rules.md) s\xE3o relativos \xE0 pasta `.poly/` \u2014 leia esses arquivos quando relevante antes de agir:",
489
+ // @-mentions
490
+ "mentions.injectedHeader": "Arquivos referenciados (@-mentions)",
491
+ "mentions.dirHeader": "@{path} (conte\xFAdo do diret\xF3rio)",
492
+ "mentions.notFound": "(n\xE3o foi poss\xEDvel resolver @{path}: n\xE3o encontrado ou fora da allow-list)",
481
493
  "models.fetching": "Buscando modelos do OpenRouter\u2026",
482
494
  "models.fetchError": "N\xE3o foi poss\xEDvel buscar modelos: {msg}",
483
495
  "models.none": "Nenhum modelo corresponde aos filtros.",
@@ -1974,6 +1986,110 @@ ${guidance}`;
1974
1986
  return { finished: false, reason: "maxsteps", steps: maxSteps, messages, usage };
1975
1987
  }
1976
1988
 
1989
+ // src/core/context/mentions.ts
1990
+ import { readdir as readdir4, readFile as readFile7, stat as stat2 } from "fs/promises";
1991
+ import { resolve as resolve8 } from "path";
1992
+ var MAX_FILE_CHARS = 1e4;
1993
+ var MENTION_RE = /(?:^|\s)@([\w./-]+)/g;
1994
+ async function resolveMentions(task, policy) {
1995
+ const tokens2 = [...task.matchAll(MENTION_RE)].map((m) => m[1]);
1996
+ const unique = [...new Set(tokens2)];
1997
+ if (unique.length === 0) return { task, injected: [] };
1998
+ const blocks = [];
1999
+ const injected = [];
2000
+ for (const token of unique) {
2001
+ const decision = checkPath(policy, token);
2002
+ if (!decision.allowed) {
2003
+ blocks.push(`## @${token}
2004
+ ${t("mentions.notFound", { path: token })}`);
2005
+ continue;
2006
+ }
2007
+ const abs = resolve8(policy.workspace, token);
2008
+ try {
2009
+ const info = await stat2(abs);
2010
+ if (info.isDirectory()) {
2011
+ const entries = await readdir4(abs, { withFileTypes: true });
2012
+ const listing = entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).sort().join("\n");
2013
+ blocks.push(`## ${t("mentions.dirHeader", { path: decision.rel })}
2014
+ ${listing || "(empty)"}`);
2015
+ injected.push(decision.rel);
2016
+ } else {
2017
+ const raw = await readFile7(abs, "utf8");
2018
+ const content = raw.length > MAX_FILE_CHARS ? raw.slice(0, MAX_FILE_CHARS) + "\n\u2026[truncated]" : raw;
2019
+ blocks.push(`## @${decision.rel}
2020
+ \`\`\`
2021
+ ${content}
2022
+ \`\`\``);
2023
+ injected.push(decision.rel);
2024
+ }
2025
+ } catch {
2026
+ blocks.push(`## @${token}
2027
+ ${t("mentions.notFound", { path: token })}`);
2028
+ }
2029
+ }
2030
+ if (injected.length === 0) return { task, injected: [] };
2031
+ const augmented = `${task}
2032
+
2033
+ --- ${t("mentions.injectedHeader")} ---
2034
+
2035
+ ${blocks.join("\n\n")}`;
2036
+ return { task: augmented, injected };
2037
+ }
2038
+
2039
+ // src/cli/commands/json-output.ts
2040
+ var OUTPUT_PREVIEW = 500;
2041
+ function createJsonCollector() {
2042
+ const log = [];
2043
+ const filesChanged = /* @__PURE__ */ new Set();
2044
+ const events = {
2045
+ onStep(step) {
2046
+ log.push({ type: "step", step });
2047
+ },
2048
+ onAssistantText(text2) {
2049
+ if (text2.trim()) log.push({ type: "assistant", text: text2 });
2050
+ },
2051
+ onToolCall(call) {
2052
+ log.push({ type: "tool_call", name: call.name, arguments: call.arguments });
2053
+ },
2054
+ onToolResult(call, result) {
2055
+ log.push({
2056
+ type: "tool_result",
2057
+ name: call.name,
2058
+ ok: result.ok,
2059
+ output: result.output.slice(0, OUTPUT_PREVIEW)
2060
+ });
2061
+ if (result.ok && (call.name === "write_file" || call.name === "edit_file")) {
2062
+ const path = call.arguments.path;
2063
+ if (typeof path === "string") filesChanged.add(path);
2064
+ }
2065
+ },
2066
+ onCorrection(call) {
2067
+ log.push({ type: "correction", name: call.name });
2068
+ },
2069
+ onReprompt(attempt) {
2070
+ log.push({ type: "reprompt", attempt });
2071
+ },
2072
+ onUsage() {
2073
+ }
2074
+ };
2075
+ return {
2076
+ events,
2077
+ build(result) {
2078
+ return {
2079
+ result: {
2080
+ reason: result.reason,
2081
+ finished: result.finished,
2082
+ steps: result.steps,
2083
+ summary: result.summary,
2084
+ filesChanged: [...filesChanged],
2085
+ usage: result.usage
2086
+ },
2087
+ events: log
2088
+ };
2089
+ }
2090
+ };
2091
+ }
2092
+
1977
2093
  // src/ui/repl.ts
1978
2094
  import pc6 from "picocolors";
1979
2095
 
@@ -2612,10 +2728,10 @@ async function readLineTTY(prompt) {
2612
2728
  stdin.resume();
2613
2729
  stdin.on("data", onData);
2614
2730
  try {
2615
- const line = await new Promise((resolve9) => {
2616
- rl.question(prompt).then(resolve9, () => resolve9(null));
2617
- rl.on("SIGINT", () => resolve9(null));
2618
- rl.on("close", () => resolve9(null));
2731
+ const line = await new Promise((resolve10) => {
2732
+ rl.question(prompt).then(resolve10, () => resolve10(null));
2733
+ rl.on("SIGINT", () => resolve10(null));
2734
+ rl.on("close", () => resolve10(null));
2619
2735
  });
2620
2736
  return line === null ? null : store.expand(line);
2621
2737
  } finally {
@@ -3227,20 +3343,23 @@ async function run(task, opts) {
3227
3343
  const resolved2 = createProvider(active);
3228
3344
  await executeTask(taskText, resolved2, workspace, session);
3229
3345
  };
3346
+ if (opts.json && !task) throw new Error(t("run.jsonNeedsTask"));
3230
3347
  if (task) {
3231
3348
  const resolved2 = createProvider(agentConfig);
3232
- console.log(
3233
- pc8.dim(
3234
- t("run.status", {
3235
- name: resolved2.config.name,
3236
- provider: resolved2.config.provider,
3237
- model: resolved2.config.model,
3238
- toolMode: resolved2.toolMode,
3239
- mode: session.mode
3240
- })
3241
- )
3242
- );
3243
- await executeTask(task, resolved2, workspace, session);
3349
+ if (!opts.json) {
3350
+ console.log(
3351
+ pc8.dim(
3352
+ t("run.status", {
3353
+ name: resolved2.config.name,
3354
+ provider: resolved2.config.provider,
3355
+ model: resolved2.config.model,
3356
+ toolMode: resolved2.toolMode,
3357
+ mode: session.mode
3358
+ })
3359
+ )
3360
+ );
3361
+ }
3362
+ await executeTask(task, resolved2, workspace, session, opts.json ?? false);
3244
3363
  return;
3245
3364
  }
3246
3365
  const resolved = createProvider(agentConfig);
@@ -3263,15 +3382,26 @@ async function run(task, opts) {
3263
3382
  };
3264
3383
  await startRepl(ctx);
3265
3384
  }
3266
- async function executeTask(task, resolved, workspace, session) {
3385
+ async function executeTask(task, resolved, workspace, session, json = false) {
3386
+ const mention = await resolveMentions(task, {
3387
+ workspace,
3388
+ allow: session.allow,
3389
+ deny: session.deny
3390
+ });
3391
+ if (mention.injected.length > 0) {
3392
+ task = mention.task;
3393
+ if (!json) console.log(pc8.dim(`\u21B3 @ ${mention.injected.join(", ")}`));
3394
+ }
3267
3395
  const spinner3 = new Spinner();
3268
3396
  const controller = new AbortController();
3269
3397
  const cancel2 = listenForCancel(controller);
3398
+ const collector = json ? createJsonCollector() : void 0;
3270
3399
  const permissions = new PermissionEngine({
3271
3400
  mode: session.mode,
3272
3401
  policy: { workspace, allow: session.allow, deny: session.deny },
3273
3402
  allowedCommands: session.allowedCommands,
3274
- confirm: async (req) => {
3403
+ // Headless runs have no TTY for confirmations — use --mode bypass instead.
3404
+ confirm: json ? async () => false : async (req) => {
3275
3405
  spinner3.stop();
3276
3406
  cancel2.pause();
3277
3407
  const ok = await confirmAction(req);
@@ -3279,7 +3409,7 @@ async function executeTask(task, resolved, workspace, session) {
3279
3409
  return ok;
3280
3410
  }
3281
3411
  });
3282
- spinner3.start(t("ui.thinking"));
3412
+ if (!json) spinner3.start(t("ui.thinking"));
3283
3413
  let result;
3284
3414
  try {
3285
3415
  result = await runAgent({
@@ -3291,13 +3421,17 @@ async function executeTask(task, resolved, workspace, session) {
3291
3421
  history: session.history,
3292
3422
  maxSteps: session.maxSteps,
3293
3423
  signal: controller.signal,
3294
- events: renderEvents(spinner3)
3424
+ events: collector ? collector.events : renderEvents(spinner3)
3295
3425
  });
3296
3426
  } finally {
3297
3427
  spinner3.stop();
3298
3428
  cancel2.dispose();
3299
3429
  }
3300
3430
  session.history = result.messages;
3431
+ if (collector) {
3432
+ process.stdout.write(JSON.stringify(collector.build(result)) + "\n");
3433
+ return;
3434
+ }
3301
3435
  if (result.reason === "finished") {
3302
3436
  console.log(pc8.green("\n" + t("run.done", { steps: result.steps })) + (result.summary ? ` ${result.summary}` : ""));
3303
3437
  } else if (result.reason === "cancelled") {
@@ -3742,7 +3876,7 @@ async function resolveOpenRouterKey() {
3742
3876
  }
3743
3877
 
3744
3878
  // src/cli/commands/prd.ts
3745
- import { writeFile as writeFile5, readFile as readFile7 } from "fs/promises";
3879
+ import { writeFile as writeFile5, readFile as readFile8 } from "fs/promises";
3746
3880
  import { execFile } from "child_process";
3747
3881
  import { promisify as promisify2 } from "util";
3748
3882
  import pc11 from "picocolors";
@@ -3834,13 +3968,13 @@ async function withRetry(fn, opts = {}) {
3834
3968
 
3835
3969
  // src/cli/commands/cli-io.ts
3836
3970
  import { readFileSync, existsSync as existsSync2 } from "fs";
3837
- import { resolve as resolve8 } from "path";
3971
+ import { resolve as resolve9 } from "path";
3838
3972
  var GUIDE_MAX = 12e3;
3839
3973
  function readProjectGuide(files) {
3840
3974
  const parts = [];
3841
3975
  for (const file of files) {
3842
3976
  try {
3843
- const path = resolve8(process.cwd(), file);
3977
+ const path = resolve9(process.cwd(), file);
3844
3978
  if (existsSync2(path)) parts.push(`# ${file}
3845
3979
  ${readFileSync(path, "utf8").trim()}`);
3846
3980
  } catch {
@@ -3881,7 +4015,7 @@ async function prd(issueRef, opts) {
3881
4015
  }
3882
4016
  async function loadIssue(issueRef, input) {
3883
4017
  if (input) {
3884
- const raw = input === "-" ? await readStdin() : await readFile7(input, "utf8");
4018
+ const raw = input === "-" ? await readStdin() : await readFile8(input, "utf8");
3885
4019
  return normalize2(JSON.parse(stripBom(raw)));
3886
4020
  }
3887
4021
  const num = numericRef(issueRef);
@@ -3900,7 +4034,7 @@ function normalize2(raw) {
3900
4034
  }
3901
4035
 
3902
4036
  // src/cli/commands/review.ts
3903
- import { writeFile as writeFile6, readFile as readFile8 } from "fs/promises";
4037
+ import { writeFile as writeFile6, readFile as readFile9 } from "fs/promises";
3904
4038
  import { execFile as execFile2 } from "child_process";
3905
4039
  import { promisify as promisify3 } from "util";
3906
4040
  import pc12 from "picocolors";
@@ -3976,7 +4110,7 @@ async function review(prRef, opts) {
3976
4110
  }
3977
4111
  }
3978
4112
  async function loadDiff(num, input) {
3979
- if (input) return input === "-" ? readStdin() : readFile8(input, "utf8");
4113
+ if (input) return input === "-" ? readStdin() : readFile9(input, "utf8");
3980
4114
  const { stdout: stdout2 } = await exec3("gh", ["pr", "diff", num]);
3981
4115
  return stdout2;
3982
4116
  }
@@ -4048,7 +4182,7 @@ function buildProgram() {
4048
4182
  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));
4049
4183
  program.command("remove-agent").argument("<name>", t("cli.arg.removeAgentName")).description(t("cli.cmd.removeAgent")).action((name) => removeAgent(name));
4050
4184
  program.command("list-agents").alias("agents").description(t("cli.cmd.listAgents")).action(() => listAgents());
4051
- 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));
4185
+ 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));
4052
4186
  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));
4053
4187
  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));
4054
4188
  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));