@cosmicstack/mercury-agent 0.3.0 → 0.3.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/dist/index.js CHANGED
@@ -14,8 +14,12 @@ import { join } from "path";
14
14
  import { homedir } from "os";
15
15
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
16
16
  import { config as loadDotenv } from "dotenv";
17
- loadDotenv();
18
17
  var MERCURY_HOME = join(homedir(), ".mercury");
18
+ loadDotenv();
19
+ var mercuryEnvPath = join(MERCURY_HOME, ".env");
20
+ if (existsSync(mercuryEnvPath)) {
21
+ loadDotenv({ path: mercuryEnvPath });
22
+ }
19
23
  function getMercuryHome() {
20
24
  return process.env.MERCURY_HOME || MERCURY_HOME;
21
25
  }
@@ -75,7 +79,9 @@ function getDefaultConfig() {
75
79
  },
76
80
  github: {
77
81
  username: getEnv("GITHUB_USERNAME", ""),
78
- email: getEnv("GITHUB_EMAIL", "")
82
+ email: getEnv("GITHUB_EMAIL", ""),
83
+ defaultOwner: getEnv("GITHUB_DEFAULT_OWNER", ""),
84
+ defaultRepo: getEnv("GITHUB_DEFAULT_REPO", "")
79
85
  },
80
86
  memory: {
81
87
  dir: getEnv("MEMORY_DIR", join(home, "memory")),
@@ -1006,6 +1012,18 @@ You can override this:
1006
1012
  if (this.tokenBudget.getUsagePercentage() > 70) {
1007
1013
  prompt += "\nBe concise to conserve tokens.";
1008
1014
  }
1015
+ const toolNames = this.capabilities.getToolNames();
1016
+ const githubTools = ["create_pr", "review_pr", "list_issues", "create_issue", "github_api"];
1017
+ const hasGitHub = githubTools.some((t) => toolNames.includes(t));
1018
+ if (hasGitHub) {
1019
+ let githubHint = "\n\nGitHub companion is active. You can create pull requests, review PRs, manage issues, and use the GitHub API.";
1020
+ const { defaultOwner, defaultRepo } = this.config.github;
1021
+ if (defaultOwner && defaultRepo) {
1022
+ githubHint += ` Default repo: ${defaultOwner}/${defaultRepo}. Use this when the user doesn't specify a repo.`;
1023
+ }
1024
+ githubHint += ' When the user says "create a PR", use create_pr. When they ask about issues, use list_issues or create_issue. When they ask to review a PR, use review_pr. Always specify owner and repo parameters.';
1025
+ prompt += githubHint;
1026
+ }
1009
1027
  return prompt;
1010
1028
  }
1011
1029
  async processInternalPrompt(prompt, channelId, channelType) {
@@ -1709,16 +1727,16 @@ var CLIChannel = class extends BaseChannel {
1709
1727
  }
1710
1728
  }
