@gaberrb/polypus 0.4.10 → 0.4.11

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
@@ -143,6 +143,7 @@ var en = {
143
143
  "run.autocorrect": "\u21BB tool failed \u2014 auto-correcting with extra context",
144
144
  "run.cancelled": "\u25A0 cancelled",
145
145
  "compaction.done": "context compacted: ~{before} \u2192 ~{after} tokens",
146
+ "tools.customLoaded": "loaded custom tool(s): {names}",
146
147
  "run.jsonNeedsTask": "--json requires a task argument (headless mode has no interactive REPL).",
147
148
  "review.approveAll": "approve all",
148
149
  "review.reject": "reject",
@@ -403,6 +404,7 @@ var ptBR = {
403
404
  "run.autocorrect": "\u21BB tool falhou \u2014 autocorrigindo com contexto extra",
404
405
  "run.cancelled": "\u25A0 cancelado",
405
406
  "compaction.done": "contexto compactado: ~{before} \u2192 ~{after} tokens",
407
+ "tools.customLoaded": "tool(s) customizada(s) carregada(s): {names}",
406
408
  "run.jsonNeedsTask": "--json exige um argumento de tarefa (o modo headless n\xE3o tem REPL interativo).",
407
409
  "review.approveAll": "aprovar tudo",
408
410
  "review.reject": "rejeitar",
@@ -2144,6 +2146,66 @@ ${summary.content.trim()}`
2144
2146
  return system ? [system, summaryMessage, ...tail] : [summaryMessage, ...tail];
2145
2147
  }
2146
2148
 
2149
+ // src/core/agent/hooks.ts
2150
+ import { exec as exec2 } from "child_process";
2151
+ import { readFile as readFile8 } from "fs/promises";
2152
+ import { join as join4 } from "path";
2153
+ import { promisify as promisify2 } from "util";
2154
+ import { z as z8 } from "zod";
2155
+ var execAsync2 = promisify2(exec2);
2156
+ var HOOK_TIMEOUT = 12e4;
2157
+ var HooksSchema = z8.object({
2158
+ /** Shell command run after a successful write_file. `{path}` is substituted. */
2159
+ afterWrite: z8.string().optional(),
2160
+ /** Shell command run after a successful edit_file. `{path}` is substituted. */
2161
+ afterEdit: z8.string().optional(),
2162
+ /** Shell command run after any successful mutating tool. `{tool}`/`{path}` substituted. */
2163
+ afterTool: z8.string().optional(),
2164
+ /** Block run_command when the command contains any of these substrings. */
2165
+ beforeCommand: z8.object({ deny: z8.array(z8.string()).default([]) }).optional()
2166
+ });
2167
+ async function loadHooks(workspace) {
2168
+ try {
2169
+ const raw = await readFile8(join4(workspace, ".poly", "hooks.json"), "utf8");
2170
+ const parsed = HooksSchema.safeParse(JSON.parse(raw));
2171
+ return parsed.success ? parsed.data : void 0;
2172
+ } catch {
2173
+ return void 0;
2174
+ }
2175
+ }
2176
+ function screenCommandHook(hooks, command) {
2177
+ const deny = hooks?.beforeCommand?.deny ?? [];
2178
+ for (const needle of deny) {
2179
+ if (needle && command.includes(needle)) {
2180
+ return { blocked: true, reason: `matches deny rule "${needle}"` };
2181
+ }
2182
+ }
2183
+ return { blocked: false };
2184
+ }
2185
+ function substitute(template, call) {
2186
+ const path = typeof call.arguments.path === "string" ? call.arguments.path : "";
2187
+ return template.replace(/\{path\}/g, path).replace(/\{tool\}/g, call.name);
2188
+ }
2189
+ async function runAfterHook(hooks, call, workspace) {
2190
+ if (!hooks) return void 0;
2191
+ const commands = [];
2192
+ if (call.name === "write_file" && hooks.afterWrite) commands.push({ label: "afterWrite", cmd: hooks.afterWrite });
2193
+ if (call.name === "edit_file" && hooks.afterEdit) commands.push({ label: "afterEdit", cmd: hooks.afterEdit });
2194
+ if (hooks.afterTool) commands.push({ label: "afterTool", cmd: hooks.afterTool });
2195
+ if (commands.length === 0) return void 0;
2196
+ const notes = [];
2197
+ for (const { label, cmd } of commands) {
2198
+ const resolved = substitute(cmd, call);
2199
+ try {
2200
+ await execAsync2(resolved, { cwd: workspace, timeout: HOOK_TIMEOUT, windowsHide: true });
2201
+ notes.push(`\u21AA hook ${label} ok`);
2202
+ } catch (err) {
2203
+ notes.push(`\u21AA hook ${label} failed: ${err.message.split("\n")[0]}`);
2204
+ }
2205
+ }
2206
+ return notes.join("\n");
2207
+ }
2208
+
2147
2209
  // src/core/agent/loop.ts
2148
2210
  function looksLikeStall(text2) {
2149
2211
  const lc = text2.toLowerCase();
@@ -2184,7 +2246,13 @@ async function runAgent(opts) {
2184
2246
  const { agent, permissions, events } = opts;
2185
2247
  const maxSteps = opts.maxSteps ?? 30;
2186
2248
  const maxReprompts = opts.maxReprompts ?? 3;
2187
- const driver = makeDriver(agent.toolMode, toolSpecs());
2249
+ const extra = opts.extraTools ?? [];
2250
+ const extraByName = new Map(extra.map((tl) => [tl.spec.name, tl]));
2251
+ const baseSpecs = toolSpecs();
2252
+ const finishSpec = baseSpecs[baseSpecs.length - 1];
2253
+ const allSpecs = [...baseSpecs.slice(0, -1), ...extra.map((tl) => tl.spec), finishSpec];
2254
+ const resolveTool = (name) => extraByName.get(name) ?? getTool(name);
2255
+ const driver = makeDriver(agent.toolMode, allSpecs);
2188
2256
  const ctx = { workspace: opts.workspace, permissions };
2189
2257
  const seeding = !(opts.history && opts.history.length > 0);
2190
2258
  const promptContext = seeding && opts.promptContext.projectInstructions === void 0 ? { ...opts.promptContext, projectInstructions: await loadProjectInstructions(opts.workspace) } : opts.promptContext;
@@ -2265,8 +2333,21 @@ async function runAgent(opts) {
2265
2333
  const summary = String(call.arguments.summary ?? "").trim();
2266
2334
  return { finished: true, reason: "finished", summary, steps: step, messages, usage: usage2 };
2267
2335
  }
2268
- const tool = getTool(call.name);
2269
- const result = tool ? await tool.run(call.arguments, ctx) : { ok: false, output: `Unknown tool "${call.name}". Available: ${toolSpecs().map((t2) => t2.name).join(", ")}` };
2336
+ const tool = resolveTool(call.name);
2337
+ const hookScreen = call.name === "run_command" ? screenCommandHook(opts.hooks, String(call.arguments.command ?? "")) : { blocked: false };
2338
+ let result;
2339
+ if (hookScreen.blocked) {
2340
+ result = { ok: false, output: `Command blocked by hook: ${hookScreen.reason}` };
2341
+ } else if (tool) {
2342
+ result = await tool.run(call.arguments, ctx);
2343
+ if (result.ok) {
2344
+ const note2 = await runAfterHook(opts.hooks, call, opts.workspace);
2345
+ if (note2) result = { ...result, output: `${result.output}
2346
+ ${note2}` };
2347
+ }
2348
+ } else {
2349
+ result = { ok: false, output: `Unknown tool "${call.name}". Available: ${allSpecs.map((t2) => t2.name).join(", ")}` };
2350
+ }
2270
2351
  events?.onToolResult?.(call, result);
2271
2352
  const sig = `${call.name}:${JSON.stringify(call.arguments)}`;
2272
2353
  let resultText = result.output;
@@ -2310,7 +2391,7 @@ ${guidance}`;
2310
2391
  }
2311
2392
 
2312
2393
  // src/core/context/mentions.ts
2313
- import { readdir as readdir4, readFile as readFile8, stat as stat2 } from "fs/promises";
2394
+ import { readdir as readdir4, readFile as readFile9, stat as stat2 } from "fs/promises";
2314
2395
  import { resolve as resolve9 } from "path";
2315
2396
  var MAX_FILE_CHARS = 1e4;
2316
2397
  var MENTION_RE = /(?:^|\s)@([\w./-]+)/g;
@@ -2337,7 +2418,7 @@ ${t("mentions.notFound", { path: token })}`);
2337
2418
  ${listing || "(empty)"}`);
