@cosmicstack/mercury-agent 0.2.6 → 0.3.0
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 +361 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
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";
|
|
8
8
|
import readline2 from "readline";
|
|
9
9
|
import chalk6 from "chalk";
|
|
10
|
-
import figlet from "figlet";
|
|
11
10
|
|
|
12
11
|
// src/utils/config.ts
|
|
13
12
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
@@ -74,6 +73,10 @@ function getDefaultConfig() {
|
|
|
74
73
|
streaming: getEnvBool("TELEGRAM_STREAMING", true)
|
|
75
74
|
}
|
|
76
75
|
},
|
|
76
|
+
github: {
|
|
77
|
+
username: getEnv("GITHUB_USERNAME", ""),
|
|
78
|
+
email: getEnv("GITHUB_EMAIL", "")
|
|
79
|
+
},
|
|
77
80
|
memory: {
|
|
78
81
|
dir: getEnv("MEMORY_DIR", join(home, "memory")),
|
|
79
82
|
shortTermMaxMessages: getEnvNum("SHORT_TERM_MAX_MESSAGES", 20)
|
|
@@ -2335,8 +2338,8 @@ var PermissionManager = class {
|
|
|
2335
2338
|
clearElevation() {
|
|
2336
2339
|
this.elevatedCommands.clear();
|
|
2337
2340
|
}
|
|
2338
|
-
isElevated(
|
|
2339
|
-
if (this.elevatedCommands.has(
|
|
2341
|
+
isElevated(tool30) {
|
|
2342
|
+
if (this.elevatedCommands.has(tool30)) return true;
|
|
2340
2343
|
return false;
|
|
2341
2344
|
}
|
|
2342
2345
|
isShellElevated() {
|
|
@@ -3282,15 +3285,19 @@ function createGitAddTool() {
|
|
|
3282
3285
|
import { tool as tool22 } from "ai";
|
|
3283
3286
|
import { z as z22 } from "zod";
|
|
3284
3287
|
import { execSync as execSync6 } from "child_process";
|
|
3288
|
+
var CO_AUTHOR = "Mercury <mercury@cosmicstack.org>";
|
|
3285
3289
|
function createGitCommitTool() {
|
|
3286
3290
|
return tool22({
|
|
3287
|
-
description: "Record changes to the repository. Creates a new commit with staged changes.",
|
|
3291
|
+
description: "Record changes to the repository. Creates a new commit with staged changes. Automatically includes a Co-authored-by trailer for attribution.",
|
|
3288
3292
|
parameters: z22.object({
|
|
3289
3293
|
message: z22.string().describe("Commit message")
|
|
3290
3294
|
}),
|
|
3291
3295
|
execute: async ({ message }) => {
|
|
3292
3296
|
try {
|
|
3293
|
-
const
|
|
3297
|
+
const fullMessage = `${message}
|
|
3298
|
+
|
|
3299
|
+
Co-authored-by: ${CO_AUTHOR}`;
|
|
3300
|
+
const escapedMsg = fullMessage.replace(/"/g, '\\"');
|
|
3294
3301
|
const result = execSync6(`git commit -m "${escapedMsg}"`, { encoding: "utf-8", timeout: 1e4 });
|
|
3295
3302
|
return result.trim() || "Committed successfully.";
|
|
3296
3303
|
} catch (err) {
|
|
@@ -3337,9 +3344,302 @@ Ask the user for permission. If they approve, try again. If they say "always", u
|
|
|
3337
3344
|
});
|
|
3338
3345
|
}
|
|
3339
3346
|
|
|
3340
|
-
// src/capabilities/
|
|
3347
|
+
// src/capabilities/github/create-pr.ts
|
|
3341
3348
|
import { tool as tool24 } from "ai";
|
|
3342
3349
|
import { z as z24 } from "zod";
|
|
3350
|
+
|
|
3351
|
+
// src/utils/github.ts
|
|
3352
|
+
var GITHUB_API = "https://api.github.com";
|
|
3353
|
+
var cachedToken = null;
|
|
3354
|
+
function setGitHubToken(token) {
|
|
3355
|
+
cachedToken = token;
|
|
3356
|
+
}
|
|
3357
|
+
function getGitHubToken() {
|
|
3358
|
+
if (cachedToken) return cachedToken;
|
|
3359
|
+
return process.env.GITHUB_TOKEN || null;
|
|
3360
|
+
}
|
|
3361
|
+
function isGitHubConfigured() {
|
|
3362
|
+
return !!getGitHubToken();
|
|
3363
|
+
}
|
|
3364
|
+
async function githubRequest(path3, options = {}) {
|
|
3365
|
+
const token = getGitHubToken();
|
|
3366
|
+
if (!token) {
|
|
3367
|
+
throw new Error("GITHUB_TOKEN not configured. Run mercury doctor to set it up.");
|
|
3368
|
+
}
|
|
3369
|
+
const url = path3.startsWith("http") ? path3 : `${GITHUB_API}${path3}`;
|
|
3370
|
+
const headers = {
|
|
3371
|
+
"Authorization": `Bearer ${token}`,
|
|
3372
|
+
"Accept": "application/vnd.github.v3+json",
|
|
3373
|
+
"User-Agent": "Mercury-Agent",
|
|
3374
|
+
...options.headers
|
|
3375
|
+
};
|
|
3376
|
+
const fetchOptions = {
|
|
3377
|
+
method: options.method || "GET",
|
|
3378
|
+
headers
|
|
3379
|
+
};
|
|
3380
|
+
if (options.body) {
|
|
3381
|
+
fetchOptions.body = JSON.stringify(options.body);
|
|
3382
|
+
headers["Content-Type"] = "application/json";
|
|
3383
|
+
}
|
|
3384
|
+
logger.info({ method: fetchOptions.method, path: path3 }, "GitHub API request");
|
|
3385
|
+
const response = await fetch(url, fetchOptions);
|
|
3386
|
+
const remaining = response.headers.get("x-ratelimit-remaining");
|
|
3387
|
+
if (remaining && parseInt(remaining, 10) < 100) {
|
|
3388
|
+
logger.warn({ remaining }, "GitHub API rate limit running low");
|
|
3389
|
+
}
|
|
3390
|
+
if (!response.ok) {
|
|
3391
|
+
const body = await response.text();
|
|
3392
|
+
throw new Error(`GitHub API ${response.status}: ${body.slice(0, 500)}`);
|
|
3393
|
+
}
|
|
3394
|
+
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;
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
// src/capabilities/github/create-pr.ts
|
|
3415
|
+
function createCreatePrTool() {
|
|
3416
|
+
return tool24({
|
|
3417
|
+
description: "Create a pull request on GitHub. Requires GITHUB_TOKEN to be configured.",
|
|
3418
|
+
parameters: z24.object({
|
|
3419
|
+
title: z24.string().describe("PR title"),
|
|
3420
|
+
body: z24.string().describe("PR description (markdown supported)").default(""),
|
|
3421
|
+
head: z24.string().describe("The branch containing the changes"),
|
|
3422
|
+
base: z24.string().describe("The branch to merge into").default("main"),
|
|
3423
|
+
draft: z24.boolean().describe("Create as draft PR").default(false)
|
|
3424
|
+
}),
|
|
3425
|
+
execute: async ({ title, body, head, base, draft }) => {
|
|
3426
|
+
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`, {
|
|
3430
|
+
method: "POST",
|
|
3431
|
+
body: {
|
|
3432
|
+
title,
|
|
3433
|
+
body,
|
|
3434
|
+
head,
|
|
3435
|
+
base,
|
|
3436
|
+
draft
|
|
3437
|
+
}
|
|
3438
|
+
});
|
|
3439
|
+
return `PR created: ${result.html_url}
|
|
3440
|
+
#${result.number}: ${result.title}
|
|
3441
|
+
${draft ? "(draft)" : ""} ${result.state}`;
|
|
3442
|
+
} catch (err) {
|
|
3443
|
+
return `Error creating PR: ${err.message}`;
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
});
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
// src/capabilities/github/review-pr.ts
|
|
3450
|
+
import { tool as tool25 } from "ai";
|
|
3451
|
+
import { z as z25 } from "zod";
|
|
3452
|
+
function createReviewPrTool() {
|
|
3453
|
+
return tool25({
|
|
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
|
+
parameters: z25.object({
|
|
3456
|
+
number: z25.number().describe("PR number"),
|
|
3457
|
+
comment: z25.string().describe("Review comment to post on the PR (optional)").optional()
|
|
3458
|
+
}),
|
|
3459
|
+
execute: async ({ number, comment }) => {
|
|
3460
|
+
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}`);
|
|
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
|
+
let summary = `PR #${pr.number}: ${pr.title}
|
|
3469
|
+
`;
|
|
3470
|
+
summary += `Author: ${pr.user?.login}
|
|
3471
|
+
`;
|
|
3472
|
+
summary += `State: ${pr.state} (${pr.merged ? "merged" : pr.mergeable_state || "unknown"})
|
|
3473
|
+
`;
|
|
3474
|
+
summary += `Branch: ${pr.head.ref} \u2192 ${pr.base.ref}
|
|
3475
|
+
`;
|
|
3476
|
+
summary += `Changed files: ${pr.changed_files} | Additions: +${pr.additions} | Deletions: -${pr.deletions}
|
|
3477
|
+
|
|
3478
|
+
`;
|
|
3479
|
+
if (pr.body) {
|
|
3480
|
+
summary += `Description:
|
|
3481
|
+
${pr.body.slice(0, 2e3)}
|
|
3482
|
+
|
|
3483
|
+
`;
|
|
3484
|
+
}
|
|
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):
|
|
3489
|
+
`;
|
|
3490
|
+
summary += diffLines.slice(0, maxDiffLines).join("\n");
|
|
3491
|
+
if (diffLines.length > maxDiffLines) {
|
|
3492
|
+
summary += `
|
|
3493
|
+
|
|
3494
|
+
... (${diffLines.length - maxDiffLines} more lines)`;
|
|
3495
|
+
}
|
|
3496
|
+
} else {
|
|
3497
|
+
summary += `(Diff not available in text format)`;
|
|
3498
|
+
}
|
|
3499
|
+
if (comment) {
|
|
3500
|
+
try {
|
|
3501
|
+
await githubRequest(`/repos/${repo.owner}/${repo.repo}/pulls/${number}/reviews`, {
|
|
3502
|
+
method: "POST",
|
|
3503
|
+
body: {
|
|
3504
|
+
body: comment,
|
|
3505
|
+
event: "COMMENT"
|
|
3506
|
+
}
|
|
3507
|
+
});
|
|
3508
|
+
summary += `
|
|
3509
|
+
|
|
3510
|
+
Review comment posted.`;
|
|
3511
|
+
} catch (err) {
|
|
3512
|
+
summary += `
|
|
3513
|
+
|
|
3514
|
+
Failed to post review comment: ${err.message}`;
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
return summary;
|
|
3518
|
+
} catch (err) {
|
|
3519
|
+
return `Error reviewing PR: ${err.message}`;
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3522
|
+
});
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
// src/capabilities/github/list-issues.ts
|
|
3526
|
+
import { tool as tool26 } from "ai";
|
|
3527
|
+
import { z as z26 } from "zod";
|
|
3528
|
+
function createListIssuesTool() {
|
|
3529
|
+
return tool26({
|
|
3530
|
+
description: "List open GitHub issues for the current repository. Requires GITHUB_TOKEN.",
|
|
3531
|
+
parameters: z26.object({
|
|
3532
|
+
state: z26.enum(["open", "closed", "all"]).describe("Filter by issue state").default("open"),
|
|
3533
|
+
labels: z26.string().describe("Comma-separated label names to filter by (optional)").optional(),
|
|
3534
|
+
limit: z26.number().describe("Maximum number of issues to return").default(10)
|
|
3535
|
+
}),
|
|
3536
|
+
execute: async ({ state, labels, limit }) => {
|
|
3537
|
+
try {
|
|
3538
|
+
const repo = await getCurrentRepo();
|
|
3539
|
+
if (!repo) return "Error: Could not detect GitHub repository.";
|
|
3540
|
+
const params = new URLSearchParams();
|
|
3541
|
+
params.set("state", state);
|
|
3542
|
+
params.set("per_page", String(Math.min(limit, 100)));
|
|
3543
|
+
params.set("sort", "updated");
|
|
3544
|
+
params.set("direction", "desc");
|
|
3545
|
+
if (labels) params.set("labels", labels);
|
|
3546
|
+
const issues = await githubRequest(`/repos/${repo.owner}/${repo.repo}/issues?${params}`);
|
|
3547
|
+
if (!Array.isArray(issues) || issues.length === 0) {
|
|
3548
|
+
return `No ${state} issues found.`;
|
|
3549
|
+
}
|
|
3550
|
+
const lines = issues.map((issue) => {
|
|
3551
|
+
const labelStr = issue.labels?.map((l) => `[${l.name}]`).join(" ") || "";
|
|
3552
|
+
return `#${issue.number} ${issue.title} ${labelStr} (${issue.state}, by ${issue.user?.login})`;
|
|
3553
|
+
});
|
|
3554
|
+
return `Issues in ${repo.owner}/${repo.repo} (${state}):
|
|
3555
|
+
${lines.join("\n")}`;
|
|
3556
|
+
} catch (err) {
|
|
3557
|
+
return `Error listing issues: ${err.message}`;
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
});
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
// src/capabilities/github/create-issue.ts
|
|
3564
|
+
import { tool as tool27 } from "ai";
|
|
3565
|
+
import { z as z27 } from "zod";
|
|
3566
|
+
function createCreateIssueTool() {
|
|
3567
|
+
return tool27({
|
|
3568
|
+
description: "Create a new GitHub issue in the current repository. Requires GITHUB_TOKEN.",
|
|
3569
|
+
parameters: z27.object({
|
|
3570
|
+
title: z27.string().describe("Issue title"),
|
|
3571
|
+
body: z27.string().describe("Issue description (markdown supported)").default(""),
|
|
3572
|
+
labels: z27.array(z27.string()).describe("Label names to apply").optional()
|
|
3573
|
+
}),
|
|
3574
|
+
execute: async ({ title, body, labels }) => {
|
|
3575
|
+
try {
|
|
3576
|
+
const repo = await getCurrentRepo();
|
|
3577
|
+
if (!repo) return "Error: Could not detect GitHub repository.";
|
|
3578
|
+
const payload = { title, body };
|
|
3579
|
+
if (labels && labels.length > 0) payload.labels = labels;
|
|
3580
|
+
const result = await githubRequest(`/repos/${repo.owner}/${repo.repo}/issues`, {
|
|
3581
|
+
method: "POST",
|
|
3582
|
+
body: payload
|
|
3583
|
+
});
|
|
3584
|
+
return `Issue created: ${result.html_url}
|
|
3585
|
+
#${result.number}: ${result.title}`;
|
|
3586
|
+
} catch (err) {
|
|
3587
|
+
return `Error creating issue: ${err.message}`;
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
});
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
// src/capabilities/github/github-api.ts
|
|
3594
|
+
import { tool as tool28 } from "ai";
|
|
3595
|
+
import { z as z28 } from "zod";
|
|
3596
|
+
function createGithubApiTool(permissions) {
|
|
3597
|
+
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.",
|
|
3599
|
+
parameters: z28.object({
|
|
3600
|
+
path: z28.string().describe("API path (e.g., /repos/owner/repo/releases)"),
|
|
3601
|
+
method: z28.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).describe("HTTP method").default("GET"),
|
|
3602
|
+
body: z28.string().describe("JSON body for write requests (as a JSON string)").optional()
|
|
3603
|
+
}),
|
|
3604
|
+
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
|
+
try {
|
|
3618
|
+
let parsedBody;
|
|
3619
|
+
if (body) {
|
|
3620
|
+
try {
|
|
3621
|
+
parsedBody = JSON.parse(body);
|
|
3622
|
+
} catch {
|
|
3623
|
+
return "Error: body must be valid JSON.";
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
const result = await githubRequest(path3, {
|
|
3627
|
+
method,
|
|
3628
|
+
body: parsedBody
|
|
3629
|
+
});
|
|
3630
|
+
if (result === null) return "Request completed (204 No Content).";
|
|
3631
|
+
if (typeof result === "string") return result;
|
|
3632
|
+
return JSON.stringify(result, null, 2);
|
|
3633
|
+
} catch (err) {
|
|
3634
|
+
return `Error: ${err.message}`;
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
});
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
// src/capabilities/web/fetch-url.ts
|
|
3641
|
+
import { tool as tool29 } from "ai";
|
|
3642
|
+
import { z as z29 } from "zod";
|
|
3343
3643
|
var MAX_CONTENT_LENGTH = 15e3;
|
|
3344
3644
|
function stripHtml(html) {
|
|
3345
3645
|
let text = html;
|
|
@@ -3372,11 +3672,11 @@ function stripHtml(html) {
|
|
|
3372
3672
|
return text;
|
|
3373
3673
|
}
|
|
3374
3674
|
function createFetchUrlTool() {
|
|
3375
|
-
return
|
|
3675
|
+
return tool29({
|
|
3376
3676
|
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.",
|
|
3377
|
-
parameters:
|
|
3378
|
-
url:
|
|
3379
|
-
format:
|
|
3677
|
+
parameters: z29.object({
|
|
3678
|
+
url: z29.string().describe("The URL to fetch"),
|
|
3679
|
+
format: z29.enum(["text", "markdown"]).optional().describe("Output format (default: markdown)")
|
|
3380
3680
|
}),
|
|
3381
3681
|
execute: async ({ url, format }) => {
|
|
3382
3682
|
const outputFormat = format ?? "markdown";
|
|
@@ -3498,6 +3798,14 @@ var CapabilityRegistry = class {
|
|
|
3498
3798
|
this.tools.git_push = createGitPushTool(this.permissions);
|
|
3499
3799
|
logger.info("Git tools registered");
|
|
3500
3800
|
}
|
|
3801
|
+
if (isGitHubConfigured()) {
|
|
3802
|
+
this.tools.create_pr = createCreatePrTool();
|
|
3803
|
+
this.tools.review_pr = createReviewPrTool();
|
|
3804
|
+
this.tools.list_issues = createListIssuesTool();
|
|
3805
|
+
this.tools.create_issue = createCreateIssueTool();
|
|
3806
|
+
this.tools.github_api = createGithubApiTool(this.permissions);
|
|
3807
|
+
logger.info("GitHub tools registered");
|
|
3808
|
+
}
|
|
3501
3809
|
this.tools.fetch_url = createFetchUrlTool();
|
|
3502
3810
|
logger.info("Web fetch tool registered");
|
|
3503
3811
|
}
|
|
@@ -4319,11 +4627,17 @@ var pkgVersion = JSON.parse(readFileSync12(join11(__dirname, "..", "package.json
|
|
|
4319
4627
|
function hr() {
|
|
4320
4628
|
console.log(chalk6.dim("\u2500".repeat(50)));
|
|
4321
4629
|
}
|
|
4630
|
+
var MERCURY_ASCII = [
|
|
4631
|
+
" __ _____________ ________ ________ __",
|
|
4632
|
+
" / |/ / ____/ __ \\/ ____/ / / / __ \\/ < /",
|
|
4633
|
+
" / /|_/ / __/ / /_/ / / / / / / /_/ /\\ / ",
|
|
4634
|
+
" / / / / /___/ _, _/ /___/ /_/ / _, _/ / / ",
|
|
4635
|
+
"/_/ /_/_____/_/ |_|\\____/\\____/_/ |_| /_/ "
|
|
4636
|
+
].filter((l) => l.trim());
|
|
4322
4637
|
function banner() {
|
|
4323
4638
|
console.log("");
|
|
4324
|
-
const
|
|
4325
|
-
|
|
4326
|
-
if (line.trim()) console.log(chalk6.bold.cyan(` ${line}`));
|
|
4639
|
+
for (const line of MERCURY_ASCII) {
|
|
4640
|
+
console.log(chalk6.bold.cyan(` ${line}`));
|
|
4327
4641
|
}
|
|
4328
4642
|
console.log("");
|
|
4329
4643
|
console.log(chalk6.white(" an AI agent for personal tasks"));
|
|
@@ -4332,9 +4646,8 @@ function banner() {
|
|
|
4332
4646
|
}
|
|
4333
4647
|
function splashScreen() {
|
|
4334
4648
|
console.log("");
|
|
4335
|
-
const
|
|
4336
|
-
|
|
4337
|
-
if (line.trim()) console.log(chalk6.bold.cyan(` ${line}`));
|
|
4649
|
+
for (const line of MERCURY_ASCII) {
|
|
4650
|
+
console.log(chalk6.bold.cyan(` ${line}`));
|
|
4338
4651
|
}
|
|
4339
4652
|
console.log("");
|
|
4340
4653
|
console.log(chalk6.dim(" an AI agent for personal tasks"));
|
|
@@ -4356,6 +4669,17 @@ function maskKey(key) {
|
|
|
4356
4669
|
if (key.length <= 8) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
4357
4670
|
return key.slice(0, 4) + "\u2022\u2022\u2022\u2022" + key.slice(-4);
|
|
4358
4671
|
}
|
|
4672
|
+
function appendToEnv(key, value) {
|
|
4673
|
+
const envPath = join11(getMercuryHome(), ".env");
|
|
4674
|
+
let envContent = "";
|
|
4675
|
+
if (existsSync16(envPath)) {
|
|
4676
|
+
envContent = readFileSync12(envPath, "utf-8");
|
|
4677
|
+
}
|
|
4678
|
+
const lines = envContent.split("\n").filter((l) => !l.startsWith(`${key}=`) && l.trim() !== "");
|
|
4679
|
+
lines.push(`${key}=${value}`);
|
|
4680
|
+
writeFileSync13(envPath, lines.join("\n") + "\n", "utf-8");
|
|
4681
|
+
process.env[key] = value;
|
|
4682
|
+
}
|
|
4359
4683
|
async function configure(existingConfig) {
|
|
4360
4684
|
const isReconfig = !!existingConfig;
|
|
4361
4685
|
const config = existingConfig ?? loadConfig();
|
|
@@ -4454,6 +4778,23 @@ async function configure(existingConfig) {
|
|
|
4454
4778
|
}
|
|
4455
4779
|
hr();
|
|
4456
4780
|
console.log("");
|
|
4781
|
+
console.log(chalk6.bold.white(" GitHub (optional)"));
|
|
4782
|
+
console.log(chalk6.dim(" Connect Mercury to GitHub for PRs, issues, and co-authored commits."));
|
|
4783
|
+
console.log(chalk6.dim(" Leave empty to skip. You can add it later with mercury doctor."));
|
|
4784
|
+
console.log("");
|
|
4785
|
+
const ghUserCurrent = isReconfig && config.github.username ? ` [${config.github.username}]` : "";
|
|
4786
|
+
const ghUsername = await ask(chalk6.white(` GitHub username${ghUserCurrent}: `));
|
|
4787
|
+
if (ghUsername) config.github.username = ghUsername;
|
|
4788
|
+
const ghEmailCurrent = isReconfig && config.github.email ? ` [${config.github.email}]` : "";
|
|
4789
|
+
const ghEmail = await ask(chalk6.white(` GitHub email${ghEmailCurrent}: `));
|
|
4790
|
+
if (ghEmail) config.github.email = ghEmail;
|
|
4791
|
+
const ghTokenCurrent = process.env.GITHUB_TOKEN ? ` [${maskKey(process.env.GITHUB_TOKEN)}]` : "";
|
|
4792
|
+
const ghToken = await ask(chalk6.white(` GitHub PAT (repo scope)${ghTokenCurrent}: `));
|
|
4793
|
+
if (ghToken) {
|
|
4794
|
+
appendToEnv("GITHUB_TOKEN", ghToken);
|
|
4795
|
+
}
|
|
4796
|
+
hr();
|
|
4797
|
+
console.log("");
|
|
4457
4798
|
console.log(chalk6.bold.white(" Token Budget"));
|
|
4458
4799
|
console.log("");
|
|
4459
4800
|
const budgetPrompt = isReconfig ? chalk6.white(` Daily token budget [${config.tokens.dailyBudget.toLocaleString()}]: `) : chalk6.white(` Daily token budget [${config.tokens.dailyBudget.toLocaleString()}]: `);
|
|
@@ -4554,6 +4895,9 @@ async function runAgent(isDaemon = false) {
|
|
|
4554
4895
|
await msg.sendFile(filePath);
|
|
4555
4896
|
}
|
|
4556
4897
|
});
|
|
4898
|
+
if (process.env.GITHUB_TOKEN) {
|
|
4899
|
+
setGitHubToken(process.env.GITHUB_TOKEN);
|
|
4900
|
+
}
|
|
4557
4901
|
capabilities.registerAll();
|
|
4558
4902
|
const agent = new Agent(
|
|
4559
4903
|
config,
|