1711
1729
  async prompt(question) {
1712
- return new Promise((resolve10) => {
1713
- this.rl?.question(question, (answer) => resolve10(answer.trim()));
1730
+ return new Promise((resolve11) => {
1731
+ this.rl?.question(question, (answer) => resolve11(answer.trim()));
1714
1732
  });
1715
1733
  }
1716
1734
  async askPermission(prompt) {
1717
- return new Promise((resolve10) => {
1735
+ return new Promise((resolve11) => {
1718
1736
  console.log("");
1719
1737
  console.log(chalk2.yellow(` \u26A0 ${prompt}`));
1720
1738
  this.rl?.question(chalk2.yellow(" > "), (answer) => {
1721
- resolve10(answer.trim());
1739
+ resolve11(answer.trim());
1722
1740
  });
1723
1741
  });
1724
1742
  }
@@ -1981,15 +1999,15 @@ var TelegramChannel = class extends BaseChannel {
1981
1999
  reply_markup: keyboard
1982
2000
  });
1983
2001
  }
1984
- return new Promise((resolve10) => {
1985
- this.pendingApprovals.set(`${id}:yes`, () => resolve10("yes"));
1986
- this.pendingApprovals.set(`${id}:always`, () => resolve10("always"));
1987
- this.pendingApprovals.set(`${id}:no`, () => resolve10("no"));
2002
+ return new Promise((resolve11) => {
2003
+ this.pendingApprovals.set(`${id}:yes`, () => resolve11("yes"));
2004
+ this.pendingApprovals.set(`${id}:always`, () => resolve11("always"));
2005
+ this.pendingApprovals.set(`${id}:no`, () => resolve11("no"));
1988
2006
  setTimeout(() => {
1989
2007
  this.pendingApprovals.delete(`${id}:yes`);
1990
2008
  this.pendingApprovals.delete(`${id}:always`);
1991
2009
  this.pendingApprovals.delete(`${id}:no`);
1992
- resolve10("no");
2010
+ resolve11("no");
1993
2011
  }, 12e4);
1994
2012
  });
1995
2013
  }
@@ -2213,7 +2231,7 @@ var TokenBudget = class {
2213
2231
 
2214
2232
  // src/capabilities/permissions.ts
2215
2233
  import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
2216
- import { join as join6, resolve, sep } from "path";
2234
+ import { join as join6, resolve as resolve2, sep } from "path";
2217
2235
  import { homedir as homedir2 } from "os";
2218
2236
  import { parse as parseYaml3, stringify as stringifyYaml3 } from "yaml";
2219
2237
  var DEFAULT_MANIFEST = {
@@ -2396,7 +2414,7 @@ var PermissionManager = class {
2396
2414
  if (!fs3.enabled) {
2397
2415
  return { allowed: false, reason: "Filesystem capability is disabled" };
2398
2416
  }
2399
- const resolved = resolve(path3);
2417
+ const resolved = resolve2(path3);
2400
2418
  const scope = this.findScope(resolved);
2401
2419
  const tempScope = this.findTempScope(resolved);
2402
2420
  if (scope) {
@@ -2486,7 +2504,7 @@ var PermissionManager = class {
2486
2504
  return this.manifest.capabilities.git.enabled && this.manifest.capabilities.git.approveWrite;
2487
2505
  }
2488
2506
  addScope(path3, read, write) {
2489
- const resolved = resolve(path3);
2507
+ const resolved = resolve2(path3);
2490
2508
  const existing = this.findScope(resolved);
2491
2509
  if (existing) {
2492
2510
  existing.read = existing.read || read;
@@ -2504,7 +2522,7 @@ var PermissionManager = class {
2504
2522
  findScope(resolvedPath) {
2505
2523
  const scopes = this.manifest.capabilities.filesystem.scopes;
2506
2524
  for (const scope of scopes) {
2507
- const scopeResolved = resolve(scope.path.replace(/^~/, homedir2()));
2525
+ const scopeResolved = resolve2(scope.path.replace(/^~/, homedir2()));
2508
2526
  if (resolvedPath === scopeResolved || resolvedPath.startsWith(scopeResolved + sep)) {
2509
2527
  return scope;
2510
2528
  }
@@ -2531,13 +2549,13 @@ Allow access?`;
2531
2549
  return { allowed: false, reason: `Permission denied for ${mode} access to ${path3}` };
2532
2550
  }
2533
2551
  addTempScope(path3, read, write) {
2534
- const resolved = resolve(path3);
2552
+ const resolved = resolve2(path3);
2535
2553
  this.tempScopes.push({ path: resolved, read, write });
2536
2554
  logger.info({ path: resolved, read, write }, "Temp permission scope added (session only)");
2537
2555
  }
2538
2556
  findTempScope(resolvedPath) {
2539
2557
  for (const scope of this.tempScopes) {
2540
- const scopeResolved = resolve(scope.path.replace(/^~/, homedir2()));
2558
+ const scopeResolved = resolve2(scope.path.replace(/^~/, homedir2()));
2541
2559
  if (resolvedPath === scopeResolved || resolvedPath.startsWith(scopeResolved + sep)) {
2542
2560
  return scope;
2543
2561
  }
@@ -2563,7 +2581,7 @@ Allow access?`;
2563
2581
  for (const p of pathPatterns) {
2564
2582
  const match = command.match(p);
2565
2583
  if (match) {
2566
- const candidate = resolve(match[1].replace(/^~/, homedir2()));
2584
+ const candidate = resolve2(match[1].replace(/^~/, homedir2()));
2567
2585
  if (!candidate.startsWith(this.cwd)) {
2568
2586
  return candidate;
2569
2587
  }
@@ -2599,7 +2617,7 @@ Allow access?`;
2599
2617
  import { tool } from "ai";
2600
2618
  import { z } from "zod";
2601
2619
  import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
2602
- import { resolve as resolve2 } from "path";
2620
+ import { resolve as resolve3 } from "path";
2603
2621
  function createReadFileTool(permissions) {
2604
2622
  return tool({
2605
2623
  description: "Read the contents of a file. The path must be within an allowed scope.",
@@ -2607,10 +2625,10 @@ function createReadFileTool(permissions) {
2607
2625
  path: z.string().describe("Absolute or relative path to the file")
2608
2626
  }),
2609
2627
  execute: async ({ path: path3 }) => {
2610
- const resolved = resolve2(path3);
2628
+ const resolved = resolve3(path3);
2611
2629
  const check = await permissions.checkFsAccess(resolved, "read");
2612
2630
  if (!check.allowed) {
2613
- const parentDir = resolve2(resolved, "..");
2631
+ const parentDir = resolve3(resolved, "..");
2614
2632
  return `Error: Permission denied for read access to ${resolved}. Use the approve_scope tool with path="${parentDir}" and mode="read" to request access from the user.`;
2615
2633
  }
2616
2634
  if (!existsSync7(resolved)) {
@@ -2636,7 +2654,7 @@ function createReadFileTool(permissions) {
2636
2654
  import { tool as tool2 } from "ai";
2637
2655
  import { z as z2 } from "zod";
2638
2656
  import { existsSync as existsSync8, writeFileSync as writeFileSync7 } from "fs";
2639
- import { resolve as resolve3 } from "path";
2657
+ import { resolve as resolve4 } from "path";
2640
2658
  function createWriteFileTool(permissions) {
2641
2659
  return tool2({
2642
2660
  description: "Write content to an existing file. The path must be within a writable scope.",
@@ -2645,10 +2663,10 @@ function createWriteFileTool(permissions) {
2645
2663
  content: z2.string().describe("The content to write to the file")
2646
2664
  }),
2647
2665
  execute: async ({ path: path3, content }) => {
2648
- const resolved = resolve3(path3);
2666
+ const resolved = resolve4(path3);
2649
2667
  const check = await permissions.checkFsAccess(resolved, "write");
2650
2668
  if (!check.allowed) {
2651
- const parentDir = resolve3(resolved, "..");
2669
+ const parentDir = resolve4(resolved, "..");
2652
2670
  return `Error: Permission denied for write access to ${resolved}. Use the approve_scope tool with path="${parentDir}" and mode="write" to request access from the user.`;
2653
2671
  }
2654
2672
  if (!existsSync8(resolved)) {
@@ -2668,7 +2686,7 @@ function createWriteFileTool(permissions) {
2668
2686
  import { tool as tool3 } from "ai";
2669
2687
  import { z as z3 } from "zod";
2670
2688
  import { existsSync as existsSync9, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "fs";
2671
- import { resolve as resolve4, dirname as dirname2 } from "path";
2689
+ import { resolve as resolve5, dirname as dirname2 } from "path";
2672
2690
  function createCreateFileTool(permissions) {
2673
2691
  return tool3({
2674
2692
  description: "Create a new file with the given content. Also creates parent directories if needed. The path must be within a writable scope.",
@@ -2677,10 +2695,10 @@ function createCreateFileTool(permissions) {
2677
2695
  content: z3.string().describe("The content of the new file")
2678
2696
  }),
2679
2697
  execute: async ({ path: path3, content }) => {
2680
- const resolved = resolve4(path3);
2698
+ const resolved = resolve5(path3);
2681
2699
  const check = await permissions.checkFsAccess(resolved, "write");
2682
2700
  if (!check.allowed) {
2683
- const parentDir = resolve4(resolved, "..");
2701
+ const parentDir = resolve5(resolved, "..");
2684
2702
  return `Error: Permission denied for write access to ${resolved}. Use the approve_scope tool with path="${parentDir}" and mode="write" to request access from the user.`;
2685
2703
  }
2686
2704
  if (existsSync9(resolved)) {
@@ -2704,7 +2722,7 @@ function createCreateFileTool(permissions) {
2704
2722
  import { tool as tool4 } from "ai";
2705
2723
  import { z as z4 } from "zod";
2706
2724
  import { existsSync as existsSync10, readdirSync as readdirSync2, statSync } from "fs";
2707
- import { resolve as resolve5 } from "path";
2725
+ import { resolve as resolve6 } from "path";
2708
2726
  function createListDirTool(permissions) {
2709
2727
  return tool4({
2710
2728
  description: "List the contents of a directory. Shows file names, types, and sizes.",
@@ -2712,7 +2730,7 @@ function createListDirTool(permissions) {
2712
2730
  path: z4.string().describe("Absolute or relative path to the directory")
2713
2731
  }),
2714
2732
  execute: async ({ path: path3 }) => {
2715
- const resolved = resolve5(path3);
2733
+ const resolved = resolve6(path3);
2716
2734
  const check = await permissions.checkFsAccess(resolved, "read");
2717
2735
  if (!check.allowed) {
2718
2736
  return `Error: Permission denied for read access to ${resolved}. Use the approve_scope tool with path="${resolved}" and mode="read" to request access from the user.`;
@@ -2762,7 +2780,7 @@ function formatSize(bytes) {
2762
2780
  import { tool as tool5 } from "ai";
2763
2781
  import { z as z5 } from "zod";
2764
2782
  import { existsSync as existsSync11, unlinkSync as unlinkSync2 } from "fs";
2765
- import { resolve as resolve6 } from "path";
2783
+ import { resolve as resolve7 } from "path";
2766
2784
  function createDeleteFileTool(permissions) {
2767
2785
  return tool5({
2768
2786
  description: "Delete a file. This action cannot be undone. The path must be within a writable scope. Always asks for confirmation.",
@@ -2770,10 +2788,10 @@ function createDeleteFileTool(permissions) {
2770
2788
  path: z5.string().describe("Absolute or relative path to the file to delete")
2771
2789
  }),
2772
2790
  execute: async ({ path: path3 }) => {
2773
- const resolved = resolve6(path3);
2791
+ const resolved = resolve7(path3);
2774
2792
  const check = await permissions.checkFsAccess(resolved, "write");
2775
2793
  if (!check.allowed) {
2776
- const parentDir = resolve6(resolved, "..");
2794
+ const parentDir = resolve7(resolved, "..");
2777
2795
  return `Error: Permission denied for write access to ${resolved}. Use the approve_scope tool with path="${parentDir}" and mode="write" to request access from the user.`;
2778
2796
  }
2779
2797
  if (!existsSync11(resolved)) {
@@ -2797,7 +2815,7 @@ function createDeleteFileTool(permissions) {
2797
2815
  import { tool as tool6 } from "ai";
2798
2816
  import { z as z6 } from "zod";
2799
2817
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
2800
- import { resolve as resolve7 } from "path";
2818
+ import { resolve as resolve8 } from "path";
2801
2819
  function createEditFileTool(permissions) {
2802
2820
  return tool6({
2803
2821
  description: "Edit a file by replacing an exact string match with new content. Use this instead of write_file when you only need to change part of a file. The old_string must match exactly (including whitespace and indentation). Fails if old_string is not found or found multiple times.",
@@ -2807,10 +2825,10 @@ function createEditFileTool(permissions) {
2807
2825
  new_string: z6.string().describe("The text to replace it with")
2808
2826
  }),
2809
2827
  execute: async ({ path: path3, old_string, new_string }) => {
2810
- const resolved = resolve7(path3);
2828
+ const resolved = resolve8(path3);
2811
2829
  const fsCheck = await permissions.checkFsAccess(resolved, "write");
2812
2830
  if (!fsCheck.allowed) {
2813
- const parentDir = resolve7(resolved, "..");
2831
+ const parentDir = resolve8(resolved, "..");
2814
2832
  return `Error: Permission denied for write access to ${resolved}. Use the approve_scope tool with path="${parentDir}" and mode="write" to request access from the user.`;
2815
2833
  }
2816
2834
  try {
@@ -2841,7 +2859,7 @@ function createEditFileTool(permissions) {
2841
2859
  import { tool as tool7 } from "ai";
2842
2860
  import { z as z7 } from "zod";
2843
2861
  import { existsSync as existsSync12, statSync as statSync2 } from "fs";
2844
- import { resolve as resolve8, basename } from "path";
2862
+ import { resolve as resolve9, basename } from "path";
2845
2863
  function createSendFileTool(permissions, sendFile) {
2846
2864
  return tool7({
2847
2865
  description: "Send a file to the user. On Telegram the file is uploaded as an attachment. On CLI the file path and size are displayed. The path must be within an allowed read scope.",
@@ -2849,10 +2867,10 @@ function createSendFileTool(permissions, sendFile) {
2849
2867
  path: z7.string().describe("Absolute or relative path to the file to send")
2850
2868
  }),
2851
2869
  execute: async ({ path: path3 }) => {
2852
- const resolved = resolve8(path3);
2870
+ const resolved = resolve9(path3);
2853
2871
  const check = await permissions.checkFsAccess(resolved, "read");
2854
2872
  if (!check.allowed) {
2855
- const parentDir = resolve8(resolved, "..");
2873
+ const parentDir = resolve9(resolved, "..");
2856
2874
  return `Error: Permission denied for read access to ${resolved}. Use the approve_scope tool with path="${parentDir}" and mode="read" to request access from the user.`;
2857
2875
  }
2858
2876
  if (!existsSync12(resolved)) {
@@ -2880,7 +2898,7 @@ function createSendFileTool(permissions, sendFile) {
2880
2898
  // src/capabilities/filesystem/approve-scope.ts
2881
2899
  import { tool as tool8 } from "ai";
2882
2900
  import { z as z8 } from "zod";
2883
- import { resolve as resolve9 } from "path";
2901
+ import { resolve as resolve10 } from "path";
2884
2902
  function createApproveScopeTool(permissions) {
2885
2903
  return tool8({
2886
2904
  description: 'Request user approval to access a directory outside current scopes. Use this when a file tool returns a permission denied error. The user gets an approval prompt (Allow/Always/Deny buttons on Telegram, yes/always/no on CLI). "Allow" grants session-only access. "Always" persists to disk. After approval, retry the original file operation.',
@@ -2889,7 +2907,7 @@ function createApproveScopeTool(permissions) {
2889
2907
  mode: z8.enum(["read", "write"]).describe("The access mode needed")
2890
2908
  }),
2891
2909
  execute: async ({ path: path3, mode }) => {
2892
- const resolved = resolve9(path3);
2910
+ const resolved = resolve10(path3);
2893
2911
  const result = await permissions.requestScopeExternal(resolved, mode);
2894
2912
  if (result.allowed) {
2895
2913
  return `Access approved for ${mode} access to ${resolved}. You can now retry the file operation.`;
@@ -3392,23 +3410,11 @@ async function githubRequest(path3, options = {}) {
3392
3410
  throw new Error(`GitHub API ${response.status}: ${body.slice(0, 500)}`);
3393
3411
  }
3394
3412
  if (response.status === 204) return null;
3395
- return response.json();
3396
- }
3397
- function parseRepo(remoteUrl) {
3398
- const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/([^.\s]+)(?:\.git)?$/);
3399
- if (sshMatch) return { owner: sshMatch[1], repo: sshMatch[2] };
3400
- const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/([^.\s]+)(?:\.git)?$/);
3401
- if (httpsMatch) return { owner: httpsMatch[1], repo: httpsMatch[2] };
3402
- return null;
3403
- }
3404
- async function getCurrentRepo() {
3405
- try {
3406
- const { execSync: execSync9 } = await import("child_process");
3407
- const remoteUrl = execSync9("git remote get-url origin", { encoding: "utf-8", timeout: 5e3 }).trim();
3408
- return parseRepo(remoteUrl);
3409
- } catch {
3410
- return null;
3413
+ const contentType = response.headers.get("content-type") || "";
3414
+ if (contentType.includes("application/json")) {
3415
+ return response.json();
3411
3416
  }
3417
+ return response.text();
3412
3418
  }
3413
3419
 
3414
3420
  // src/capabilities/github/create-pr.ts
@@ -3416,25 +3422,19 @@ function createCreatePrTool() {
3416
3422
  return tool24({
3417
3423
  description: "Create a pull request on GitHub. Requires GITHUB_TOKEN to be configured.",
3418
3424
  parameters: z24.object({
3425
+ owner: z24.string().describe("Repository owner (username or org)"),
3426
+ repo: z24.string().describe("Repository name"),
3419
3427
  title: z24.string().describe("PR title"),
3420
3428
  body: z24.string().describe("PR description (markdown supported)").default(""),
3421
3429
  head: z24.string().describe("The branch containing the changes"),
3422
3430
  base: z24.string().describe("The branch to merge into").default("main"),
3423
3431
  draft: z24.boolean().describe("Create as draft PR").default(false)
3424
3432
  }),
3425
- execute: async ({ title, body, head, base, draft }) => {
3433
+ execute: async ({ owner, repo, title, body, head, base, draft }) => {
3426
3434
  try {
3427
- const repo = await getCurrentRepo();
3428
- if (!repo) return "Error: Could not detect GitHub repository. Make sure you are in a git repo with a GitHub remote.";
3429
- const result = await githubRequest(`/repos/${repo.owner}/${repo.repo}/pulls`, {
3435
+ const result = await githubRequest(`/repos/${owner}/${repo}/pulls`, {
3430
3436
  method: "POST",
3431
- body: {
3432
- title,
3433
- body,
3434
- head,
3435
- base,
3436
- draft
3437
- }
3437
+ body: { title, body, head, base, draft }
3438
3438
  });
3439
3439
  return `PR created: ${result.html_url}
3440
3440
  #${result.number}: ${result.title}
@@ -3453,18 +3453,15 @@ function createReviewPrTool() {
3453
3453
  return tool25({
3454
3454
  description: "Get details of a pull request including the diff. Reviews the PR and returns the title, body, changed files, and diff. Optionally post a review comment.",
3455
3455
  parameters: z25.object({
3456
+ owner: z25.string().describe("Repository owner (username or org)"),
3457
+ repo: z25.string().describe("Repository name"),
3456
3458
  number: z25.number().describe("PR number"),
3457
3459
  comment: z25.string().describe("Review comment to post on the PR (optional)").optional()
3458
3460
  }),
3459
- execute: async ({ number, comment }) => {
3461
+ execute: async ({ owner, repo, number, comment }) => {
3460
3462
  try {
3461
- const repo = await getCurrentRepo();
3462
- if (!repo) return "Error: Could not detect GitHub repository.";
3463
- const pr = await githubRequest(`/repos/${repo.owner}/${repo.repo}/pulls/${number}`);
3463
+ const pr = await githubRequest(`/repos/${owner}/${repo}/pulls/${number}`);
3464
3464
  if (!pr) return `Error: PR #${number} not found.`;
3465
- const diff = await githubRequest(`/repos/${repo.owner}/${repo.repo}/pulls/${number}`, {
3466
- headers: { "Accept": "application/vnd.github.v3.diff" }
3467
- });
3468
3465
  let summary = `PR #${pr.number}: ${pr.title}
3469
3466
  `;
3470
3467
  summary += `Author: ${pr.user?.login}
@@ -3482,28 +3479,30 @@ ${pr.body.slice(0, 2e3)}
3482
3479
 
3483
3480
  `;
3484
3481
  }
3485
- if (typeof diff === "string") {
3486
- const diffLines = diff.split("\n");
3487
- const maxDiffLines = 200;
3488
- summary += `Diff (first ${Math.min(diffLines.length, maxDiffLines)} of ${diffLines.length} lines):
3482
+ try {
3483
+ const diff = await githubRequest(`/repos/${owner}/${repo}/pulls/${number}`, {
3484
+ headers: { "Accept": "application/vnd.github.v3.diff" }
3485
+ });
3486
+ if (typeof diff === "string") {
3487
+ const diffLines = diff.split("\n");
3488
+ const maxDiffLines = 200;
3489
+ summary += `Diff (first ${Math.min(diffLines.length, maxDiffLines)} of ${diffLines.length} lines):
3489
3490
  `;
3490
- summary += diffLines.slice(0, maxDiffLines).join("\n");
3491
- if (diffLines.length > maxDiffLines) {
3492
- summary += `
3491
+ summary += diffLines.slice(0, maxDiffLines).join("\n");
3492
+ if (diffLines.length > maxDiffLines) {
3493
+ summary += `
3493
3494
 
3494
3495
  ... (${diffLines.length - maxDiffLines} more lines)`;
3496
+ }
3495
3497
  }
3496
- } else {
3497
- summary += `(Diff not available in text format)`;
3498
+ } catch {
3499
+ summary += `(Diff not available)`;
3498
3500
  }
3499
3501
  if (comment) {
3500
3502
  try {
3501
- await githubRequest(`/repos/${repo.owner}/${repo.repo}/pulls/${number}/reviews`, {
3503
+ await githubRequest(`/repos/${owner}/${repo}/pulls/${number}/reviews`, {
3502
3504
  method: "POST",
3503
- body: {
3504
- body: comment,
3505
- event: "COMMENT"
3506
- }
3505
+ body: { body: comment, event: "COMMENT" }
3507
3506
  });
3508
3507
  summary += `
3509
3508
 
@@ -3527,31 +3526,31 @@ import { tool as tool26 } from "ai";
3527
3526
  import { z as z26 } from "zod";
3528
3527
  function createListIssuesTool() {
3529
3528
  return tool26({
3530
- description: "List open GitHub issues for the current repository. Requires GITHUB_TOKEN.",
3529
+ description: "List GitHub issues for a repository. Requires GITHUB_TOKEN.",
3531
3530
  parameters: z26.object({
3531
+ owner: z26.string().describe("Repository owner (username or org)"),
3532
+ repo: z26.string().describe("Repository name"),
3532
3533
  state: z26.enum(["open", "closed", "all"]).describe("Filter by issue state").default("open"),
3533
3534
  labels: z26.string().describe("Comma-separated label names to filter by (optional)").optional(),
3534
3535
  limit: z26.number().describe("Maximum number of issues to return").default(10)
3535
3536
  }),
3536
- execute: async ({ state, labels, limit }) => {
3537
+ execute: async ({ owner, repo, state, labels, limit }) => {
3537
3538
  try {
3538
- const repo = await getCurrentRepo();
3539
- if (!repo) return "Error: Could not detect GitHub repository.";
3540
3539
  const params = new URLSearchParams();
3541
3540
  params.set("state", state);
3542
3541
  params.set("per_page", String(Math.min(limit, 100)));
3543
3542
  params.set("sort", "updated");
3544
3543
  params.set("direction", "desc");
3545
3544
  if (labels) params.set("labels", labels);
3546
- const issues = await githubRequest(`/repos/${repo.owner}/${repo.repo}/issues?${params}`);
3545
+ const issues = await githubRequest(`/repos/${owner}/${repo}/issues?${params}`);
3547
3546
  if (!Array.isArray(issues) || issues.length === 0) {
3548
- return `No ${state} issues found.`;
3547
+ return `No ${state} issues found in ${owner}/${repo}.`;
3549
3548
  }
3550
3549
  const lines = issues.map((issue) => {
3551
3550
  const labelStr = issue.labels?.map((l) => `[${l.name}]`).join(" ") || "";
3552
3551
  return `#${issue.number} ${issue.title} ${labelStr} (${issue.state}, by ${issue.user?.login})`;
3553
3552
  });
3554
- return `Issues in ${repo.owner}/${repo.repo} (${state}):
3553
+ return `Issues in ${owner}/${repo} (${state}):
3555
3554
  ${lines.join("\n")}`;
3556
3555
  } catch (err) {
3557
3556
  return `Error listing issues: ${err.message}`;
@@ -3565,19 +3564,19 @@ import { tool as tool27 } from "ai";
3565
3564
  import { z as z27 } from "zod";
3566
3565
  function createCreateIssueTool() {
3567
3566
  return tool27({
3568
- description: "Create a new GitHub issue in the current repository. Requires GITHUB_TOKEN.",
3567
+ description: "Create a new GitHub issue in a repository. Requires GITHUB_TOKEN.",
3569
3568
  parameters: z27.object({
3569
+ owner: z27.string().describe("Repository owner (username or org)"),
3570
+ repo: z27.string().describe("Repository name"),
3570
3571
  title: z27.string().describe("Issue title"),
3571
3572
  body: z27.string().describe("Issue description (markdown supported)").default(""),
3572
3573
  labels: z27.array(z27.string()).describe("Label names to apply").optional()
3573
3574
  }),
3574
- execute: async ({ title, body, labels }) => {
3575
+ execute: async ({ owner, repo, title, body, labels }) => {
3575
3576
  try {
3576
- const repo = await getCurrentRepo();
3577
- if (!repo) return "Error: Could not detect GitHub repository.";
3578
3577
  const payload = { title, body };
3579
3578
  if (labels && labels.length > 0) payload.labels = labels;
3580
- const result = await githubRequest(`/repos/${repo.owner}/${repo.repo}/issues`, {
3579
+ const result = await githubRequest(`/repos/${owner}/${repo}/issues`, {
3581
3580
  method: "POST",
3582
3581
  body: payload
3583
3582
  });
@@ -3593,27 +3592,15 @@ function createCreateIssueTool() {
3593
3592
  // src/capabilities/github/github-api.ts
3594
3593
  import { tool as tool28 } from "ai";
3595
3594
  import { z as z28 } from "zod";
3596
- function createGithubApiTool(permissions) {
3595
+ function createGithubApiTool() {
3597
3596
  return tool28({
3598
- description: "Make a raw request to the GitHub API. Use this for any GitHub operation not covered by other tools. GET requests are auto-approved; write operations (POST, PUT, PATCH, DELETE) require approval.",
3597
+ description: "Make a raw request to the GitHub API. Use this for any GitHub operation not covered by other tools. GET requests (read-only) are always allowed. Write operations (POST, PUT, PATCH, DELETE) will ask the user for approval via the permission system.",
3599
3598
  parameters: z28.object({
3600
- path: z28.string().describe("API path (e.g., /repos/owner/repo/releases)"),
3599
+ path: z28.string().describe("Full API path (e.g., /repos/owner/repo/issues or /user)"),
3601
3600
  method: z28.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).describe("HTTP method").default("GET"),
3602
3601
  body: z28.string().describe("JSON body for write requests (as a JSON string)").optional()
3603
3602
  }),
3604
3603
  execute: async ({ path: path3, method, body }) => {
3605
- const isWrite = ["POST", "PUT", "PATCH", "DELETE"].includes(method);
3606
- if (isWrite) {
3607
- const check = await permissions.checkShellCommand(`github-api ${method} ${path3}`);
3608
- if (!check.allowed) {
3609
- if (check.needsApproval) {
3610
- return `This GitHub API write operation requires approval: ${method} ${path3}
3611
-
3612
- Tell the user what this does and ask for confirmation. If approved, try again.`;
3613
- }
3614
- return `Error: ${check.reason}`;
3615
- }
3616
- }
3617
3604
  try {
3618
3605
  let parsedBody;
3619
3606
  if (body) {
@@ -3803,7 +3790,7 @@ var CapabilityRegistry = class {
3803
3790
  this.tools.review_pr = createReviewPrTool();
3804
3791
  this.tools.list_issues = createListIssuesTool();
3805
3792
  this.tools.create_issue = createCreateIssueTool();
3806
- this.tools.github_api = createGithubApiTool(this.permissions);
3793
+ this.tools.github_api = createGithubApiTool();
3807
3794
  logger.info("GitHub tools registered");
3808
3795
  }
3809
3796
  this.tools.fetch_url = createFetchUrlTool();
@@ -4618,7 +4605,7 @@ async function runWithWatchdog(agentFn) {
4618
4605
  await attempt();
4619
4606
  }
4620
4607
  function sleep(ms) {
4621
- return new Promise((resolve10) => setTimeout(resolve10, ms));
4608
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
4622
4609
  }
4623
4610
 
4624
4611
  // src/index.ts
@@ -4657,10 +4644,10 @@ function splashScreen() {
4657
4644
  }
4658
4645
  async function ask(prompt) {
4659
4646
  const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
4660
- return new Promise((resolve10) => {
4647
+ return new Promise((resolve11) => {
4661
4648
  rl.question(prompt, (answer) => {
4662
4649
  rl.close();
4663
- resolve10(answer.trim());
4650
+ resolve11(answer.trim());
4664
4651
  });
4665
4652
  });
4666
4653
  }
@@ -4793,6 +4780,14 @@ async function configure(existingConfig) {
4793
4780
  if (ghToken) {
4794
4781
  appendToEnv("GITHUB_TOKEN", ghToken);
4795
4782
  }
4783
+ if (config.github.username || process.env.GITHUB_TOKEN) {
4784
+ const ghOwnerCurrent = isReconfig && config.github.defaultOwner ? ` [${config.github.defaultOwner}]` : "";
4785
+ const ghOwner = await ask(chalk6.white(` Default GitHub owner/org${ghOwnerCurrent}: `));
4786
+ if (ghOwner) config.github.defaultOwner = ghOwner;
4787
+ const ghRepoCurrent = isReconfig && config.github.defaultRepo ? ` [${config.github.defaultRepo}]` : "";
4788
+ const ghRepo = await ask(chalk6.white(` Default repo name${ghRepoCurrent}: `));
4789
+ if (ghRepo) config.github.defaultRepo = ghRepo;
4790
+ }
4796
4791
  hr();
4797
4792
  console.log("");
4798
4793
  console.log(chalk6.bold.white(" Token Budget"));