2338
2419
  injected.push(decision.rel);
2339
2420
  } else {
2340
- const raw = await readFile8(abs, "utf8");
2421
+ const raw = await readFile9(abs, "utf8");
2341
2422
  const content = raw.length > MAX_FILE_CHARS ? raw.slice(0, MAX_FILE_CHARS) + "\n\u2026[truncated]" : raw;
2342
2423
  blocks.push(`## @${decision.rel}
2343
2424
  \`\`\`
@@ -2360,16 +2441,16 @@ ${blocks.join("\n\n")}`;
2360
2441
  }
2361
2442
 
2362
2443
  // src/core/agent/verify.ts
2363
- import { exec as exec2 } from "child_process";
2364
- import { readFile as readFile9 } from "fs/promises";
2444
+ import { exec as exec3 } from "child_process";
2445
+ import { readFile as readFile10 } from "fs/promises";
2365
2446
  import { resolve as resolve10 } from "path";
2366
- import { promisify as promisify2 } from "util";
2367
- var execAsync2 = promisify2(exec2);
2447
+ import { promisify as promisify3 } from "util";
2448
+ var execAsync3 = promisify3(exec3);
2368
2449
  var MAX_OUTPUT3 = 8e3;
2369
2450
  var CHECK_SCRIPTS = ["typecheck", "build", "test"];
2370
2451
  async function detectChecks(workspace) {
2371
2452
  try {
2372
- const raw = await readFile9(resolve10(workspace, "package.json"), "utf8");
2453
+ const raw = await readFile10(resolve10(workspace, "package.json"), "utf8");
2373
2454
  const scripts = JSON.parse(raw).scripts ?? {};
2374
2455
  return CHECK_SCRIPTS.filter((s) => typeof scripts[s] === "string").map((s) => `npm run ${s}`);
2375
2456
  } catch {
@@ -2380,7 +2461,7 @@ async function runChecks(workspace, commands) {
2380
2461
  const results = [];
2381
2462
  for (const command of commands) {
2382
2463
  try {
2383
- const { stdout: stdout2, stderr } = await execAsync2(command, {
2464
+ const { stdout: stdout2, stderr } = await execAsync3(command, {
2384
2465
  cwd: workspace,
2385
2466
  timeout: 3e5,
2386
2467
  maxBuffer: 10 * 1024 * 1024,
@@ -2412,8 +2493,8 @@ function clamp2(s) {
2412
2493
  }
2413
2494
 
2414
2495
  // src/core/agent/usage.ts
2415
- import { appendFile, mkdir as mkdir3, readFile as readFile10 } from "fs/promises";
2416
- import { join as join4 } from "path";
2496
+ import { appendFile, mkdir as mkdir3, readFile as readFile11 } from "fs/promises";
2497
+ import { join as join5 } from "path";
2417
2498
 
2418
2499
  // src/core/providers/openrouter.ts
2419
2500
  var MODELS_URL = "https://openrouter.ai/api/v1/models";
@@ -2513,7 +2594,7 @@ function fmtUsd(n) {
2513
2594
  return `US$${n.toFixed(2)}`;
2514
2595
  }
2515
2596
  function usagePath() {
2516
- return join4(configDir(), "usage.jsonl");
2597
+ return join5(configDir(), "usage.jsonl");
2517
2598
  }
2518
2599
  async function recordUsage(entry) {
2519
2600
  try {
@@ -2525,7 +2606,7 @@ async function recordUsage(entry) {
2525
2606
  async function aggregateUsage() {
2526
2607
  let text2 = "";
2527
2608
  try {
2528
- text2 = await readFile10(usagePath(), "utf8");
2609
+ text2 = await readFile11(usagePath(), "utf8");
2529
2610
  } catch {
2530
2611
  return { days: [], total: emptyBucket("total") };
2531
2612
  }
@@ -2559,10 +2640,10 @@ function accumulate(bucket, e) {
2559
2640
  }
2560
2641
 
2561
2642
  // src/core/agent/session-store.ts
2562
- import { mkdir as mkdir4, readFile as readFile11, readdir as readdir5, writeFile as writeFile4 } from "fs/promises";
2563
- import { join as join5 } from "path";
2643
+ import { mkdir as mkdir4, readFile as readFile12, readdir as readdir5, writeFile as writeFile4 } from "fs/promises";
2644
+ import { join as join6 } from "path";
2564
2645
  function sessionsDir() {
2565
- return join5(configDir(), "sessions");
2646
+ return join6(configDir(), "sessions");
2566
2647
  }
2567
2648
  function newSessionId() {
2568
2649
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -2570,7 +2651,7 @@ function newSessionId() {
2570
2651
  return `${stamp}-${rand}`;
2571
2652
  }
2572
2653
  function sessionPath(id) {
2573
- return join5(sessionsDir(), `${id}.json`);
2654
+ return join6(sessionsDir(), `${id}.json`);
2574
2655
  }
2575
2656
  async function saveSession(record) {
2576
2657
  await mkdir4(sessionsDir(), { recursive: true });
@@ -2582,7 +2663,7 @@ async function saveSession(record) {
2582
2663
  }
2583
2664
  async function loadSession(id) {
2584
2665
  try {
2585
- return JSON.parse(await readFile11(sessionPath(id), "utf8"));
2666
+ return JSON.parse(await readFile12(sessionPath(id), "utf8"));
2586
2667
  } catch {
2587
2668
  return void 0;
2588
2669
  }
@@ -2597,7 +2678,7 @@ async function listSessions() {
2597
2678
  const summaries = [];
2598
2679
  for (const f of files) {
2599
2680
  try {
2600
- const r = JSON.parse(await readFile11(join5(sessionsDir(), f), "utf8"));
2681
+ const r = JSON.parse(await readFile12(join6(sessionsDir(), f), "utf8"));
2601
2682
  summaries.push({
2602
2683
  id: r.id,
2603
2684
  updatedAt: r.updatedAt,
@@ -2621,6 +2702,83 @@ function deriveTitle(messages) {
2621
2702
  return text2.length > 60 ? text2.slice(0, 60) + "\u2026" : text2 || "(untitled)";
2622
2703
  }
2623
2704
 
2705
+ // src/core/tools/custom.ts
2706
+ import { exec as exec4 } from "child_process";
2707
+ import { readFile as readFile13, readdir as readdir6 } from "fs/promises";
2708
+ import { join as join7 } from "path";
2709
+ import { promisify as promisify4 } from "util";
2710
+ import { z as z9 } from "zod";
2711
+ var execAsync4 = promisify4(exec4);
2712
+ var MAX_OUTPUT4 = 2e4;
2713
+ var CustomToolSchema = z9.object({
2714
+ name: z9.string().min(1).regex(/^[a-z][a-z0-9_]*$/i, "tool name must be alphanumeric/underscore"),
2715
+ description: z9.string().min(1),
2716
+ /** JSON-schema object for the tool parameters (advertised to the model). */
2717
+ parameters: z9.record(z9.unknown()).optional(),
2718
+ /** Shell command template; `{argName}` placeholders are filled from the call arguments. */
2719
+ command: z9.string().min(1)
2720
+ });
2721
+ function makeCommandTool(def) {
2722
+ return {
2723
+ mutating: true,
2724
+ spec: {
2725
+ name: def.name,
2726
+ description: def.description,
2727
+ parameters: def.parameters ?? { type: "object", properties: {} }
2728
+ },
2729
+ async run(rawArgs, ctx) {
2730
+ const command = fillTemplate(def.command, rawArgs);
2731
+ const decision = await ctx.permissions.authorizeCommand(command);
2732
+ if (!decision.allowed) return { ok: false, output: `Command denied: ${decision.reason}` };
2733
+ try {
2734
+ const { stdout: stdout2, stderr } = await execAsync4(command, {
2735
+ cwd: ctx.workspace,
2736
+ timeout: 12e4,
2737
+ maxBuffer: 10 * 1024 * 1024,
2738
+ windowsHide: true
2739
+ });
2740
+ return { ok: true, output: clamp3(`${stdout2}${stderr ? `
2741
+ [stderr]
2742
+ ${stderr}` : ""}`.trim() || "(no output)") };
2743
+ } catch (err) {
2744
+ const e = err;
2745
+ return {
2746
+ ok: false,
2747
+ output: clamp3(`Command failed (exit ${e.code ?? "?"}): ${e.message}
2748
+ ${e.stdout ?? ""}${e.stderr ?? ""}`)
2749
+ };
2750
+ }
2751
+ }
2752
+ };
2753
+ }
2754
+ async function loadCustomTools(workspace) {
2755
+ let files;
2756
+ try {
2757
+ files = (await readdir6(join7(workspace, ".poly", "tools"))).filter((f) => f.endsWith(".json"));
2758
+ } catch {
2759
+ return [];
2760
+ }
2761
+ const tools = [];
2762
+ for (const f of files) {
2763
+ try {
2764
+ const raw = await readFile13(join7(workspace, ".poly", "tools", f), "utf8");
2765
+ const parsed = CustomToolSchema.safeParse(JSON.parse(raw));
2766
+ if (parsed.success) tools.push(makeCommandTool(parsed.data));
2767
+ } catch {
2768
+ }
2769
+ }
2770
+ return tools;
2771
+ }
2772
+ function fillTemplate(template, args) {
2773
+ return template.replace(/\{(\w+)\}/g, (_, key) => {
2774
+ const v = args[key];
2775
+ return v === void 0 || v === null ? "" : String(v);
2776
+ });
2777
+ }
2778
+ function clamp3(s) {
2779
+ return s.length > MAX_OUTPUT4 ? s.slice(0, MAX_OUTPUT4) + "\n\u2026[truncated]" : s;
2780
+ }
2781
+
2624
2782
  // src/cli/commands/json-output.ts
2625
2783
  var OUTPUT_PREVIEW = 500;
2626
2784
  function createJsonCollector() {
@@ -3419,7 +3577,7 @@ import pc7 from "picocolors";
3419
3577
  // src/core/git/worktree.ts
3420
3578
  import { mkdtemp } from "fs/promises";
3421
3579
  import { tmpdir } from "os";
3422
- import { join as join6 } from "path";
3580
+ import { join as join8 } from "path";
3423
3581
  import { simpleGit } from "simple-git";
3424
3582
  async function ensureRepo(workspace) {
3425
3583
  const git = simpleGit(workspace);
@@ -3440,7 +3598,7 @@ async function identityArgs(git) {
3440
3598
  }
3441
3599
  async function createWorktree(git, label) {
3442
3600
  const branch = `polypus/${label}-${Date.now().toString(36)}`;
3443
- const path = await mkdtemp(join6(tmpdir(), "polypus-wt-"));
3601
+ const path = await mkdtemp(join8(tmpdir(), "polypus-wt-"));
3444
3602
  await git.raw(["worktree", "add", "-b", branch, path, "HEAD"]);
3445
3603
  return { path, branch };
3446
3604
  }
@@ -3991,6 +4149,10 @@ async function executeTask(task, resolved, workspace, session, json = false, ver
3991
4149
  return ok;
3992
4150
  }
3993
4151
  });
4152
+ const [extraTools, hooks] = await Promise.all([loadCustomTools(workspace), loadHooks(workspace)]);
4153
+ if (!json && extraTools.length > 0) {
4154
+ console.log(pc8.dim(t("tools.customLoaded", { names: extraTools.map((tl) => tl.spec.name).join(", ") })));
4155
+ }
3994
4156
  const runOnce = (taskText) => runAgent({
3995
4157
  task: taskText,
3996
4158
  workspace,
@@ -4000,6 +4162,8 @@ async function executeTask(task, resolved, workspace, session, json = false, ver
4000
4162
  history: session.history,
4001
4163
  maxSteps: session.maxSteps,
4002
4164
  compactThresholdTokens: compactionThreshold(),
4165
+ extraTools,
4166
+ hooks,
4003
4167
  signal: controller.signal,
4004
4168
  events
4005
4169
  });
@@ -4204,7 +4368,7 @@ import pc9 from "picocolors";
4204
4368
 
4205
4369
  // src/core/scaffold/init.ts
4206
4370
  import { mkdir as mkdir5, writeFile as writeFile5, access } from "fs/promises";
4207
- import { dirname as dirname3, join as join7 } from "path";
4371
+ import { dirname as dirname3, join as join9 } from "path";
4208
4372
 
4209
4373
  // src/core/scaffold/templates.ts
4210
4374
  function polyTemplates(locale) {
@@ -4447,7 +4611,7 @@ async function scaffoldPoly(workspace, opts) {
4447
4611
  const skipped = [];
4448
4612
  for (const [rel, content] of Object.entries(templates)) {
4449
4613
  const display = `.poly/${rel}`;
4450
- const abs = join7(workspace, ".poly", ...rel.split("/"));
4614
+ const abs = join9(workspace, ".poly", ...rel.split("/"));
4451
4615
  if (!opts.force && await exists(abs)) {
4452
4616
  skipped.push(display);
4453
4617
  continue;
@@ -4587,9 +4751,9 @@ async function sessions() {
4587
4751
  }
4588
4752
 
4589
4753
  // src/cli/commands/prd.ts
4590
- import { writeFile as writeFile6, readFile as readFile12 } from "fs/promises";
4754
+ import { writeFile as writeFile6, readFile as readFile14 } from "fs/promises";
4591
4755
  import { execFile } from "child_process";
4592
- import { promisify as promisify3 } from "util";
4756
+ import { promisify as promisify5 } from "util";
4593
4757
  import pc13 from "picocolors";
4594
4758
 
4595
4759
  // src/core/agent/prd.ts
@@ -4711,7 +4875,7 @@ function stripBom(s) {
4711
4875
  }
4712
4876
 
4713
4877
  // src/cli/commands/prd.ts
4714
- var exec3 = promisify3(execFile);
4878
+ var exec5 = promisify5(execFile);
4715
4879
  async function prd(issueRef, opts) {
4716
4880
  const issue = await loadIssue(issueRef, opts.input);
4717
4881
  const { provider } = resolveFreeProvider(opts.model ?? DEFAULT_PRD_MODEL);
@@ -4726,11 +4890,11 @@ async function prd(issueRef, opts) {
4726
4890
  }
4727
4891
  async function loadIssue(issueRef, input) {
4728
4892
  if (input) {
4729
- const raw = input === "-" ? await readStdin() : await readFile12(input, "utf8");
4893
+ const raw = input === "-" ? await readStdin() : await readFile14(input, "utf8");
4730
4894
  return normalize2(JSON.parse(stripBom(raw)));
4731
4895
  }
4732
4896
  const num = numericRef(issueRef);
4733
- const { stdout: stdout2 } = await exec3("gh", ["issue", "view", num, "--json", "number,title,body,comments"]);
4897
+ const { stdout: stdout2 } = await exec5("gh", ["issue", "view", num, "--json", "number,title,body,comments"]);
4734
4898
  const data = normalize2(JSON.parse(stdout2));
4735
4899
  data.number ??= Number(num);
4736
4900
  return data;
@@ -4745,9 +4909,9 @@ function normalize2(raw) {
4745
4909
  }
4746
4910
 
4747
4911
  // src/cli/commands/review.ts
4748
- import { writeFile as writeFile7, readFile as readFile13 } from "fs/promises";
4912
+ import { writeFile as writeFile7, readFile as readFile15 } from "fs/promises";
4749
4913
  import { execFile as execFile2 } from "child_process";
4750
- import { promisify as promisify4 } from "util";
4914
+ import { promisify as promisify6 } from "util";
4751
4915
  import pc14 from "picocolors";
4752
4916
 
4753
4917
  // src/core/agent/review.ts
@@ -4805,7 +4969,7 @@ ${projectGuide}`
4805
4969
  }
4806
4970
 
4807
4971
  // src/cli/commands/review.ts
4808
- var exec4 = promisify4(execFile2);
4972
+ var exec6 = promisify6(execFile2);
4809
4973
  async function review(prRef, opts) {
4810
4974
  const num = opts.input ? prRef.replace(/^#/, "") : numericRef(prRef);
4811
4975
  const diff = await loadDiff(num, opts.input);
@@ -4821,19 +4985,19 @@ async function review(prRef, opts) {
4821
4985
  }
4822
4986
  }
4823
4987
  async function loadDiff(num, input) {
4824
- if (input) return input === "-" ? readStdin() : readFile13(input, "utf8");
4825
- const { stdout: stdout2 } = await exec4("gh", ["pr", "diff", num]);
4988
+ if (input) return input === "-" ? readStdin() : readFile15(input, "utf8");
4989
+ const { stdout: stdout2 } = await exec6("gh", ["pr", "diff", num]);
4826
4990
  return stdout2;
4827
4991
  }
4828
4992
  async function loadMeta(num, input) {
4829
4993
  if (input) return { number: Number(num) || void 0, title: `PR ${num}`, body: "" };
4830
- const { stdout: stdout2 } = await exec4("gh", ["pr", "view", num, "--json", "number,title,body"]);
4994
+ const { stdout: stdout2 } = await exec6("gh", ["pr", "view", num, "--json", "number,title,body"]);
4831
4995
  const raw = JSON.parse(stdout2);
4832
4996
  return { number: raw.number, title: raw.title ?? "", body: raw.body ?? "" };
4833
4997
  }
4834
4998
 
4835
4999
  // src/cli/index.ts
4836
- import { join as join8 } from "path";
5000
+ import { join as join10 } from "path";
4837
5001
 
4838
5002
  // src/core/config/dotenv.ts
4839
5003
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
@@ -4904,7 +5068,7 @@ function buildProgram() {
4904
5068
  }
4905
5069
  async function main() {
4906
5070
  try {
4907
- loadDotenv([join8(configDir(), ".env"), join8(process.cwd(), ".env")]);
5071
+ loadDotenv([join10(configDir(), ".env"), join10(process.cwd(), ".env")]);
4908
5072
  await resolveLocale();
4909
5073
  await buildProgram().parseAsync(process.argv);
4910
5074
  } catch (err) {