@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 +114 -119
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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((
|
|
1713
|
-
this.rl?.question(question, (answer) =>
|
|
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((
|
|
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
|
-
|
|
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((
|
|
1985
|
-
this.pendingApprovals.set(`${id}:yes`, () =>
|
|
1986
|
-
this.pendingApprovals.set(`${id}:always`, () =>
|
|
1987
|
-
this.pendingApprovals.set(`${id}: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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
2628
|
+
const resolved = resolve3(path3);
|
|
2611
2629
|
const check = await permissions.checkFsAccess(resolved, "read");
|
|
2612
2630
|
if (!check.allowed) {
|
|
2613
|
-
const parentDir =
|
|
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
|
|
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 =
|
|
2666
|
+
const resolved = resolve4(path3);
|
|
2649
2667
|
const check = await permissions.checkFsAccess(resolved, "write");
|
|
2650
2668
|
if (!check.allowed) {
|
|
2651
|
-
const parentDir =
|
|
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
|
|
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 =
|
|
2698
|
+
const resolved = resolve5(path3);
|
|
2681
2699
|
const check = await permissions.checkFsAccess(resolved, "write");
|
|
2682
2700
|
if (!check.allowed) {
|
|
2683
|
-
const parentDir =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
2791
|
+
const resolved = resolve7(path3);
|
|
2774
2792
|
const check = await permissions.checkFsAccess(resolved, "write");
|
|
2775
2793
|
if (!check.allowed) {
|
|
2776
|
-
const parentDir =
|
|
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
|
|
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 =
|
|
2828
|
+
const resolved = resolve8(path3);
|
|
2811
2829
|
const fsCheck = await permissions.checkFsAccess(resolved, "write");
|
|
2812
2830
|
if (!fsCheck.allowed) {
|
|
2813
|
-
const parentDir =
|
|
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
|
|
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 =
|
|
2870
|
+
const resolved = resolve9(path3);
|
|
2853
2871
|
const check = await permissions.checkFsAccess(resolved, "read");
|
|
2854
2872
|
if (!check.allowed) {
|
|
2855
|
-
const parentDir =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
3486
|
-
const
|
|
3487
|
-
|
|
3488
|
-
|
|
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
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
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
|
-
}
|
|
3497
|
-
summary += `(Diff not available
|
|
3498
|
+
} catch {
|
|
3499
|
+
summary += `(Diff not available)`;
|
|
3498
3500
|
}
|
|
3499
3501
|
if (comment) {
|
|
3500
3502
|
try {
|
|
3501
|
-
await githubRequest(`/repos/${
|
|
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
|
|
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/${
|
|
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 ${
|
|
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
|
|
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/${
|
|
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(
|
|
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
|
|
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/
|
|
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(
|
|
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((
|
|
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((
|
|
4647
|
+
return new Promise((resolve11) => {
|
|
4661
4648
|
rl.question(prompt, (answer) => {
|
|
4662
4649
|
rl.close();
|
|
4663
|
-
|
|
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"));
|