@cosmicstack/mercury-agent 0.3.0 → 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 +52 -42
- 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
|
}
|
|
@@ -1006,6 +1010,12 @@ You can override this:
|
|
|
1006
1010
|
if (this.tokenBudget.getUsagePercentage() > 70) {
|
|
1007
1011
|
prompt += "\nBe concise to conserve tokens.";
|
|
1008
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
|
+
}
|
|
1009
1019
|
return prompt;
|
|
1010
1020
|
}
|
|
1011
1021
|
async processInternalPrompt(prompt, channelId, channelType) {
|
|
@@ -1709,16 +1719,16 @@ var CLIChannel = class extends BaseChannel {
|
|
|
1709
1719
|
}
|
|
1710
1720
|
}
|
|
1711
1721
|
async prompt(question) {
|
|
1712
|
-
return new Promise((
|
|
1713
|
-
this.rl?.question(question, (answer) =>
|
|
1722
|
+
return new Promise((resolve11) => {
|
|
1723
|
+
this.rl?.question(question, (answer) => resolve11(answer.trim()));
|
|
1714
1724
|
});
|
|
1715
1725
|
}
|
|
1716
1726
|
async askPermission(prompt) {
|
|
1717
|
-
return new Promise((
|
|
1727
|
+
return new Promise((resolve11) => {
|
|
1718
1728
|
console.log("");
|
|
1719
1729
|
console.log(chalk2.yellow(` \u26A0 ${prompt}`));
|
|
1720
1730
|
this.rl?.question(chalk2.yellow(" > "), (answer) => {
|
|
1721
|
-
|
|
1731
|
+
resolve11(answer.trim());
|
|
1722
1732
|
});
|
|
1723
1733
|
});
|
|
1724
1734
|
}
|
|
@@ -1981,15 +1991,15 @@ var TelegramChannel = class extends BaseChannel {
|
|
|
1981
1991
|
reply_markup: keyboard
|
|
1982
1992
|
});
|
|
1983
1993
|
}
|
|
1984
|
-
return new Promise((
|
|
1985
|
-
this.pendingApprovals.set(`${id}:yes`, () =>
|
|
1986
|
-
this.pendingApprovals.set(`${id}:always`, () =>
|
|
1987
|
-
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"));
|
|
1988
1998
|
setTimeout(() => {
|
|
1989
1999
|
this.pendingApprovals.delete(`${id}:yes`);
|
|
1990
2000
|
this.pendingApprovals.delete(`${id}:always`);
|
|
1991
2001
|
this.pendingApprovals.delete(`${id}:no`);
|
|
1992
|
-
|
|
2002
|
+
resolve11("no");
|
|
1993
2003
|
}, 12e4);
|
|
1994
2004
|
});
|
|
1995
2005
|
}
|
|
@@ -2213,7 +2223,7 @@ var TokenBudget = class {
|
|
|
2213
2223
|
|
|
2214
2224
|
// src/capabilities/permissions.ts
|
|
2215
2225
|
import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
|
|
2216
|
-
import { join as join6, resolve, sep } from "path";
|
|
2226
|
+
import { join as join6, resolve as resolve2, sep } from "path";
|
|
2217
2227
|
import { homedir as homedir2 } from "os";
|
|
2218
2228
|
import { parse as parseYaml3, stringify as stringifyYaml3 } from "yaml";
|
|
2219
2229
|
var DEFAULT_MANIFEST = {
|
|
@@ -2396,7 +2406,7 @@ var PermissionManager = class {
|
|
|
2396
2406
|
if (!fs3.enabled) {
|
|
2397
2407
|
return { allowed: false, reason: "Filesystem capability is disabled" };
|
|
2398
2408
|
}
|
|
2399
|
-
const resolved =
|
|
2409
|
+
const resolved = resolve2(path3);
|
|
2400
2410
|
const scope = this.findScope(resolved);
|
|
2401
2411
|
const tempScope = this.findTempScope(resolved);
|
|
2402
2412
|
if (scope) {
|
|
@@ -2486,7 +2496,7 @@ var PermissionManager = class {
|
|
|
2486
2496
|
return this.manifest.capabilities.git.enabled && this.manifest.capabilities.git.approveWrite;
|
|
2487
2497
|
}
|
|
2488
2498
|
addScope(path3, read, write) {
|
|
2489
|
-
const resolved =
|
|
2499
|
+
const resolved = resolve2(path3);
|
|
2490
2500
|
const existing = this.findScope(resolved);
|
|
2491
2501
|
if (existing) {
|
|
2492
2502
|
existing.read = existing.read || read;
|
|
@@ -2504,7 +2514,7 @@ var PermissionManager = class {
|
|
|
2504
2514
|
findScope(resolvedPath) {
|
|
2505
2515
|
const scopes = this.manifest.capabilities.filesystem.scopes;
|
|
2506
2516
|
for (const scope of scopes) {
|
|
2507
|
-
const scopeResolved =
|
|
2517
|
+
const scopeResolved = resolve2(scope.path.replace(/^~/, homedir2()));
|
|
2508
2518
|
if (resolvedPath === scopeResolved || resolvedPath.startsWith(scopeResolved + sep)) {
|
|
2509
2519
|
return scope;
|
|
2510
2520
|
}
|
|
@@ -2531,13 +2541,13 @@ Allow access?`;
|
|
|
2531
2541
|
return { allowed: false, reason: `Permission denied for ${mode} access to ${path3}` };
|
|
2532
2542
|
}
|
|
2533
2543
|
addTempScope(path3, read, write) {
|
|
2534
|
-
const resolved =
|
|
2544
|
+
const resolved = resolve2(path3);
|
|
2535
2545
|
this.tempScopes.push({ path: resolved, read, write });
|
|
2536
2546
|
logger.info({ path: resolved, read, write }, "Temp permission scope added (session only)");
|
|
2537
2547
|
}
|
|
2538
2548
|
findTempScope(resolvedPath) {
|
|
2539
2549
|
for (const scope of this.tempScopes) {
|
|
2540
|
-
const scopeResolved =
|
|
2550
|
+
const scopeResolved = resolve2(scope.path.replace(/^~/, homedir2()));
|
|
2541
2551
|
if (resolvedPath === scopeResolved || resolvedPath.startsWith(scopeResolved + sep)) {
|
|
2542
2552
|
return scope;
|
|
2543
2553
|
}
|
|
@@ -2563,7 +2573,7 @@ Allow access?`;
|
|
|
2563
2573
|
for (const p of pathPatterns) {
|
|
2564
2574
|
const match = command.match(p);
|
|
2565
2575
|
if (match) {
|
|
2566
|
-
const candidate =
|
|
2576
|
+
const candidate = resolve2(match[1].replace(/^~/, homedir2()));
|
|
2567
2577
|
if (!candidate.startsWith(this.cwd)) {
|
|
2568
2578
|
return candidate;
|
|
2569
2579
|
}
|
|
@@ -2599,7 +2609,7 @@ Allow access?`;
|
|
|
2599
2609
|
import { tool } from "ai";
|
|
2600
2610
|
import { z } from "zod";
|
|
2601
2611
|
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
2602
|
-
import { resolve as
|
|
2612
|
+
import { resolve as resolve3 } from "path";
|
|
2603
2613
|
function createReadFileTool(permissions) {
|
|
2604
2614
|
return tool({
|
|
2605
2615
|
description: "Read the contents of a file. The path must be within an allowed scope.",
|
|
@@ -2607,10 +2617,10 @@ function createReadFileTool(permissions) {
|
|
|
2607
2617
|
path: z.string().describe("Absolute or relative path to the file")
|
|
2608
2618
|
}),
|
|
2609
2619
|
execute: async ({ path: path3 }) => {
|
|
2610
|
-
const resolved =
|
|
2620
|
+
const resolved = resolve3(path3);
|
|
2611
2621
|
const check = await permissions.checkFsAccess(resolved, "read");
|
|
2612
2622
|
if (!check.allowed) {
|
|
2613
|
-
const parentDir =
|
|
2623
|
+
const parentDir = resolve3(resolved, "..");
|
|
2614
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.`;
|
|
2615
2625
|
}
|
|
2616
2626
|
if (!existsSync7(resolved)) {
|
|
@@ -2636,7 +2646,7 @@ function createReadFileTool(permissions) {
|
|
|
2636
2646
|
import { tool as tool2 } from "ai";
|
|
2637
2647
|
import { z as z2 } from "zod";
|
|
2638
2648
|
import { existsSync as existsSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
2639
|
-
import { resolve as
|
|
2649
|
+
import { resolve as resolve4 } from "path";
|
|
2640
2650
|
function createWriteFileTool(permissions) {
|
|
2641
2651
|
return tool2({
|
|
2642
2652
|
description: "Write content to an existing file. The path must be within a writable scope.",
|
|
@@ -2645,10 +2655,10 @@ function createWriteFileTool(permissions) {
|
|
|
2645
2655
|
content: z2.string().describe("The content to write to the file")
|
|
2646
2656
|
}),
|
|
2647
2657
|
execute: async ({ path: path3, content }) => {
|
|
2648
|
-
const resolved =
|
|
2658
|
+
const resolved = resolve4(path3);
|
|
2649
2659
|
const check = await permissions.checkFsAccess(resolved, "write");
|
|
2650
2660
|
if (!check.allowed) {
|
|
2651
|
-
const parentDir =
|
|
2661
|
+
const parentDir = resolve4(resolved, "..");
|
|
2652
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.`;
|
|
2653
2663
|
}
|
|
2654
2664
|
if (!existsSync8(resolved)) {
|
|
@@ -2668,7 +2678,7 @@ function createWriteFileTool(permissions) {
|
|
|
2668
2678
|
import { tool as tool3 } from "ai";
|
|
2669
2679
|
import { z as z3 } from "zod";
|
|
2670
2680
|
import { existsSync as existsSync9, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7 } from "fs";
|
|
2671
|
-
import { resolve as
|
|
2681
|
+
import { resolve as resolve5, dirname as dirname2 } from "path";
|
|
2672
2682
|
function createCreateFileTool(permissions) {
|
|
2673
2683
|
return tool3({
|
|
2674
2684
|
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 +2687,10 @@ function createCreateFileTool(permissions) {
|
|
|
2677
2687
|
content: z3.string().describe("The content of the new file")
|
|
2678
2688
|
}),
|
|
2679
2689
|
execute: async ({ path: path3, content }) => {
|
|
2680
|
-
const resolved =
|
|
2690
|
+
const resolved = resolve5(path3);
|
|
2681
2691
|
const check = await permissions.checkFsAccess(resolved, "write");
|
|
2682
2692
|
if (!check.allowed) {
|
|
2683
|
-
const parentDir =
|
|
2693
|
+
const parentDir = resolve5(resolved, "..");
|
|
2684
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.`;
|
|
2685
2695
|
}
|
|
2686
2696
|
if (existsSync9(resolved)) {
|
|
@@ -2704,7 +2714,7 @@ function createCreateFileTool(permissions) {
|
|
|
2704
2714
|
import { tool as tool4 } from "ai";
|
|
2705
2715
|
import { z as z4 } from "zod";
|
|
2706
2716
|
import { existsSync as existsSync10, readdirSync as readdirSync2, statSync } from "fs";
|
|
2707
|
-
import { resolve as
|
|
2717
|
+
import { resolve as resolve6 } from "path";
|
|
2708
2718
|
function createListDirTool(permissions) {
|
|
2709
2719
|
return tool4({
|
|
2710
2720
|
description: "List the contents of a directory. Shows file names, types, and sizes.",
|
|
@@ -2712,7 +2722,7 @@ function createListDirTool(permissions) {
|
|
|
2712
2722
|
path: z4.string().describe("Absolute or relative path to the directory")
|
|
2713
2723
|
}),
|
|
2714
2724
|
execute: async ({ path: path3 }) => {
|
|
2715
|
-
const resolved =
|
|
2725
|
+
const resolved = resolve6(path3);
|
|
2716
2726
|
const check = await permissions.checkFsAccess(resolved, "read");
|
|
2717
2727
|
if (!check.allowed) {
|
|
2718
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.`;
|
|
@@ -2762,7 +2772,7 @@ function formatSize(bytes) {
|
|
|
2762
2772
|
import { tool as tool5 } from "ai";
|
|
2763
2773
|
import { z as z5 } from "zod";
|
|
2764
2774
|
import { existsSync as existsSync11, unlinkSync as unlinkSync2 } from "fs";
|
|
2765
|
-
import { resolve as
|
|
2775
|
+
import { resolve as resolve7 } from "path";
|
|
2766
2776
|
function createDeleteFileTool(permissions) {
|
|
2767
2777
|
return tool5({
|
|
2768
2778
|
description: "Delete a file. This action cannot be undone. The path must be within a writable scope. Always asks for confirmation.",
|
|
@@ -2770,10 +2780,10 @@ function createDeleteFileTool(permissions) {
|
|
|
2770
2780
|
path: z5.string().describe("Absolute or relative path to the file to delete")
|
|
2771
2781
|
}),
|
|
2772
2782
|
execute: async ({ path: path3 }) => {
|
|
2773
|
-
const resolved =
|
|
2783
|
+
const resolved = resolve7(path3);
|
|
2774
2784
|
const check = await permissions.checkFsAccess(resolved, "write");
|
|
2775
2785
|
if (!check.allowed) {
|
|
2776
|
-
const parentDir =
|
|
2786
|
+
const parentDir = resolve7(resolved, "..");
|
|
2777
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.`;
|
|
2778
2788
|
}
|
|
2779
2789
|
if (!existsSync11(resolved)) {
|
|
@@ -2797,7 +2807,7 @@ function createDeleteFileTool(permissions) {
|
|
|
2797
2807
|
import { tool as tool6 } from "ai";
|
|
2798
2808
|
import { z as z6 } from "zod";
|
|
2799
2809
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
2800
|
-
import { resolve as
|
|
2810
|
+
import { resolve as resolve8 } from "path";
|
|
2801
2811
|
function createEditFileTool(permissions) {
|
|
2802
2812
|
return tool6({
|
|
2803
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.",
|
|
@@ -2807,10 +2817,10 @@ function createEditFileTool(permissions) {
|
|
|
2807
2817
|
new_string: z6.string().describe("The text to replace it with")
|
|
2808
2818
|
}),
|
|
2809
2819
|
execute: async ({ path: path3, old_string, new_string }) => {
|
|
2810
|
-
const resolved =
|
|
2820
|
+
const resolved = resolve8(path3);
|
|
2811
2821
|
const fsCheck = await permissions.checkFsAccess(resolved, "write");
|
|
2812
2822
|
if (!fsCheck.allowed) {
|
|
2813
|
-
const parentDir =
|
|
2823
|
+
const parentDir = resolve8(resolved, "..");
|
|
2814
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.`;
|
|
2815
2825
|
}
|
|
2816
2826
|
try {
|
|
@@ -2841,7 +2851,7 @@ function createEditFileTool(permissions) {
|
|
|
2841
2851
|
import { tool as tool7 } from "ai";
|
|
2842
2852
|
import { z as z7 } from "zod";
|
|
2843
2853
|
import { existsSync as existsSync12, statSync as statSync2 } from "fs";
|
|
2844
|
-
import { resolve as
|
|
2854
|
+
import { resolve as resolve9, basename } from "path";
|
|
2845
2855
|
function createSendFileTool(permissions, sendFile) {
|
|
2846
2856
|
return tool7({
|
|
2847
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.",
|
|
@@ -2849,10 +2859,10 @@ function createSendFileTool(permissions, sendFile) {
|
|
|
2849
2859
|
path: z7.string().describe("Absolute or relative path to the file to send")
|
|
2850
2860
|
}),
|
|
2851
2861
|
execute: async ({ path: path3 }) => {
|
|
2852
|
-
const resolved =
|
|
2862
|
+
const resolved = resolve9(path3);
|
|
2853
2863
|
const check = await permissions.checkFsAccess(resolved, "read");
|
|
2854
2864
|
if (!check.allowed) {
|
|
2855
|
-
const parentDir =
|
|
2865
|
+
const parentDir = resolve9(resolved, "..");
|
|
2856
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.`;
|
|
2857
2867
|
}
|
|
2858
2868
|
if (!existsSync12(resolved)) {
|
|
@@ -2880,7 +2890,7 @@ function createSendFileTool(permissions, sendFile) {
|
|
|
2880
2890
|
// src/capabilities/filesystem/approve-scope.ts
|
|
2881
2891
|
import { tool as tool8 } from "ai";
|
|
2882
2892
|
import { z as z8 } from "zod";
|
|
2883
|
-
import { resolve as
|
|
2893
|
+
import { resolve as resolve10 } from "path";
|
|
2884
2894
|
function createApproveScopeTool(permissions) {
|
|
2885
2895
|
return tool8({
|
|
2886
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.',
|
|
@@ -2889,7 +2899,7 @@ function createApproveScopeTool(permissions) {
|
|
|
2889
2899
|
mode: z8.enum(["read", "write"]).describe("The access mode needed")
|
|
2890
2900
|
}),
|
|
2891
2901
|
execute: async ({ path: path3, mode }) => {
|
|
2892
|
-
const resolved =
|
|
2902
|
+
const resolved = resolve10(path3);
|
|
2893
2903
|
const result = await permissions.requestScopeExternal(resolved, mode);
|
|
2894
2904
|
if (result.allowed) {
|
|
2895
2905
|
return `Access approved for ${mode} access to ${resolved}. You can now retry the file operation.`;
|
|
@@ -4618,7 +4628,7 @@ async function runWithWatchdog(agentFn) {
|
|
|
4618
4628
|
await attempt();
|
|
4619
4629
|
}
|
|
4620
4630
|
function sleep(ms) {
|
|
4621
|
-
return new Promise((
|
|
4631
|
+
return new Promise((resolve11) => setTimeout(resolve11, ms));
|
|
4622
4632
|
}
|
|
4623
4633
|
|
|
4624
4634
|
// src/index.ts
|
|
@@ -4657,10 +4667,10 @@ function splashScreen() {
|
|
|
4657
4667
|
}
|
|
4658
4668
|
async function ask(prompt) {
|
|
4659
4669
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
4660
|
-
return new Promise((
|
|
4670
|
+
return new Promise((resolve11) => {
|
|
4661
4671
|
rl.question(prompt, (answer) => {
|
|
4662
4672
|
rl.close();
|
|
4663
|
-
|
|
4673
|
+
resolve11(answer.trim());
|
|
4664
4674
|
});
|
|
4665
4675
|
});
|
|
4666
4676
|
}
|