@cosmicstack/mercury-agent 0.2.7 → 0.3.1
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 +402 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as readFileSync12 } from "fs";
|
|
4
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync13, existsSync as existsSync16 } from "fs";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { dirname as dirname3, join as join11 } from "path";
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -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
|
}
|
|
@@ -73,6 +77,10 @@ function getDefaultConfig() {
|
|
|
73
77
|
streaming: getEnvBool("TELEGRAM_STREAMING", true)
|
|
74
78
|
}
|
|
75
79
|
},
|
|
80
|
+
github: {
|
|
81
|
+
username: getEnv("GITHUB_USERNAME", ""),
|
|
82
|
+
email: getEnv("GITHUB_EMAIL", "")
|
|
83
|
+
},
|
|
76
84
|
memory: {
|
|
77
85
|
dir: getEnv("MEMORY_DIR", join(home, "memory")),
|
|
78
86
|
shortTermMaxMessages: getEnvNum("SHORT_TERM_MAX_MESSAGES", 20)
|
|
@@ -1002,6 +1010,12 @@ You can override this:
|
|
|
1002
1010
|
if (this.tokenBudget.getUsagePercentage() > 70) {
|
|
1003
1011
|
prompt += "\nBe concise to conserve tokens.";
|
|
1004
1012
|
}
|
|
1013
|
+
const toolNames = this.capabilities.getToolNames();
|
|
1014
|
+
const githubTools = ["create_pr", "review_pr", "list_issues", "create_issue", "github_api"];
|
|
1015
|
+
const hasGitHub = githubTools.some((t) => toolNames.includes(t));
|
|
1016
|
+
if (hasGitHub) {
|
|
1017
|
+
prompt += "\n\nGitHub companion is active. You can create pull requests, review PRs, manage issues, and use the GitHub API. When the user asks to 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.";
|
|
1018
|
+
}
|
|
1005
1019
|
return prompt;
|
|
1006
1020
|
}
|
|
1007
1021
|
async processInternalPrompt(prompt, channelId, channelType) {
|
|
@@ -1705,16 +1719,16 @@ var CLIChannel = class extends BaseChannel {
|
|
|
1705
1719
|
}
|
|
1706
1720
|
}
|
|
1707
1721
|
async prompt(question) {
|
|
1708
|
-
return new Promise((
|
|
1709
|
-
this.rl?.question(question, (answer) =>
|
|
1722
|
+
return new Promise((resolve11) => {
|
|
1723
|
+
this.rl?.question(question, (answer) => resolve11(answer.trim()));
|
|
1710
1724
|
});
|
|
1711
1725
|
}
|
|
1712
1726
|
async askPermission(prompt) {
|
|
1713
|
-
return new Promise((
|
|
1727
|
+
return new Promise((resolve11) => {
|
|
1714
1728
|
console.log("");
|
|
1715
1729
|
console.log(chalk2.yellow(` \u26A0 ${prompt}`));
|
|
1716
1730
|
this.rl?.question(chalk2.yellow(" > "), (answer) => {
|
|
1717
|
-
|
|
1731
|
+
resolve11(answer.trim());
|
|
1718
1732
|
});
|
|
1719
1733
|
});
|
|
1720
1734
|
}
|
|
@@ -1977,15 +1991,15 @@ var TelegramChannel = class extends BaseChannel {
|
|
|
1977
1991
|
reply_markup: keyboard
|
|
1978
1992
|
});
|
|
1979
1993
|
}
|
|
1980
|
-
return new Promise((
|
|
1981
|
-
this.pendingApprovals.set(`${id}:yes`, () =>
|
|
1982
|
-
this.pendingApprovals.set(`${id}:always`, () =>
|
|
1983
|
-
this.pendingApprovals.set(`${id}:no`, () =>
|
|
1994
|
+
return new Promise((resolve11) => {
|
|
1995
|
+
this.pendingApprovals.set(`${id}:yes`, () => resolve11("yes"));
|
|
1996
|
+
this.pendingApprovals.set(`${id}:always`, () => resolve11("always"));
|
|
1997
|
+
this.pendingApprovals.set(`${id}:no`, () => resolve11("no"));
|
|
1984
1998
|
setTimeout(() => {
|
|
1985
1999
|
this.pendingApprovals.delete(`${id}:yes`);
|
|
1986
2000
|
this.pendingApprovals.delete(`${id}:always`);
|
|
1987
2001
|
this.pendingApprovals.delete(`${id}:no`);
|
|
1988
|
-
|
|
2002
|
+
resolve11("no");
|
|
1989
2003
|
}, 12e4);
|
|
1990
2004
|
});
|
|
1991
2005
|
}
|
|
@@ -2209,7 +2223,7 @@ var TokenBudget = class {
|
|
|
2209
2223
|
|
|
2210
2224
|
// src/capabilities/permissions.ts
|
|
2211
2225
|
import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
|
|
2212
|
-
import { join as join6, resolve, sep } from "path";
|
|
2226
|
+
import { join as join6, resolve as resolve2, sep } from "path";
|
|
2213
2227
|
import { homedir as homedir2 } from "os";
|
|
2214
2228
|
import { parse as parseYaml3, stringify as stringifyYaml3 } from "yaml";
|
|
2215
2229
|
var DEFAULT_MANIFEST = {
|
|
@@ -2334,8 +2348,8 @@ var PermissionManager = class {
|
|
|
2334
2348
|
clearElevation() {
|
|
2335
2349
|
this.elevatedCommands.clear();
|
|
2336
2350
|
}
|
|
2337
|
-
isElevated(
|
|
2338
|
-
if (this.elevatedCommands.has(
|
|
2351
|
+
isElevated(tool30) {
|
|
2352
|
+
if (this.elevatedCommands.has(tool30)) return true;
|
|
2339
2353
|
return false;
|
|
2340
2354
|
}
|
|
2341
2355
|
isShellElevated() {
|
|
@@ -2392,7 +2406,7 @@ var PermissionManager = class {
|
|
|
2392
2406
|
if (!fs3.enabled) {
|
|
2393
2407
|
return { allowed: false, reason: "Filesystem capability is disabled" };
|
|
2394
2408
|
}
|
|
2395
|
-
const resolved =
|
|
2409
|
+
const resolved = resolve2(path3);
|
|
2396
2410
|
const scope = this.findScope(resolved);
|
|
2397
2411
|
const tempScope = this.findTempScope(resolved);
|
|
2398
2412
|
if (scope) {
|
|
@@ -2482,7 +2496,7 @@ var PermissionManager = class {
|
|
|
2482
2496
|
return this.manifest.capabilities.git.enabled && this.manifest.capabilities.git.approveWrite;
|
|
2483
2497
|
}
|
|
2484
2498
|
addScope(path3, read, write) {
|
|
2485
|
-
const resolved =
|
|
2499
|
+
const resolved = resolve2(path3);
|
|
2486
2500
|
const existing = this.findScope(resolved);
|
|
2487
2501
|
if (existing) {
|
|
2488
2502
|
existing.read = existing.read || read;
|
|
@@ -2500,7 +2514,7 @@ var PermissionManager = class {
|
|
|
2500
2514
|
findScope(resolvedPath) {
|
|
2501
2515
|
const scopes = this.manifest.capabilities.filesystem.scopes;
|
|
2502
2516
|
for (const scope of scopes) {
|
|
2503
|
-
const scopeResolved =
|
|
2517
|
+
const scopeResolved = resolve2(scope.path.replace(/^~/, homedir2()));
|
|
2504
2518
|
if (resolvedPath === scopeResolved || resolvedPath.startsWith(scopeResolved + sep)) {
|
|
2505
2519
|
return scope;
|
|
2506
2520
|
}
|
|
@@ -2527,13 +2541,13 @@ Allow access?`;
|
|
|
2527
2541
|
return { allowed: false, reason: `Permission denied for ${mode} access to ${path3}` };
|
|
2528
2542
|
}
|
|
2529
2543
|
addTempScope(path3, read, write) {
|
|
2530
|
-
const resolved =
|
|
2544
|
+
const resolved = resolve2(path3);
|
|
2531
2545
|
this.tempScopes.push({ path: resolved, read, write });
|
|
2532
2546
|
logger.info({ path: resolved, read, write }, "Temp permission scope added (session only)");
|
|
2533
2547
|
}
|
|
2534
2548
|
findTempScope(resolvedPath) {
|
|
2535
2549
|
for (const scope of this.tempScopes) {
|
|
2536
|
-
const scopeResolved =
|
|
2550
|
+
const scopeResolved = resolve2(scope.path.replace(/^~/, homedir2()));
|
|
2537
2551
|
if (resolvedPath === scopeResolved || resolvedPath.startsWith(scopeResolved + sep)) {
|
|
2538
2552
|
return scope;
|
|
2539
2553
|
}
|
|
@@ -2559,7 +2573,7 @@ Allow access?`;
|
|
|
2559
2573
|
for (const p of pathPatterns) {
|
|
2560
2574
|
const match = command.match(p);
|
|
2561
2575
|
if (match) {
|
|
2562
|
-
const candidate =
|
|
2576
|
+
const candidate = resolve2(match[1].replace(/^~/, homedir2()));
|
|
2563
2577
|
if (!candidate.startsWith(this.cwd)) {
|
|
2564
2578
|
return candidate;
|
|
2565
2579
|
}
|
|
@@ -2595,7 +2609,7 @@ Allow access?`;
|
|
|
2595
2609
|
import { tool } from "ai";
|
|
2596
2610
|
import { z } from "zod";
|
|
2597
2611
|
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
2598
|
-
import { resolve as
|
|
2612
|
+
import { resolve as resolve3 } from "path";
|
|
2599
2613
|
function createReadFileTool(permissions) {
|
|
2600
2614
|
return tool({
|
|
2601
2615
|
description: "Read the contents of a file. The path must be within an allowed scope.",
|
|
@@ -2603,10 +2617,10 @@ function createReadFileTool(permissions) {
|
|
|
2603
2617
|
path: z.string().describe("Absolute or relative path to the file")
|
|
2604
2618
|
}),
|
|
2605
2619
|
execute: async ({ path: path3 }) => {
|
|
2606
|
-
const resolved =
|
|
2620
|
+
const resolved = resolve3(path3);
|
|
2607
2621
|
const check = await permissions.checkFsAccess(resolved, "read");
|
|
2608
2622
|
if (!check.allowed) {
|
|
2609
|
-
const parentDir =
|
|
2623
|
+
const parentDir = resolve3(resolved, "..");
|
|
2610
2624
|
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.`;
|
|
2611
2625
|
}
|
|
2612
2626
|
if (!existsSync7(resolved)) {
|
|
@@ -2632,7 +2646,7 @@ function createReadFileTool(permissions) {
|
|
|
2632
2646
|
import { tool as tool2 } from "ai";
|
|
2633
2647
|
import { z as z2 } from "zod";
|
|
2634
2648
|
import { existsSync as existsSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
2635
|
-
import { resolve as
|
|
2649
|
+
import { resolve as resolve4 } from "path";
|
|
2636
2650
|
function createWriteFileTool(permissions) {
|
|
2637
2651
|
return tool2({
|
|
2638
2652
|
description: "Write content to an existing file. The path must be within a writable scope.",
|
|
@@ -2641,10 +2655,10 @@ function createWriteFileTool(permissions) {
|
|
|
2641
2655
|
content: z2.string().describe("The content to write to the file")
|
|
2642
2656
|
}),
|
|
2643
2657
|
execute: async ({ path: path3, content }) => {
|
|
2644
|
-
const resolved =
|
|
2658
|
+
const resolved = resolve4(path3);
|
|
2645
2659
|
const check = await permissions.checkFsAccess(resolved, "write");
|
|
2646
2660
|
if (!check.allowed) {
|
|
2647
|
-
const parentDir =
|
|
2661
|
+
const parentDir = resolve4(resolved, "..");
|
|
2648
2662
|
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.`;
|
|
2649
2663
|
}
|
|
2650
2664
|
if (!existsSync8(resolved)) {
|
|
@@ -2664,7 +2678,7 @@ function createWriteFileTool(permissions) {
|
|
|
2664
2678
|
import { tool as tool3 } from "ai";
|
|
2665
2679
|
import { z as z3 } from "zod";
|
|
2666
2680
|
import { existsSync as existsSync9, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "fs";
|
|
2667
|
-
import { resolve as
|
|
2681
|
+
import { resolve as resolve5, dirname as dirname2 } from "path";
|
|
2668
2682
|
function createCreateFileTool(permissions) {
|
|
2669
2683
|
return tool3({
|
|
2670
2684
|
description: "Create a new file with the given content. Also creates parent directories if needed. The path must be within a writable scope.",
|
|
@@ -2673,10 +2687,10 @@ function createCreateFileTool(permissions) {
|
|
|
2673
2687
|
content: z3.string().describe("The content of the new file")
|
|
2674
2688
|
}),
|
|
2675
2689
|
execute: async ({ path: path3, content }) => {
|
|
2676
|
-
const resolved =
|
|
2690
|
+
const resolved = resolve5(path3);
|
|
2677
2691
|
const check = await permissions.checkFsAccess(resolved, "write");
|
|
2678
2692
|
if (!check.allowed) {
|
|
2679
|
-
const parentDir =
|
|
2693
|
+
const parentDir = resolve5(resolved, "..");
|
|
2680
2694
|
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.`;
|
|
2681
2695
|
}
|
|
2682
2696
|
if (existsSync9(resolved)) {
|
|
@@ -2700,7 +2714,7 @@ function createCreateFileTool(permissions) {
|
|
|
2700
2714
|
import { tool as tool4 } from "ai";
|
|
2701
2715
|
import { z as z4 } from "zod";
|
|
2702
2716
|
import { existsSync as existsSync10, readdirSync as readdirSync2, statSync } from "fs";
|
|
2703
|
-
import { resolve as
|
|
2717
|
+
import { resolve as resolve6 } from "path";
|
|
2704
2718
|
function createListDirTool(permissions) {
|
|
2705
2719
|
return tool4({
|
|
2706
2720
|
description: "List the contents of a directory. Shows file names, types, and sizes.",
|
|
@@ -2708,7 +2722,7 @@ function createListDirTool(permissions) {
|
|
|
2708
2722
|
path: z4.string().describe("Absolute or relative path to the directory")
|
|
2709
2723
|
}),
|
|
2710
2724
|
execute: async ({ path: path3 }) => {
|
|
2711
|
-
const resolved =
|
|
2725
|
+
const resolved = resolve6(path3);
|
|
2712
2726
|
const check = await permissions.checkFsAccess(resolved, "read");
|
|
2713
2727
|
if (!check.allowed) {
|
|
2714
2728
|
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.`;
|
|
@@ -2758,7 +2772,7 @@ function formatSize(bytes) {
|
|
|
2758
2772
|
import { tool as tool5 } from "ai";
|
|
2759
2773
|
import { z as z5 } from "zod";
|
|
2760
2774
|
import { existsSync as existsSync11, unlinkSync as unlinkSync2 } from "fs";
|
|
2761
|
-
import { resolve as
|
|
2775
|
+
import { resolve as resolve7 } from "path";
|
|
2762
2776
|
function createDeleteFileTool(permissions) {
|
|
2763
2777
|
return tool5({
|
|
2764
2778
|
description: "Delete a file. This action cannot be undone. The path must be within a writable scope. Always asks for confirmation.",
|
|
@@ -2766,10 +2780,10 @@ function createDeleteFileTool(permissions) {
|
|
|
2766
2780
|
path: z5.string().describe("Absolute or relative path to the file to delete")
|
|
2767
2781
|
}),
|
|
2768
2782
|
execute: async ({ path: path3 }) => {
|
|
2769
|
-
const resolved =
|
|
2783
|
+
const resolved = resolve7(path3);
|
|
2770
2784
|
const check = await permissions.checkFsAccess(resolved, "write");
|
|
2771
2785
|
if (!check.allowed) {
|
|
2772
|
-
const parentDir =
|
|
2786
|
+
const parentDir = resolve7(resolved, "..");
|
|
2773
2787
|
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.`;
|
|
2774
2788
|
}
|
|
2775
2789
|
if (!existsSync11(resolved)) {
|
|
@@ -2793,7 +2807,7 @@ function createDeleteFileTool(permissions) {
|
|
|
2793
2807
|
import { tool as tool6 } from "ai";
|
|
2794
2808
|
import { z as z6 } from "zod";
|
|
2795
2809
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
2796
|
-
import { resolve as
|
|
2810
|
+
import { resolve as resolve8 } from "path";
|
|
2797
2811
|
function createEditFileTool(permissions) {
|
|
2798
2812
|
return tool6({
|
|
2799
2813
|
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.",
|
|
@@ -2803,10 +2817,10 @@ function createEditFileTool(permissions) {
|
|
|
2803
2817
|
new_string: z6.string().describe("The text to replace it with")
|
|
2804
2818
|
}),
|
|
2805
2819
|
execute: async ({ path: path3, old_string, new_string }) => {
|
|
2806
|
-
const resolved =
|
|
2820
|
+
const resolved = resolve8(path3);
|
|
2807
2821
|
const fsCheck = await permissions.checkFsAccess(resolved, "write");
|
|
2808
2822
|
if (!fsCheck.allowed) {
|
|
2809
|
-
const parentDir =
|
|
2823
|
+
const parentDir = resolve8(resolved, "..");
|
|
2810
2824
|
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.`;
|
|
2811
2825
|
}
|
|
2812
2826
|
try {
|
|
@@ -2837,7 +2851,7 @@ function createEditFileTool(permissions) {
|
|
|
2837
2851
|
import { tool as tool7 } from "ai";
|
|
2838
2852
|
import { z as z7 } from "zod";
|
|
2839
2853
|
import { existsSync as existsSync12, statSync as statSync2 } from "fs";
|
|
2840
|
-
import { resolve as
|
|
2854
|
+
import { resolve as resolve9, basename } from "path";
|
|
2841
2855
|
function createSendFileTool(permissions, sendFile) {
|
|
2842
2856
|
return tool7({
|
|
2843
2857
|
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.",
|
|
@@ -2845,10 +2859,10 @@ function createSendFileTool(permissions, sendFile) {
|
|
|
2845
2859
|
path: z7.string().describe("Absolute or relative path to the file to send")
|
|
2846
2860
|
}),
|
|
2847
2861
|
execute: async ({ path: path3 }) => {
|
|
2848
|
-
const resolved =
|
|
2862
|
+
const resolved = resolve9(path3);
|
|
2849
2863
|
const check = await permissions.checkFsAccess(resolved, "read");
|
|
2850
2864
|
if (!check.allowed) {
|
|
2851
|
-
const parentDir =
|
|
2865
|
+
const parentDir = resolve9(resolved, "..");
|
|
2852
2866
|
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.`;
|
|
2853
2867
|
}
|
|
2854
2868
|
if (!existsSync12(resolved)) {
|
|
@@ -2876,7 +2890,7 @@ function createSendFileTool(permissions, sendFile) {
|
|
|
2876
2890
|
// src/capabilities/filesystem/approve-scope.ts
|
|
2877
2891
|
import { tool as tool8 } from "ai";
|
|
2878
2892
|
import { z as z8 } from "zod";
|
|
2879
|
-
import { resolve as
|
|
2893
|
+
import { resolve as resolve10 } from "path";
|
|
2880
2894
|
function createApproveScopeTool(permissions) {
|
|
2881
2895
|
return tool8({
|
|
2882
2896
|
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.',
|
|
@@ -2885,7 +2899,7 @@ function createApproveScopeTool(permissions) {
|
|
|
2885
2899
|
mode: z8.enum(["read", "write"]).describe("The access mode needed")
|
|
2886
2900
|
}),
|
|
2887
2901
|
execute: async ({ path: path3, mode }) => {
|
|
2888
|
-
const resolved =
|
|
2902
|
+
const resolved = resolve10(path3);
|
|
2889
2903
|
const result = await permissions.requestScopeExternal(resolved, mode);
|
|
2890
2904
|
if (result.allowed) {
|
|
2891
2905
|
return `Access approved for ${mode} access to ${resolved}. You can now retry the file operation.`;
|
|
@@ -3281,15 +3295,19 @@ function createGitAddTool() {
|
|
|
3281
3295
|
import { tool as tool22 } from "ai";
|
|
3282
3296
|
import { z as z22 } from "zod";
|
|
3283
3297
|
import { execSync as execSync6 } from "child_process";
|
|
3298
|
+
var CO_AUTHOR = "Mercury <mercury@cosmicstack.org>";
|
|
3284
3299
|
function createGitCommitTool() {
|
|
3285
3300
|
return tool22({
|
|
3286
|
-
description: "Record changes to the repository. Creates a new commit with staged changes.",
|
|
3301
|
+
description: "Record changes to the repository. Creates a new commit with staged changes. Automatically includes a Co-authored-by trailer for attribution.",
|
|
3287
3302
|
parameters: z22.object({
|
|
3288
3303
|
message: z22.string().describe("Commit message")
|
|
3289
3304
|
}),
|
|
3290
3305
|
execute: async ({ message }) => {
|
|
3291
3306
|
try {
|
|
3292
|
-
const
|
|
3307
|
+
const fullMessage = `${message}
|
|
3308
|
+
|
|
3309
|
+
Co-authored-by: ${CO_AUTHOR}`;
|
|
3310
|
+
const escapedMsg = fullMessage.replace(/"/g, '\\"');
|
|
3293
3311
|
const result = execSync6(`git commit -m "${escapedMsg}"`, { encoding: "utf-8", timeout: 1e4 });
|
|
3294
3312
|
return result.trim() || "Committed successfully.";
|
|
3295
3313
|
} catch (err) {
|
|
@@ -3336,9 +3354,302 @@ Ask the user for permission. If they approve, try again. If they say "always", u
|
|
|
3336
3354
|
});
|
|
3337
3355
|
}
|
|
3338
3356
|
|
|
3339
|
-
// src/capabilities/
|
|
3357
|
+
// src/capabilities/github/create-pr.ts
|
|
3340
3358
|
import { tool as tool24 } from "ai";
|
|
3341
3359
|
import { z as z24 } from "zod";
|
|
3360
|
+
|
|
3361
|
+
// src/utils/github.ts
|
|
3362
|
+
var GITHUB_API = "https://api.github.com";
|
|
3363
|
+
var cachedToken = null;
|
|
3364
|
+
function setGitHubToken(token) {
|
|
3365
|
+
cachedToken = token;
|
|
3366
|
+
}
|
|
3367
|
+
function getGitHubToken() {
|
|
3368
|
+
if (cachedToken) return cachedToken;
|
|
3369
|
+
return process.env.GITHUB_TOKEN || null;
|
|
3370
|
+
}
|
|
3371
|
+
function isGitHubConfigured() {
|
|
3372
|
+
return !!getGitHubToken();
|
|
3373
|
+
}
|
|
3374
|
+
async function githubRequest(path3, options = {}) {
|
|
3375
|
+
const token = getGitHubToken();
|
|
3376
|
+
if (!token) {
|
|
3377
|
+
throw new Error("GITHUB_TOKEN not configured. Run mercury doctor to set it up.");
|
|
3378
|
+
}
|
|
3379
|
+
const url = path3.startsWith("http") ? path3 : `${GITHUB_API}${path3}`;
|
|
3380
|
+
const headers = {
|
|
3381
|
+
"Authorization": `Bearer ${token}`,
|
|
3382
|
+
"Accept": "application/vnd.github.v3+json",
|
|
3383
|
+
"User-Agent": "Mercury-Agent",
|
|
3384
|
+
...options.headers
|
|
3385
|
+
};
|
|
3386
|
+
const fetchOptions = {
|
|
3387
|
+
method: options.method || "GET",
|
|
3388
|
+
headers
|
|
3389
|
+
};
|
|
3390
|
+
if (options.body) {
|
|
3391
|
+
fetchOptions.body = JSON.stringify(options.body);
|
|
3392
|
+
headers["Content-Type"] = "application/json";
|
|
3393
|
+
}
|
|
3394
|
+
logger.info({ method: fetchOptions.method, path: path3 }, "GitHub API request");
|
|
3395
|
+
const response = await fetch(url, fetchOptions);
|
|
3396
|
+
const remaining = response.headers.get("x-ratelimit-remaining");
|
|
3397
|
+
if (remaining && parseInt(remaining, 10) < 100) {
|
|
3398
|
+
logger.warn({ remaining }, "GitHub API rate limit running low");
|
|
3399
|
+
}
|
|
3400
|
+
if (!response.ok) {
|
|
3401
|
+
const body = await response.text();
|
|
3402
|
+
throw new Error(`GitHub API ${response.status}: ${body.slice(0, 500)}`);
|
|
3403
|
+
}
|
|
3404
|
+
if (response.status === 204) return null;
|
|
3405
|
+
return response.json();
|
|
3406
|
+
}
|
|
3407
|
+
function parseRepo(remoteUrl) {
|
|
3408
|
+
const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/([^.\s]+)(?:\.git)?$/);
|
|
3409
|
+
if (sshMatch) return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
3410
|
+
const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/([^.\s]+)(?:\.git)?$/);
|
|
3411
|
+
if (httpsMatch) return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
3412
|
+
return null;
|
|
3413
|
+
}
|
|
3414
|
+
async function getCurrentRepo() {
|
|
3415
|
+
try {
|
|
3416
|
+
const { execSync: execSync9 } = await import("child_process");
|
|
3417
|
+
const remoteUrl = execSync9("git remote get-url origin", { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
3418
|
+
return parseRepo(remoteUrl);
|
|
3419
|
+
} catch {
|
|
3420
|
+
return null;
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
// src/capabilities/github/create-pr.ts
|
|
3425
|
+
function createCreatePrTool() {
|
|
3426
|
+
return tool24({
|
|
3427
|
+
description: "Create a pull request on GitHub. Requires GITHUB_TOKEN to be configured.",
|
|
3428
|
+
parameters: z24.object({
|
|
3429
|
+
title: z24.string().describe("PR title"),
|
|
3430
|
+
body: z24.string().describe("PR description (markdown supported)").default(""),
|
|
3431
|
+
head: z24.string().describe("The branch containing the changes"),
|
|
3432
|
+
base: z24.string().describe("The branch to merge into").default("main"),
|
|
3433
|
+
draft: z24.boolean().describe("Create as draft PR").default(false)
|
|
3434
|
+
}),
|
|
3435
|
+
execute: async ({ title, body, head, base, draft }) => {
|
|
3436
|
+
try {
|
|
3437
|
+
const repo = await getCurrentRepo();
|
|
3438
|
+
if (!repo) return "Error: Could not detect GitHub repository. Make sure you are in a git repo with a GitHub remote.";
|
|
3439
|
+
const result = await githubRequest(`/repos/${repo.owner}/${repo.repo}/pulls`, {
|
|
3440
|
+
method: "POST",
|
|
3441
|
+
body: {
|
|
3442
|
+
title,
|
|
3443
|
+
body,
|
|
3444
|
+
head,
|
|
3445
|
+
base,
|
|
3446
|
+
draft
|
|
3447
|
+
}
|
|
3448
|
+
});
|
|
3449
|
+
return `PR created: ${result.html_url}
|
|
3450
|
+
#${result.number}: ${result.title}
|
|
3451
|
+
${draft ? "(draft)" : ""} ${result.state}`;
|
|
3452
|
+
} catch (err) {
|
|
3453
|
+
return `Error creating PR: ${err.message}`;
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
});
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
// src/capabilities/github/review-pr.ts
|
|
3460
|
+
import { tool as tool25 } from "ai";
|
|
3461
|
+
import { z as z25 } from "zod";
|
|
3462
|
+
function createReviewPrTool() {
|
|
3463
|
+
return tool25({
|
|
3464
|
+
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.",
|
|
3465
|
+
parameters: z25.object({
|
|
3466
|
+
number: z25.number().describe("PR number"),
|
|
3467
|
+
comment: z25.string().describe("Review comment to post on the PR (optional)").optional()
|
|
3468
|
+
}),
|
|
3469
|
+
execute: async ({ number, comment }) => {
|
|
3470
|
+
try {
|
|
3471
|
+
const repo = await getCurrentRepo();
|
|
3472
|
+
if (!repo) return "Error: Could not detect GitHub repository.";
|
|
3473
|
+
const pr = await githubRequest(`/repos/${repo.owner}/${repo.repo}/pulls/${number}`);
|
|
3474
|
+
if (!pr) return `Error: PR #${number} not found.`;
|
|
3475
|
+
const diff = await githubRequest(`/repos/${repo.owner}/${repo.repo}/pulls/${number}`, {
|
|
3476
|
+
headers: { "Accept": "application/vnd.github.v3.diff" }
|
|
3477
|
+
});
|
|
3478
|
+
let summary = `PR #${pr.number}: ${pr.title}
|
|
3479
|
+
`;
|
|
3480
|
+
summary += `Author: ${pr.user?.login}
|
|
3481
|
+
`;
|
|
3482
|
+
summary += `State: ${pr.state} (${pr.merged ? "merged" : pr.mergeable_state || "unknown"})
|
|
3483
|
+
`;
|
|
3484
|
+
summary += `Branch: ${pr.head.ref} \u2192 ${pr.base.ref}
|
|
3485
|
+
`;
|
|
3486
|
+
summary += `Changed files: ${pr.changed_files} | Additions: +${pr.additions} | Deletions: -${pr.deletions}
|
|
3487
|
+
|
|
3488
|
+
`;
|
|
3489
|
+
if (pr.body) {
|
|
3490
|
+
summary += `Description:
|
|
3491
|
+
${pr.body.slice(0, 2e3)}
|
|
3492
|
+
|
|
3493
|
+
`;
|
|
3494
|
+
}
|
|
3495
|
+
if (typeof diff === "string") {
|
|
3496
|
+
const diffLines = diff.split("\n");
|
|
3497
|
+
const maxDiffLines = 200;
|
|
3498
|
+
summary += `Diff (first ${Math.min(diffLines.length, maxDiffLines)} of ${diffLines.length} lines):
|
|
3499
|
+
`;
|
|
3500
|
+
summary += diffLines.slice(0, maxDiffLines).join("\n");
|
|
3501
|
+
if (diffLines.length > maxDiffLines) {
|
|
3502
|
+
summary += `
|
|
3503
|
+
|
|
3504
|
+
... (${diffLines.length - maxDiffLines} more lines)`;
|
|
3505
|
+
}
|
|
3506
|
+
} else {
|
|
3507
|
+
summary += `(Diff not available in text format)`;
|
|
3508
|
+
}
|
|
3509
|
+
if (comment) {
|
|
3510
|
+
try {
|
|
3511
|
+
await githubRequest(`/repos/${repo.owner}/${repo.repo}/pulls/${number}/reviews`, {
|
|
3512
|
+
method: "POST",
|
|
3513
|
+
body: {
|
|
3514
|
+
body: comment,
|
|
3515
|
+
event: "COMMENT"
|
|
3516
|
+
}
|
|
3517
|
+
});
|
|
3518
|
+
summary += `
|
|
3519
|
+
|
|
3520
|
+
Review comment posted.`;
|
|
3521
|
+
} catch (err) {
|
|
3522
|
+
summary += `
|
|
3523
|
+
|
|
3524
|
+
Failed to post review comment: ${err.message}`;
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
return summary;
|
|
3528
|
+
} catch (err) {
|
|
3529
|
+
return `Error reviewing PR: ${err.message}`;
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
});
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3535
|
+
// src/capabilities/github/list-issues.ts
|
|
3536
|
+
import { tool as tool26 } from "ai";
|
|
3537
|
+
import { z as z26 } from "zod";
|
|
3538
|
+
function createListIssuesTool() {
|
|
3539
|
+
return tool26({
|
|
3540
|
+
description: "List open GitHub issues for the current repository. Requires GITHUB_TOKEN.",
|
|
3541
|
+
parameters: z26.object({
|
|
3542
|
+
state: z26.enum(["open", "closed", "all"]).describe("Filter by issue state").default("open"),
|
|
3543
|
+
labels: z26.string().describe("Comma-separated label names to filter by (optional)").optional(),
|
|
3544
|
+
limit: z26.number().describe("Maximum number of issues to return").default(10)
|
|
3545
|
+
}),
|
|
3546
|
+
execute: async ({ state, labels, limit }) => {
|
|
3547
|
+
try {
|
|
3548
|
+
const repo = await getCurrentRepo();
|
|
3549
|
+
if (!repo) return "Error: Could not detect GitHub repository.";
|
|
3550
|
+
const params = new URLSearchParams();
|
|
3551
|
+
params.set("state", state);
|
|
3552
|
+
params.set("per_page", String(Math.min(limit, 100)));
|
|
3553
|
+
params.set("sort", "updated");
|
|
3554
|
+
params.set("direction", "desc");
|
|
3555
|
+
if (labels) params.set("labels", labels);
|
|
3556
|
+
const issues = await githubRequest(`/repos/${repo.owner}/${repo.repo}/issues?${params}`);
|
|
3557
|
+
if (!Array.isArray(issues) || issues.length === 0) {
|
|
3558
|
+
return `No ${state} issues found.`;
|
|
3559
|
+
}
|
|
3560
|
+
const lines = issues.map((issue) => {
|
|
3561
|
+
const labelStr = issue.labels?.map((l) => `[${l.name}]`).join(" ") || "";
|
|
3562
|
+
return `#${issue.number} ${issue.title} ${labelStr} (${issue.state}, by ${issue.user?.login})`;
|
|
3563
|
+
});
|
|
3564
|
+
return `Issues in ${repo.owner}/${repo.repo} (${state}):
|
|
3565
|
+
${lines.join("\n")}`;
|
|
3566
|
+
} catch (err) {
|
|
3567
|
+
return `Error listing issues: ${err.message}`;
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
});
|
|
3571
|
+
}
|
|
3572
|
+
|
|
3573
|
+
// src/capabilities/github/create-issue.ts
|
|
3574
|
+
import { tool as tool27 } from "ai";
|
|
3575
|
+
import { z as z27 } from "zod";
|
|
3576
|
+
function createCreateIssueTool() {
|
|
3577
|
+
return tool27({
|
|
3578
|
+
description: "Create a new GitHub issue in the current repository. Requires GITHUB_TOKEN.",
|
|
3579
|
+
parameters: z27.object({
|
|
3580
|
+
title: z27.string().describe("Issue title"),
|
|
3581
|
+
body: z27.string().describe("Issue description (markdown supported)").default(""),
|
|
3582
|
+
labels: z27.array(z27.string()).describe("Label names to apply").optional()
|
|
3583
|
+
}),
|
|
3584
|
+
execute: async ({ title, body, labels }) => {
|
|
3585
|
+
try {
|
|
3586
|
+
const repo = await getCurrentRepo();
|
|
3587
|
+
if (!repo) return "Error: Could not detect GitHub repository.";
|
|
3588
|
+
const payload = { title, body };
|
|
3589
|
+
if (labels && labels.length > 0) payload.labels = labels;
|
|
3590
|
+
const result = await githubRequest(`/repos/${repo.owner}/${repo.repo}/issues`, {
|
|
3591
|
+
method: "POST",
|
|
3592
|
+
body: payload
|
|
3593
|
+
});
|
|
3594
|
+
return `Issue created: ${result.html_url}
|
|
3595
|
+
#${result.number}: ${result.title}`;
|
|
3596
|
+
} catch (err) {
|
|
3597
|
+
return `Error creating issue: ${err.message}`;
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
});
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
// src/capabilities/github/github-api.ts
|
|
3604
|
+
import { tool as tool28 } from "ai";
|
|
3605
|
+
import { z as z28 } from "zod";
|
|
3606
|
+
function createGithubApiTool(permissions) {
|
|
3607
|
+
return tool28({
|
|
3608
|
+
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.",
|
|
3609
|
+
parameters: z28.object({
|
|
3610
|
+
path: z28.string().describe("API path (e.g., /repos/owner/repo/releases)"),
|
|
3611
|
+
method: z28.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).describe("HTTP method").default("GET"),
|
|
3612
|
+
body: z28.string().describe("JSON body for write requests (as a JSON string)").optional()
|
|
3613
|
+
}),
|
|
3614
|
+
execute: async ({ path: path3, method, body }) => {
|
|
3615
|
+
const isWrite = ["POST", "PUT", "PATCH", "DELETE"].includes(method);
|
|
3616
|
+
if (isWrite) {
|
|
3617
|
+
const check = await permissions.checkShellCommand(`github-api ${method} ${path3}`);
|
|
3618
|
+
if (!check.allowed) {
|
|
3619
|
+
if (check.needsApproval) {
|
|
3620
|
+
return `This GitHub API write operation requires approval: ${method} ${path3}
|
|
3621
|
+
|
|
3622
|
+
Tell the user what this does and ask for confirmation. If approved, try again.`;
|
|
3623
|
+
}
|
|
3624
|
+
return `Error: ${check.reason}`;
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
try {
|
|
3628
|
+
let parsedBody;
|
|
3629
|
+
if (body) {
|
|
3630
|
+
try {
|
|
3631
|
+
parsedBody = JSON.parse(body);
|
|
3632
|
+
} catch {
|
|
3633
|
+
return "Error: body must be valid JSON.";
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
const result = await githubRequest(path3, {
|
|
3637
|
+
method,
|
|
3638
|
+
body: parsedBody
|
|
3639
|
+
});
|
|
3640
|
+
if (result === null) return "Request completed (204 No Content).";
|
|
3641
|
+
if (typeof result === "string") return result;
|
|
3642
|
+
return JSON.stringify(result, null, 2);
|
|
3643
|
+
} catch (err) {
|
|
3644
|
+
return `Error: ${err.message}`;
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
});
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3650
|
+
// src/capabilities/web/fetch-url.ts
|
|
3651
|
+
import { tool as tool29 } from "ai";
|
|
3652
|
+
import { z as z29 } from "zod";
|
|
3342
3653
|
var MAX_CONTENT_LENGTH = 15e3;
|
|
3343
3654
|
function stripHtml(html) {
|
|
3344
3655
|
let text = html;
|
|
@@ -3371,11 +3682,11 @@ function stripHtml(html) {
|
|
|
3371
3682
|
return text;
|
|
3372
3683
|
}
|
|
3373
3684
|
function createFetchUrlTool() {
|
|
3374
|
-
return
|
|
3685
|
+
return tool29({
|
|
3375
3686
|
description: "Fetch a URL and return its content as text. Strips HTML to readable markdown-like format. Useful for reading documentation, APIs, or web pages.",
|
|
3376
|
-
parameters:
|
|
3377
|
-
url:
|
|
3378
|
-
format:
|
|
3687
|
+
parameters: z29.object({
|
|
3688
|
+
url: z29.string().describe("The URL to fetch"),
|
|
3689
|
+
format: z29.enum(["text", "markdown"]).optional().describe("Output format (default: markdown)")
|
|
3379
3690
|
}),
|
|
3380
3691
|
execute: async ({ url, format }) => {
|
|
3381
3692
|
const outputFormat = format ?? "markdown";
|
|
@@ -3497,6 +3808,14 @@ var CapabilityRegistry = class {
|
|
|
3497
3808
|
this.tools.git_push = createGitPushTool(this.permissions);
|
|
3498
3809
|
logger.info("Git tools registered");
|
|
3499
3810
|
}
|
|
3811
|
+
if (isGitHubConfigured()) {
|
|
3812
|
+
this.tools.create_pr = createCreatePrTool();
|
|
3813
|
+
this.tools.review_pr = createReviewPrTool();
|
|
3814
|
+
this.tools.list_issues = createListIssuesTool();
|
|
3815
|
+
this.tools.create_issue = createCreateIssueTool();
|
|
3816
|
+
this.tools.github_api = createGithubApiTool(this.permissions);
|
|
3817
|
+
logger.info("GitHub tools registered");
|
|
3818
|
+
}
|
|
3500
3819
|
this.tools.fetch_url = createFetchUrlTool();
|
|
3501
3820
|
logger.info("Web fetch tool registered");
|
|
3502
3821
|
}
|
|
@@ -4309,7 +4628,7 @@ async function runWithWatchdog(agentFn) {
|
|
|
4309
4628
|
await attempt();
|
|
4310
4629
|
}
|
|
4311
4630
|
function sleep(ms) {
|
|
4312
|
-
return new Promise((
|
|
4631
|
+
return new Promise((resolve11) => setTimeout(resolve11, ms));
|
|
4313
4632
|
}
|
|
4314
4633
|
|
|
4315
4634
|
// src/index.ts
|
|
@@ -4348,10 +4667,10 @@ function splashScreen() {
|
|
|
4348
4667
|
}
|
|
4349
4668
|
async function ask(prompt) {
|
|
4350
4669
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
4351
|
-
return new Promise((
|
|
4670
|
+
return new Promise((resolve11) => {
|
|
4352
4671
|
rl.question(prompt, (answer) => {
|
|
4353
4672
|
rl.close();
|
|
4354
|
-
|
|
4673
|
+
resolve11(answer.trim());
|
|
4355
4674
|
});
|
|
4356
4675
|
});
|
|
4357
4676
|
}
|
|
@@ -4360,6 +4679,17 @@ function maskKey(key) {
|
|
|
4360
4679
|
if (key.length <= 8) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
4361
4680
|
return key.slice(0, 4) + "\u2022\u2022\u2022\u2022" + key.slice(-4);
|
|
4362
4681
|
}
|
|
4682
|
+
function appendToEnv(key, value) {
|
|
4683
|
+
const envPath = join11(getMercuryHome(), ".env");
|
|
4684
|
+
let envContent = "";
|
|
4685
|
+
if (existsSync16(envPath)) {
|
|
4686
|
+
envContent = readFileSync12(envPath, "utf-8");
|
|
4687
|
+
}
|
|
4688
|
+
const lines = envContent.split("\n").filter((l) => !l.startsWith(`${key}=`) && l.trim() !== "");
|
|
4689
|
+
lines.push(`${key}=${value}`);
|
|
4690
|
+
writeFileSync13(envPath, lines.join("\n") + "\n", "utf-8");
|
|
4691
|
+
process.env[key] = value;
|
|
4692
|
+
}
|
|
4363
4693
|
async function configure(existingConfig) {
|
|
4364
4694
|
const isReconfig = !!existingConfig;
|
|
4365
4695
|
const config = existingConfig ?? loadConfig();
|
|
@@ -4458,6 +4788,23 @@ async function configure(existingConfig) {
|
|
|
4458
4788
|
}
|
|
4459
4789
|
hr();
|
|
4460
4790
|
console.log("");
|
|
4791
|
+
console.log(chalk6.bold.white(" GitHub (optional)"));
|
|
4792
|
+
console.log(chalk6.dim(" Connect Mercury to GitHub for PRs, issues, and co-authored commits."));
|
|
4793
|
+
console.log(chalk6.dim(" Leave empty to skip. You can add it later with mercury doctor."));
|
|
4794
|
+
console.log("");
|
|
4795
|
+
const ghUserCurrent = isReconfig && config.github.username ? ` [${config.github.username}]` : "";
|
|
4796
|
+
const ghUsername = await ask(chalk6.white(` GitHub username${ghUserCurrent}: `));
|
|
4797
|
+
if (ghUsername) config.github.username = ghUsername;
|
|
4798
|
+
const ghEmailCurrent = isReconfig && config.github.email ? ` [${config.github.email}]` : "";
|
|
4799
|
+
const ghEmail = await ask(chalk6.white(` GitHub email${ghEmailCurrent}: `));
|
|
4800
|
+
if (ghEmail) config.github.email = ghEmail;
|
|
4801
|
+
const ghTokenCurrent = process.env.GITHUB_TOKEN ? ` [${maskKey(process.env.GITHUB_TOKEN)}]` : "";
|
|
4802
|
+
const ghToken = await ask(chalk6.white(` GitHub PAT (repo scope)${ghTokenCurrent}: `));
|
|
4803
|
+
if (ghToken) {
|
|
4804
|
+
appendToEnv("GITHUB_TOKEN", ghToken);
|
|
4805
|
+
}
|
|
4806
|
+
hr();
|
|
4807
|
+
console.log("");
|
|
4461
4808
|
console.log(chalk6.bold.white(" Token Budget"));
|
|
4462
4809
|
console.log("");
|
|
4463
4810
|
const budgetPrompt = isReconfig ? chalk6.white(` Daily token budget [${config.tokens.dailyBudget.toLocaleString()}]: `) : chalk6.white(` Daily token budget [${config.tokens.dailyBudget.toLocaleString()}]: `);
|
|
@@ -4558,6 +4905,9 @@ async function runAgent(isDaemon = false) {
|
|
|
4558
4905
|
await msg.sendFile(filePath);
|
|
4559
4906
|
}
|
|
4560
4907
|
});
|
|
4908
|
+
if (process.env.GITHUB_TOKEN) {
|
|
4909
|
+
setGitHubToken(process.env.GITHUB_TOKEN);
|
|
4910
|
+
}
|
|
4561
4911
|
capabilities.registerAll();
|
|
4562
4912
|
const agent = new Agent(
|
|
4563
4913
|
config,
|