@sunilaleti/hx 0.3.0-beta.1 → 1.1.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/CHANGELOG.md +72 -1
- package/README.md +112 -81
- package/dist/hx.js +1114 -592
- package/dist/hx.js.map +4 -4
- package/package.json +2 -2
package/dist/hx.js
CHANGED
|
@@ -825,7 +825,7 @@ var init_json = __esm({
|
|
|
825
825
|
|
|
826
826
|
// src/paths.ts
|
|
827
827
|
import { createHash } from "node:crypto";
|
|
828
|
-
import { closeSync, mkdirSync, openSync } from "node:fs";
|
|
828
|
+
import { chmodSync, closeSync, mkdirSync, openSync, statSync as statSync3 } from "node:fs";
|
|
829
829
|
function userHome() {
|
|
830
830
|
const override = process.env[HX_HOME_ENV];
|
|
831
831
|
return override ? purePath(expandUser(override)) : join(purePath(home()), ".hx");
|
|
@@ -883,7 +883,13 @@ function projectInstructionsFiles(cwd, root) {
|
|
|
883
883
|
return chain2.slice(0, index + 1).reverse().map((directory) => join(directory, "AGENTS.md"));
|
|
884
884
|
}
|
|
885
885
|
function ensureUserDirs() {
|
|
886
|
-
|
|
886
|
+
const root = userHome();
|
|
887
|
+
mkdirSync(root, { recursive: true, mode: 448 });
|
|
888
|
+
try {
|
|
889
|
+
if (statSync3(root).mode & 63) chmodSync(root, 448);
|
|
890
|
+
} catch {
|
|
891
|
+
}
|
|
892
|
+
for (const path of [sessionsDir(), userSkillsDir(), userAgentsDir(), logsDir()]) {
|
|
887
893
|
mkdirSync(path, { recursive: true });
|
|
888
894
|
}
|
|
889
895
|
try {
|
|
@@ -932,7 +938,7 @@ var init_paths = __esm({
|
|
|
932
938
|
});
|
|
933
939
|
|
|
934
940
|
// src/auth/store.ts
|
|
935
|
-
import { chmodSync, closeSync as closeSync2, mkdirSync as mkdirSync2, openSync as openSync2, renameSync, rmSync, writeSync } from "node:fs";
|
|
941
|
+
import { chmodSync as chmodSync2, closeSync as closeSync2, mkdirSync as mkdirSync2, openSync as openSync2, renameSync, rmSync, writeSync } from "node:fs";
|
|
936
942
|
function expired(credential, leeway = 300) {
|
|
937
943
|
return Date.now() / 1e3 >= credential.expires - leeway;
|
|
938
944
|
}
|
|
@@ -1088,7 +1094,7 @@ var init_store = __esm({
|
|
|
1088
1094
|
throw error;
|
|
1089
1095
|
}
|
|
1090
1096
|
closeSync2(descriptor);
|
|
1091
|
-
|
|
1097
|
+
chmodSync2(tmp, 384);
|
|
1092
1098
|
renameSync(tmp, path);
|
|
1093
1099
|
}
|
|
1094
1100
|
};
|
|
@@ -1744,13 +1750,13 @@ var init_base = __esm({
|
|
|
1744
1750
|
});
|
|
1745
1751
|
|
|
1746
1752
|
// src/which.ts
|
|
1747
|
-
import { accessSync, constants, statSync as
|
|
1753
|
+
import { accessSync, constants, statSync as statSync4 } from "node:fs";
|
|
1748
1754
|
function which(name, path = process.env.PATH) {
|
|
1749
1755
|
const candidates2 = name.includes("/") ? [name] : (path ?? "").split(":").map((dir) => join(dir || ".", name));
|
|
1750
1756
|
for (const candidate of candidates2) {
|
|
1751
1757
|
try {
|
|
1752
1758
|
accessSync(candidate, constants.X_OK);
|
|
1753
|
-
if (
|
|
1759
|
+
if (statSync4(candidate).isFile()) return candidate;
|
|
1754
1760
|
} catch {
|
|
1755
1761
|
}
|
|
1756
1762
|
}
|
|
@@ -2385,12 +2391,13 @@ function quote(text3) {
|
|
|
2385
2391
|
if (SAFE.test(text3)) return text3;
|
|
2386
2392
|
return `'${text3.replaceAll("'", `'"'"'`)}'`;
|
|
2387
2393
|
}
|
|
2388
|
-
var WHITESPACE, SAFE;
|
|
2394
|
+
var WHITESPACE, SAFE, join2;
|
|
2389
2395
|
var init_shlex = __esm({
|
|
2390
2396
|
"src/shlex.ts"() {
|
|
2391
2397
|
"use strict";
|
|
2392
2398
|
WHITESPACE = " \r\n";
|
|
2393
2399
|
SAFE = /^[%+,\-./0-9:=@A-Z_a-z]+$/;
|
|
2400
|
+
join2 = (parts2) => parts2.map(quote).join(" ");
|
|
2394
2401
|
}
|
|
2395
2402
|
});
|
|
2396
2403
|
|
|
@@ -2584,7 +2591,7 @@ var init_messages = __esm({
|
|
|
2584
2591
|
});
|
|
2585
2592
|
|
|
2586
2593
|
// src/core/images.ts
|
|
2587
|
-
import { readFileSync as readFileSync2, statSync as
|
|
2594
|
+
import { readFileSync as readFileSync2, statSync as statSync5 } from "node:fs";
|
|
2588
2595
|
async function loadSharp() {
|
|
2589
2596
|
sharpModule ??= (await import("sharp")).default;
|
|
2590
2597
|
return sharpModule;
|
|
@@ -2627,7 +2634,7 @@ async function loadImage(data, label = "") {
|
|
|
2627
2634
|
async function loadImageFile(path, label) {
|
|
2628
2635
|
let bytes;
|
|
2629
2636
|
try {
|
|
2630
|
-
bytes =
|
|
2637
|
+
bytes = statSync5(path).size;
|
|
2631
2638
|
} catch (error) {
|
|
2632
2639
|
throw new ImageError(`cannot read ${path}: ${strerror(error)}`);
|
|
2633
2640
|
}
|
|
@@ -5079,7 +5086,7 @@ import {
|
|
|
5079
5086
|
openSync as openSync3,
|
|
5080
5087
|
readdirSync as readdirSync2,
|
|
5081
5088
|
realpathSync as realpathSync2,
|
|
5082
|
-
statSync as
|
|
5089
|
+
statSync as statSync6,
|
|
5083
5090
|
writeSync as writeSync2
|
|
5084
5091
|
} from "node:fs";
|
|
5085
5092
|
function servedName(name) {
|
|
@@ -5213,7 +5220,7 @@ function projectDir2(cwd) {
|
|
|
5213
5220
|
const prefix = sanitized.slice(0, 200);
|
|
5214
5221
|
try {
|
|
5215
5222
|
for (const entry of readdirSync2(projects))
|
|
5216
|
-
if (entry.startsWith(`${prefix}-`) &&
|
|
5223
|
+
if (entry.startsWith(`${prefix}-`) && statSync6(join(projects, entry)).isDirectory())
|
|
5217
5224
|
return join(projects, entry);
|
|
5218
5225
|
} catch {
|
|
5219
5226
|
}
|
|
@@ -6344,7 +6351,8 @@ function buildSettings(data, cwd) {
|
|
|
6344
6351
|
tui: { enterWhileBusy: busyRaw, fullscreen: truthy(tui.fullscreen ?? false) },
|
|
6345
6352
|
tools: { hashline: truthy(pick(tools, "hashline", true)) },
|
|
6346
6353
|
theme: pick(data, "theme", "dark"),
|
|
6347
|
-
quietStartup: truthy(pick(data, "quietStartup", pick(data, "quiet_startup", false)))
|
|
6354
|
+
quietStartup: truthy(pick(data, "quietStartup", pick(data, "quiet_startup", false))),
|
|
6355
|
+
autoUpdates: truthy(pick(data, "autoUpdates", pick(data, "auto_updates", true)))
|
|
6348
6356
|
};
|
|
6349
6357
|
}
|
|
6350
6358
|
function iterate(value) {
|
|
@@ -16962,7 +16970,7 @@ var require_abnf = __commonJS({
|
|
|
16962
16970
|
scope: "attribute",
|
|
16963
16971
|
match: regex.concat(IDENT, /(?=\s*=)/)
|
|
16964
16972
|
};
|
|
16965
|
-
const
|
|
16973
|
+
const ASSIGNMENT2 = {
|
|
16966
16974
|
scope: "operator",
|
|
16967
16975
|
match: /=\/?/
|
|
16968
16976
|
};
|
|
@@ -16971,7 +16979,7 @@ var require_abnf = __commonJS({
|
|
|
16971
16979
|
illegal: /[!@#$^&',?+~`|:]/,
|
|
16972
16980
|
keywords: KEYWORDS,
|
|
16973
16981
|
contains: [
|
|
16974
|
-
|
|
16982
|
+
ASSIGNMENT2,
|
|
16975
16983
|
RULE_DECLARATION,
|
|
16976
16984
|
COMMENT,
|
|
16977
16985
|
TERMINAL_BINARY,
|
|
@@ -30310,7 +30318,7 @@ var require_gams = __commonJS({
|
|
|
30310
30318
|
illegal: "\\n",
|
|
30311
30319
|
contains: [hljs.BACKSLASH_ESCAPE]
|
|
30312
30320
|
};
|
|
30313
|
-
const
|
|
30321
|
+
const ASSIGNMENT2 = {
|
|
30314
30322
|
begin: "/",
|
|
30315
30323
|
end: "/",
|
|
30316
30324
|
keywords: KEYWORDS,
|
|
@@ -30332,7 +30340,7 @@ var require_gams = __commonJS({
|
|
|
30332
30340
|
endsWithParent: true,
|
|
30333
30341
|
contains: [
|
|
30334
30342
|
QSTR,
|
|
30335
|
-
|
|
30343
|
+
ASSIGNMENT2,
|
|
30336
30344
|
{
|
|
30337
30345
|
className: "comment",
|
|
30338
30346
|
// one comment word, then possibly more
|
|
@@ -30379,7 +30387,7 @@ var require_gams = __commonJS({
|
|
|
30379
30387
|
hljs.C_BLOCK_COMMENT_MODE,
|
|
30380
30388
|
hljs.QUOTE_STRING_MODE,
|
|
30381
30389
|
hljs.APOS_STRING_MODE,
|
|
30382
|
-
|
|
30390
|
+
ASSIGNMENT2,
|
|
30383
30391
|
DESCTEXT
|
|
30384
30392
|
]
|
|
30385
30393
|
},
|
|
@@ -40434,7 +40442,7 @@ var require_makefile = __commonJS({
|
|
|
40434
40442
|
// Added QUOTE_STRING as they can be a part of functions
|
|
40435
40443
|
]
|
|
40436
40444
|
};
|
|
40437
|
-
const
|
|
40445
|
+
const ASSIGNMENT2 = { begin: "^" + hljs.UNDERSCORE_IDENT_RE + "\\s*(?=[:+?]?=)" };
|
|
40438
40446
|
const META = {
|
|
40439
40447
|
className: "meta",
|
|
40440
40448
|
begin: /^\.PHONY:/,
|
|
@@ -40466,7 +40474,7 @@ var require_makefile = __commonJS({
|
|
|
40466
40474
|
VARIABLE,
|
|
40467
40475
|
QUOTE_STRING,
|
|
40468
40476
|
FUNC,
|
|
40469
|
-
|
|
40477
|
+
ASSIGNMENT2,
|
|
40470
40478
|
META,
|
|
40471
40479
|
TARGET
|
|
40472
40480
|
]
|
|
@@ -67725,7 +67733,7 @@ init_net();
|
|
|
67725
67733
|
init_py();
|
|
67726
67734
|
|
|
67727
67735
|
// src/version.ts
|
|
67728
|
-
var VERSION = "
|
|
67736
|
+
var VERSION = "1.1.0";
|
|
67729
67737
|
var PACKAGE = "@sunilaleti/hx";
|
|
67730
67738
|
|
|
67731
67739
|
// src/providers/codex.ts
|
|
@@ -68165,12 +68173,12 @@ init_images();
|
|
|
68165
68173
|
|
|
68166
68174
|
// src/core/session.ts
|
|
68167
68175
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
68168
|
-
import { appendFileSync, mkdirSync as mkdirSync7, statSync as
|
|
68176
|
+
import { appendFileSync, mkdirSync as mkdirSync7, statSync as statSync9, writeFileSync as writeFileSync4 } from "node:fs";
|
|
68169
68177
|
|
|
68170
68178
|
// src/glob.ts
|
|
68171
68179
|
init_pathlib();
|
|
68172
68180
|
init_py();
|
|
68173
|
-
import { opendirSync, statSync as
|
|
68181
|
+
import { opendirSync, statSync as statSync7 } from "node:fs";
|
|
68174
68182
|
function translate(pattern) {
|
|
68175
68183
|
let out = "";
|
|
68176
68184
|
const chars = [...pattern];
|
|
@@ -68222,7 +68230,7 @@ function isDirEntry(directory, entry) {
|
|
|
68222
68230
|
if (entry.isDirectory()) return true;
|
|
68223
68231
|
if (!entry.isSymbolicLink()) return false;
|
|
68224
68232
|
try {
|
|
68225
|
-
return
|
|
68233
|
+
return statSync7(join(directory, entry.name)).isDirectory();
|
|
68226
68234
|
} catch {
|
|
68227
68235
|
return false;
|
|
68228
68236
|
}
|
|
@@ -68259,7 +68267,7 @@ function select(path, rest, out) {
|
|
|
68259
68267
|
const child = join(path, part);
|
|
68260
68268
|
if (tail3.length === 0) {
|
|
68261
68269
|
try {
|
|
68262
|
-
|
|
68270
|
+
statSync7(child);
|
|
68263
68271
|
out.push(child);
|
|
68264
68272
|
} catch {
|
|
68265
68273
|
}
|
|
@@ -68477,7 +68485,7 @@ function digestOf(path) {
|
|
|
68477
68485
|
init_messages();
|
|
68478
68486
|
|
|
68479
68487
|
// src/core/reads.ts
|
|
68480
|
-
import { statSync as
|
|
68488
|
+
import { statSync as statSync8 } from "node:fs";
|
|
68481
68489
|
var fileReadToDict = (read2) => ({
|
|
68482
68490
|
index: read2.index,
|
|
68483
68491
|
path: read2.path,
|
|
@@ -68516,7 +68524,7 @@ var STAT_DIGESTS = /* @__PURE__ */ new Map();
|
|
|
68516
68524
|
function cachedDigest(key2) {
|
|
68517
68525
|
let stamp;
|
|
68518
68526
|
try {
|
|
68519
|
-
const info =
|
|
68527
|
+
const info = statSync8(key2, { bigint: true });
|
|
68520
68528
|
stamp = `${info.ino}:${info.size}:${info.mtimeNs}`;
|
|
68521
68529
|
} catch {
|
|
68522
68530
|
STAT_DIGESTS.delete(key2);
|
|
@@ -68908,7 +68916,7 @@ var BACKFILL_MAX_BYTES = 4e6;
|
|
|
68908
68916
|
function backfilled(meta) {
|
|
68909
68917
|
if (meta.promptCount || meta.messageCount <= 0) return meta;
|
|
68910
68918
|
try {
|
|
68911
|
-
if (
|
|
68919
|
+
if (statSync9(sessionTranscriptFile(meta.sessionId)).size > BACKFILL_MAX_BYTES) return meta;
|
|
68912
68920
|
} catch {
|
|
68913
68921
|
return meta;
|
|
68914
68922
|
}
|
|
@@ -69034,6 +69042,75 @@ function changelog(version = null) {
|
|
|
69034
69042
|
);
|
|
69035
69043
|
}
|
|
69036
69044
|
|
|
69045
|
+
// src/mcp/approval.ts
|
|
69046
|
+
init_json();
|
|
69047
|
+
init_pathlib();
|
|
69048
|
+
init_paths();
|
|
69049
|
+
init_shlex();
|
|
69050
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
69051
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "node:fs";
|
|
69052
|
+
var approvalsFile = (cwd) => join(projectStateDir(cwd), "mcp-approved.json");
|
|
69053
|
+
function fingerprint2(config2) {
|
|
69054
|
+
const fields = {
|
|
69055
|
+
transport: config2.transport,
|
|
69056
|
+
command: config2.command,
|
|
69057
|
+
args: config2.args,
|
|
69058
|
+
env: config2.env,
|
|
69059
|
+
url: config2.url,
|
|
69060
|
+
headers: config2.headers
|
|
69061
|
+
};
|
|
69062
|
+
return createHash8("sha256").update(dumps(fields, { sortKeys: true })).digest("hex");
|
|
69063
|
+
}
|
|
69064
|
+
function readApprovals(cwd) {
|
|
69065
|
+
const path = approvalsFile(cwd);
|
|
69066
|
+
if (!isFile(path)) return {};
|
|
69067
|
+
try {
|
|
69068
|
+
const data = JSON.parse(readText(path));
|
|
69069
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) return {};
|
|
69070
|
+
return Object.fromEntries(
|
|
69071
|
+
Object.entries(data).filter(
|
|
69072
|
+
(entry) => typeof entry[1] === "string"
|
|
69073
|
+
)
|
|
69074
|
+
);
|
|
69075
|
+
} catch {
|
|
69076
|
+
return {};
|
|
69077
|
+
}
|
|
69078
|
+
}
|
|
69079
|
+
function writeApprovals(cwd, approvals) {
|
|
69080
|
+
const path = approvalsFile(cwd);
|
|
69081
|
+
mkdirSync8(parent(path), { recursive: true });
|
|
69082
|
+
writeFileSync5(path, `${dumps(approvals, { indent: 2, sortKeys: true })}
|
|
69083
|
+
`);
|
|
69084
|
+
}
|
|
69085
|
+
function approve(config2, cwd) {
|
|
69086
|
+
writeApprovals(cwd, { ...readApprovals(cwd), [config2.name]: fingerprint2(config2) });
|
|
69087
|
+
}
|
|
69088
|
+
function revoke(name, cwd) {
|
|
69089
|
+
const approvals = readApprovals(cwd);
|
|
69090
|
+
if (!(name in approvals)) return;
|
|
69091
|
+
delete approvals[name];
|
|
69092
|
+
writeApprovals(cwd, approvals);
|
|
69093
|
+
}
|
|
69094
|
+
function partition(configs, cwd) {
|
|
69095
|
+
const approvals = readApprovals(cwd);
|
|
69096
|
+
const startable = [];
|
|
69097
|
+
const waiting = [];
|
|
69098
|
+
for (const config2 of configs) {
|
|
69099
|
+
const approved = approvals[config2.name];
|
|
69100
|
+
if (config2.source === "user" || approved === fingerprint2(config2)) startable.push(config2);
|
|
69101
|
+
else waiting.push({ config: config2, changed: approved !== void 0 });
|
|
69102
|
+
}
|
|
69103
|
+
return [startable, waiting];
|
|
69104
|
+
}
|
|
69105
|
+
function describeServer(config2) {
|
|
69106
|
+
if (config2.url) return config2.url;
|
|
69107
|
+
return join2([config2.command ?? "", ...config2.args]);
|
|
69108
|
+
}
|
|
69109
|
+
function waitingNotice({ config: config2, changed }) {
|
|
69110
|
+
const why = changed ? "has changed since you approved it" : "came with the project and has not been approved";
|
|
69111
|
+
return `MCP server ${config2.name} in .hx/mcp.json ${why}, so HX has not started it. It ${config2.url ? "connects to" : "runs"}: ${describeServer(config2)}. Run \`hx mcp approve ${config2.name}\` to start it.`;
|
|
69112
|
+
}
|
|
69113
|
+
|
|
69037
69114
|
// src/mcp/client.ts
|
|
69038
69115
|
init_json();
|
|
69039
69116
|
init_net();
|
|
@@ -69614,7 +69691,7 @@ init_json();
|
|
|
69614
69691
|
init_pathlib();
|
|
69615
69692
|
init_paths();
|
|
69616
69693
|
init_py();
|
|
69617
|
-
import { mkdirSync as
|
|
69694
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "node:fs";
|
|
69618
69695
|
|
|
69619
69696
|
// src/tools/base.ts
|
|
69620
69697
|
function toolResult(content, fields = {}) {
|
|
@@ -69671,7 +69748,7 @@ function isAbort(error, signal) {
|
|
|
69671
69748
|
init_pathlib();
|
|
69672
69749
|
init_paths();
|
|
69673
69750
|
init_py();
|
|
69674
|
-
import { mkdirSync as
|
|
69751
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "node:fs";
|
|
69675
69752
|
var MIN_BODY_CHARS = 200;
|
|
69676
69753
|
function elision(omittedLines, omittedChars, path) {
|
|
69677
69754
|
return `
|
|
@@ -69726,10 +69803,10 @@ function split2(lines2, charCap, lineCap, headRatio) {
|
|
|
69726
69803
|
}
|
|
69727
69804
|
function spill(text3, sessionId, toolUseId) {
|
|
69728
69805
|
const directory = sessionOutputsDir(sessionId);
|
|
69729
|
-
|
|
69806
|
+
mkdirSync9(directory, { recursive: true });
|
|
69730
69807
|
const safe = toolUseId.replace(/[^A-Za-z0-9_.-]/g, "_") || "output";
|
|
69731
69808
|
const path = join(directory, `${safe}.txt`);
|
|
69732
|
-
|
|
69809
|
+
writeFileSync6(path, text3, "utf8");
|
|
69733
69810
|
return path;
|
|
69734
69811
|
}
|
|
69735
69812
|
function summarizeForUi(text3, maxLen = 120) {
|
|
@@ -70295,6 +70372,7 @@ function serverConfig(fields) {
|
|
|
70295
70372
|
enabled: true,
|
|
70296
70373
|
timeout: 30,
|
|
70297
70374
|
oauth: noOptions(),
|
|
70375
|
+
source: "user",
|
|
70298
70376
|
...fields
|
|
70299
70377
|
};
|
|
70300
70378
|
}
|
|
@@ -70369,11 +70447,13 @@ async function normalise(raw, tool) {
|
|
|
70369
70447
|
return [images2, problems];
|
|
70370
70448
|
}
|
|
70371
70449
|
var MCPManager = class {
|
|
70372
|
-
constructor(configs, store = new AuthStore()) {
|
|
70450
|
+
constructor(configs, store = new AuthStore(), waiting = []) {
|
|
70373
70451
|
this.store = store;
|
|
70452
|
+
this.waiting = waiting;
|
|
70374
70453
|
this.configs = configs.filter((config2) => config2.enabled);
|
|
70375
70454
|
}
|
|
70376
70455
|
store;
|
|
70456
|
+
waiting;
|
|
70377
70457
|
configs;
|
|
70378
70458
|
clients = /* @__PURE__ */ new Map();
|
|
70379
70459
|
statuses = /* @__PURE__ */ new Map();
|
|
@@ -70585,13 +70665,16 @@ function buildTransport(config2, store) {
|
|
|
70585
70665
|
var userMcpFile = () => join(userHome(), "mcp.json");
|
|
70586
70666
|
function loadConfigs(cwd) {
|
|
70587
70667
|
const merged = /* @__PURE__ */ new Map();
|
|
70588
|
-
for (const path of [
|
|
70589
|
-
|
|
70668
|
+
for (const [path, source] of [
|
|
70669
|
+
[userMcpFile(), "user"],
|
|
70670
|
+
[projectMcpFile(cwd), "project"]
|
|
70671
|
+
])
|
|
70672
|
+
for (const config2 of readFile(path, source)) merged.set(config2.name, config2);
|
|
70590
70673
|
return [...merged.keys()].sort(compareCodePoints).map((name) => merged.get(name));
|
|
70591
70674
|
}
|
|
70592
70675
|
var isObject7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
70593
70676
|
var strings = (raw) => Object.fromEntries(Object.entries(isObject7(raw) ? raw : {}).map(([k, v]) => [k, String(v)]));
|
|
70594
|
-
function readFile(path) {
|
|
70677
|
+
function readFile(path, source) {
|
|
70595
70678
|
if (!isFile(path)) return [];
|
|
70596
70679
|
let data;
|
|
70597
70680
|
try {
|
|
@@ -70617,7 +70700,8 @@ function readFile(path) {
|
|
|
70617
70700
|
headers: strings(raw.headers),
|
|
70618
70701
|
enabled: "enabled" in raw ? truthy(raw.enabled) : true,
|
|
70619
70702
|
timeout: "timeout" in raw ? toFloat(raw.timeout) : 30,
|
|
70620
|
-
oauth: optionsFromJson(raw.oauth)
|
|
70703
|
+
oauth: optionsFromJson(raw.oauth),
|
|
70704
|
+
source
|
|
70621
70705
|
})
|
|
70622
70706
|
);
|
|
70623
70707
|
}
|
|
@@ -70632,7 +70716,7 @@ function readJson(path) {
|
|
|
70632
70716
|
return {};
|
|
70633
70717
|
}
|
|
70634
70718
|
}
|
|
70635
|
-
var writeJson = (path, data) =>
|
|
70719
|
+
var writeJson = (path, data) => writeFileSync7(path, `${dumps(data, { indent: 2, sortKeys: true })}
|
|
70636
70720
|
`);
|
|
70637
70721
|
function saveConfig(config2, cwd, userLevel = false) {
|
|
70638
70722
|
const path = userLevel ? userMcpFile() : projectMcpFile(cwd);
|
|
@@ -70648,7 +70732,7 @@ function saveConfig(config2, cwd, userLevel = false) {
|
|
|
70648
70732
|
const block2 = optionsToJson(config2.oauth);
|
|
70649
70733
|
if (Object.keys(block2).length) entry.oauth = block2;
|
|
70650
70734
|
servers[config2.name] = entry;
|
|
70651
|
-
|
|
70735
|
+
mkdirSync10(parent(path), { recursive: true });
|
|
70652
70736
|
writeJson(path, data);
|
|
70653
70737
|
return path;
|
|
70654
70738
|
}
|
|
@@ -70673,6 +70757,69 @@ function removeConfig(name, cwd, userLevel = false) {
|
|
|
70673
70757
|
init_net();
|
|
70674
70758
|
init_pathlib();
|
|
70675
70759
|
init_paths();
|
|
70760
|
+
|
|
70761
|
+
// src/providers/deferred.ts
|
|
70762
|
+
init_resolve();
|
|
70763
|
+
init_base();
|
|
70764
|
+
init_registry();
|
|
70765
|
+
var NOT_SIGNED_IN = "Not signed in yet. Type /login to sign in with ChatGPT Plus/Pro, Claude Pro/Max, an OpenRouter API key or Devin.";
|
|
70766
|
+
var NEEDS = {
|
|
70767
|
+
openrouter: "an OpenRouter API key",
|
|
70768
|
+
"openai-codex": "a ChatGPT Plus/Pro sign-in",
|
|
70769
|
+
"claude-code": "a Claude Pro/Max sign-in",
|
|
70770
|
+
devin: "a Devin sign-in"
|
|
70771
|
+
};
|
|
70772
|
+
function signInHint(modelId, resolver) {
|
|
70773
|
+
if (!available(resolver).length) return NOT_SIGNED_IN;
|
|
70774
|
+
const route = providerFor(modelId);
|
|
70775
|
+
const needs = NEEDS[route.id] ?? `a ${route.label} sign-in`;
|
|
70776
|
+
return `${modelId} needs ${needs}, which is not set up yet. Type /login to sign in, or /model to pick a model you can use.`;
|
|
70777
|
+
}
|
|
70778
|
+
var DeferredProvider = class {
|
|
70779
|
+
constructor(modelId, resolver, sessionId, models) {
|
|
70780
|
+
this.modelId = modelId;
|
|
70781
|
+
this.resolver = resolver;
|
|
70782
|
+
this.sessionId = sessionId;
|
|
70783
|
+
this.models = models;
|
|
70784
|
+
}
|
|
70785
|
+
modelId;
|
|
70786
|
+
resolver;
|
|
70787
|
+
sessionId;
|
|
70788
|
+
models;
|
|
70789
|
+
inner = null;
|
|
70790
|
+
get name() {
|
|
70791
|
+
return this.inner?.name ?? providerFor(this.modelId).id;
|
|
70792
|
+
}
|
|
70793
|
+
/** The prompt-cache key, kept for the provider this becomes. */
|
|
70794
|
+
setSessionId(sessionId) {
|
|
70795
|
+
this.sessionId = sessionId;
|
|
70796
|
+
this.inner?.setSessionId?.(sessionId);
|
|
70797
|
+
}
|
|
70798
|
+
/** A key saved now is read from the store when the provider is built. */
|
|
70799
|
+
setApiKey(key2) {
|
|
70800
|
+
this.inner?.setApiKey?.(key2);
|
|
70801
|
+
}
|
|
70802
|
+
async *astream(request2, signal) {
|
|
70803
|
+
if (this.inner === null) {
|
|
70804
|
+
try {
|
|
70805
|
+
this.inner = buildProvider(request2.model, this.resolver, {
|
|
70806
|
+
sessionId: this.sessionId,
|
|
70807
|
+
models: this.models
|
|
70808
|
+
});
|
|
70809
|
+
} catch (error) {
|
|
70810
|
+
if (error instanceof MissingCredential || error instanceof ExpiredCredential)
|
|
70811
|
+
throw new ProviderError(signInHint(request2.model, this.resolver));
|
|
70812
|
+
throw error;
|
|
70813
|
+
}
|
|
70814
|
+
}
|
|
70815
|
+
yield* this.inner.astream(request2, signal);
|
|
70816
|
+
}
|
|
70817
|
+
async aclose() {
|
|
70818
|
+
await this.inner?.aclose();
|
|
70819
|
+
}
|
|
70820
|
+
};
|
|
70821
|
+
|
|
70822
|
+
// src/cli.ts
|
|
70676
70823
|
init_models();
|
|
70677
70824
|
init_openrouter();
|
|
70678
70825
|
init_registry();
|
|
@@ -71228,7 +71375,7 @@ init_py();
|
|
|
71228
71375
|
|
|
71229
71376
|
// src/tools/anchors.ts
|
|
71230
71377
|
init_py();
|
|
71231
|
-
import { createHash as
|
|
71378
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
71232
71379
|
var WIDTHS = [4, 6, 8];
|
|
71233
71380
|
var SEPARATOR = "\0";
|
|
71234
71381
|
function split3(content) {
|
|
@@ -71243,7 +71390,7 @@ function window2(lines2, index) {
|
|
|
71243
71390
|
return [previous, lines2[index], following].join(SEPARATOR);
|
|
71244
71391
|
}
|
|
71245
71392
|
function digests(lines2) {
|
|
71246
|
-
return lines2.map((_, i) =>
|
|
71393
|
+
return lines2.map((_, i) => createHash9("sha1").update(window2(lines2, i), "utf8").digest("hex"));
|
|
71247
71394
|
}
|
|
71248
71395
|
function anchorWidth(full) {
|
|
71249
71396
|
const distinct = new Set(full).size;
|
|
@@ -71289,7 +71436,7 @@ var AnchorError = class extends Error {
|
|
|
71289
71436
|
init_images();
|
|
71290
71437
|
init_pathlib();
|
|
71291
71438
|
init_py();
|
|
71292
|
-
import { closeSync as closeSync6, existsSync as existsSync2, openSync as openSync6, readFileSync as readFileSync4, readSync as readSync2, statSync as
|
|
71439
|
+
import { closeSync as closeSync6, existsSync as existsSync2, openSync as openSync6, readFileSync as readFileSync4, readSync as readSync2, statSync as statSync10 } from "node:fs";
|
|
71293
71440
|
var DESCRIPTION = `Read a file from the local filesystem.
|
|
71294
71441
|
|
|
71295
71442
|
Returns numbered lines, each labelled with a short content anchor:
|
|
@@ -71326,7 +71473,7 @@ var ReadTool = class extends Tool {
|
|
|
71326
71473
|
if (isImagePath(path) || isBinary(path) && looksLikeImage(path)) return readImage(path);
|
|
71327
71474
|
if (isBinary(path)) {
|
|
71328
71475
|
throw new ToolError(
|
|
71329
|
-
`${path}: binary file (${count(
|
|
71476
|
+
`${path}: binary file (${count(statSync10(path).size, "byte")}), not read as text`
|
|
71330
71477
|
);
|
|
71331
71478
|
}
|
|
71332
71479
|
const offset = Math.max(toInt(params.offset || 1), 1);
|
|
@@ -71417,16 +71564,16 @@ function clip2(line) {
|
|
|
71417
71564
|
init_pathlib();
|
|
71418
71565
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
71419
71566
|
import {
|
|
71420
|
-
chmodSync as
|
|
71567
|
+
chmodSync as chmodSync3,
|
|
71421
71568
|
closeSync as closeSync7,
|
|
71422
71569
|
existsSync as existsSync3,
|
|
71423
71570
|
lstatSync as lstatSync2,
|
|
71424
|
-
mkdirSync as
|
|
71571
|
+
mkdirSync as mkdirSync11,
|
|
71425
71572
|
openSync as openSync7,
|
|
71426
71573
|
renameSync as renameSync5,
|
|
71427
71574
|
rmSync as rmSync3,
|
|
71428
|
-
statSync as
|
|
71429
|
-
writeFileSync as
|
|
71575
|
+
statSync as statSync11,
|
|
71576
|
+
writeFileSync as writeFileSync8
|
|
71430
71577
|
} from "node:fs";
|
|
71431
71578
|
var DESCRIPTION2 = `Write a file, overwriting it if it exists.
|
|
71432
71579
|
|
|
@@ -71472,7 +71619,7 @@ var WriteTool = class extends Tool {
|
|
|
71472
71619
|
}
|
|
71473
71620
|
}
|
|
71474
71621
|
const existed = existsSync3(path);
|
|
71475
|
-
|
|
71622
|
+
mkdirSync11(parent(path), { recursive: true });
|
|
71476
71623
|
this.checkpoints?.capture(path);
|
|
71477
71624
|
writeAtomic(path, content);
|
|
71478
71625
|
ctx.reads.markRead(path);
|
|
@@ -71506,7 +71653,7 @@ function writeAtomic(path, content) {
|
|
|
71506
71653
|
}
|
|
71507
71654
|
let mode2;
|
|
71508
71655
|
try {
|
|
71509
|
-
mode2 =
|
|
71656
|
+
mode2 = statSync11(target).mode & 4095;
|
|
71510
71657
|
} catch (error) {
|
|
71511
71658
|
if (error.code !== "ENOENT") throw error;
|
|
71512
71659
|
mode2 = null;
|
|
@@ -71515,11 +71662,11 @@ function writeAtomic(path, content) {
|
|
|
71515
71662
|
const fd = openSync7(tmp, "wx", mode2 ?? 438);
|
|
71516
71663
|
try {
|
|
71517
71664
|
try {
|
|
71518
|
-
|
|
71665
|
+
writeFileSync8(fd, content, "utf8");
|
|
71519
71666
|
} finally {
|
|
71520
71667
|
closeSync7(fd);
|
|
71521
71668
|
}
|
|
71522
|
-
if (mode2 !== null)
|
|
71669
|
+
if (mode2 !== null) chmodSync3(tmp, mode2);
|
|
71523
71670
|
renameSync5(tmp, target);
|
|
71524
71671
|
} catch (error) {
|
|
71525
71672
|
rmSync3(tmp, { force: true });
|
|
@@ -72415,7 +72562,7 @@ ${gate.contextText}`;
|
|
|
72415
72562
|
};
|
|
72416
72563
|
for (const call of calls) {
|
|
72417
72564
|
if (this.runsSerially(call)) {
|
|
72418
|
-
|
|
72565
|
+
if (concurrent.length) await gather(concurrent);
|
|
72419
72566
|
concurrent = [];
|
|
72420
72567
|
await run(call);
|
|
72421
72568
|
} else concurrent.push(call);
|
|
@@ -72950,7 +73097,7 @@ init_lateinject();
|
|
|
72950
73097
|
init_pathlib();
|
|
72951
73098
|
init_py();
|
|
72952
73099
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
72953
|
-
import { statSync as
|
|
73100
|
+
import { statSync as statSync12 } from "node:fs";
|
|
72954
73101
|
var STATUS_COMMAND = ["git", "status", "--porcelain=v2", "--branch", "-z"];
|
|
72955
73102
|
var ROOT_COMMAND = ["git", "rev-parse", "--show-toplevel"];
|
|
72956
73103
|
var TIMEOUT_SECONDS = 2;
|
|
@@ -73089,7 +73236,7 @@ var BranchWatcher = class {
|
|
|
73089
73236
|
if (head4 === null) return null;
|
|
73090
73237
|
let stamp;
|
|
73091
73238
|
try {
|
|
73092
|
-
const info =
|
|
73239
|
+
const info = statSync12(head4, { bigint: true });
|
|
73093
73240
|
stamp = `${info.mtimeNs}:${info.size}`;
|
|
73094
73241
|
} catch {
|
|
73095
73242
|
return this.current;
|
|
@@ -73193,7 +73340,7 @@ function statusCode(xy) {
|
|
|
73193
73340
|
}
|
|
73194
73341
|
function signature(path) {
|
|
73195
73342
|
try {
|
|
73196
|
-
const info =
|
|
73343
|
+
const info = statSync12(path, { bigint: true });
|
|
73197
73344
|
return `${info.mtimeNs}:${info.size}`;
|
|
73198
73345
|
} catch {
|
|
73199
73346
|
return "none";
|
|
@@ -73383,6 +73530,29 @@ init_py();
|
|
|
73383
73530
|
init_shlex();
|
|
73384
73531
|
var OPERATORS = ["&&", "||", ";;", ";", "|&", "|", "\n", "&"];
|
|
73385
73532
|
var DYNAMIC_EXECUTABLES = /* @__PURE__ */ new Set(["eval", "exec", "source", ".", "env"]);
|
|
73533
|
+
var WRAPPERS = /* @__PURE__ */ new Set([
|
|
73534
|
+
"builtin",
|
|
73535
|
+
"caffeinate",
|
|
73536
|
+
"chrt",
|
|
73537
|
+
"command",
|
|
73538
|
+
"doas",
|
|
73539
|
+
"flock",
|
|
73540
|
+
"gtimeout",
|
|
73541
|
+
"ionice",
|
|
73542
|
+
"nice",
|
|
73543
|
+
"nohup",
|
|
73544
|
+
"setsid",
|
|
73545
|
+
"stdbuf",
|
|
73546
|
+
"strace",
|
|
73547
|
+
"sudo",
|
|
73548
|
+
"taskset",
|
|
73549
|
+
"time",
|
|
73550
|
+
"timeout",
|
|
73551
|
+
"unbuffer",
|
|
73552
|
+
"watch",
|
|
73553
|
+
"xargs"
|
|
73554
|
+
]);
|
|
73555
|
+
var ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*\+?=/;
|
|
73386
73556
|
var FD_DUP = /^\d*>&\d*-?$/;
|
|
73387
73557
|
var OUT_OP = /^(?:\d*>>?|&>>?)$/;
|
|
73388
73558
|
var IN_OP = /^\d*<<?-?$/;
|
|
@@ -73468,7 +73638,17 @@ var READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
73468
73638
|
var MUTATING_FLAGS = {
|
|
73469
73639
|
sed: /* @__PURE__ */ new Set(["-i", "--in-place"]),
|
|
73470
73640
|
awk: /* @__PURE__ */ new Set(["-i", "--include"]),
|
|
73471
|
-
find: /* @__PURE__ */ new Set([
|
|
73641
|
+
find: /* @__PURE__ */ new Set([
|
|
73642
|
+
"-delete",
|
|
73643
|
+
"-exec",
|
|
73644
|
+
"-execdir",
|
|
73645
|
+
"-ok",
|
|
73646
|
+
"-okdir",
|
|
73647
|
+
"-fprint",
|
|
73648
|
+
"-fprint0",
|
|
73649
|
+
"-fprintf",
|
|
73650
|
+
"-fls"
|
|
73651
|
+
]),
|
|
73472
73652
|
fd: /* @__PURE__ */ new Set(["-x", "--exec", "-X", "--exec-batch"]),
|
|
73473
73653
|
sort: /* @__PURE__ */ new Set(["-o", "--output", "--compress-program"]),
|
|
73474
73654
|
tree: /* @__PURE__ */ new Set(["-o"]),
|
|
@@ -73643,10 +73823,18 @@ function toSegment(text3, inSub) {
|
|
|
73643
73823
|
if (tokens.length === 0) return [null, [], true];
|
|
73644
73824
|
const [words, redirects] = splitRedirections(tokens);
|
|
73645
73825
|
if (words.length === 0) return [null, redirects, true];
|
|
73646
|
-
|
|
73826
|
+
let first2 = 0;
|
|
73827
|
+
while (first2 < words.length && ASSIGNMENT.test(words[first2])) first2++;
|
|
73828
|
+
const executable = words[first2] ?? "";
|
|
73647
73829
|
const ok = !executable.includes("$") && !executable.includes("`");
|
|
73648
73830
|
return [
|
|
73649
|
-
{
|
|
73831
|
+
{
|
|
73832
|
+
raw: stripped,
|
|
73833
|
+
executable,
|
|
73834
|
+
args: words.slice(first2 + 1),
|
|
73835
|
+
assignments: words.slice(0, first2),
|
|
73836
|
+
inSubstitution: inSub
|
|
73837
|
+
},
|
|
73650
73838
|
redirects,
|
|
73651
73839
|
ok
|
|
73652
73840
|
];
|
|
@@ -73726,13 +73914,20 @@ function scan(text3) {
|
|
|
73726
73914
|
index++;
|
|
73727
73915
|
continue;
|
|
73728
73916
|
}
|
|
73729
|
-
if (text3.startsWith("$(", index) || char === "`") {
|
|
73917
|
+
if (text3.startsWith("$(", index) || text3.startsWith("<(", index) || text3.startsWith(">(", index) || char === "`") {
|
|
73730
73918
|
const [body, consumed, closed] = readSubstitution(text3, index);
|
|
73731
73919
|
substitutions.push(...recurse(body, substitutions));
|
|
73732
73920
|
ok = ok && closed;
|
|
73733
73921
|
index += consumed;
|
|
73734
73922
|
continue;
|
|
73735
73923
|
}
|
|
73924
|
+
if (char === "(" && strip(buffer)) {
|
|
73925
|
+
const [body, consumed] = readSubstitution(text3, index);
|
|
73926
|
+
substitutions.push(...recurse(body, substitutions));
|
|
73927
|
+
ok = false;
|
|
73928
|
+
index += consumed;
|
|
73929
|
+
continue;
|
|
73930
|
+
}
|
|
73736
73931
|
if (char === "(" || char === ")") {
|
|
73737
73932
|
index++;
|
|
73738
73933
|
continue;
|
|
@@ -73770,27 +73965,51 @@ function readSubstitution(text3, index) {
|
|
|
73770
73965
|
if (end === -1) return [text3.slice(index + 1), text3.length - index, false];
|
|
73771
73966
|
return [text3.slice(index + 1, end), end - index + 1, true];
|
|
73772
73967
|
}
|
|
73968
|
+
const open = text3.indexOf("(", index);
|
|
73773
73969
|
let depth = 0;
|
|
73774
|
-
for (let cursor =
|
|
73970
|
+
for (let cursor = open; cursor < text3.length; cursor++) {
|
|
73775
73971
|
if (text3[cursor] === "(") depth++;
|
|
73776
73972
|
else if (text3[cursor] === ")") {
|
|
73777
73973
|
depth--;
|
|
73778
|
-
if (depth === 0) return [text3.slice(
|
|
73974
|
+
if (depth === 0) return [text3.slice(open + 1, cursor), cursor - index + 1, true];
|
|
73779
73975
|
}
|
|
73780
73976
|
}
|
|
73781
|
-
return [text3.slice(
|
|
73977
|
+
return [text3.slice(open + 1), text3.length - index, false];
|
|
73782
73978
|
}
|
|
73783
|
-
function matchSpecifier(segment, pattern) {
|
|
73979
|
+
function matchSpecifier(segment, pattern, exact = false) {
|
|
73784
73980
|
if (pattern === "*" || pattern === ":*") return true;
|
|
73785
|
-
|
|
73981
|
+
if (exact) return matchWords(written(segment), pattern);
|
|
73982
|
+
return commandForms(segment).some((words) => matchWords(words, pattern));
|
|
73983
|
+
}
|
|
73984
|
+
function matchWords(words, pattern) {
|
|
73985
|
+
const commandLine = words.join(" ");
|
|
73786
73986
|
if (pattern.endsWith(":*")) {
|
|
73787
73987
|
const prefix = strip(pattern.slice(0, -2));
|
|
73788
73988
|
return commandLine === prefix || commandLine.startsWith(`${prefix} `);
|
|
73789
73989
|
}
|
|
73790
73990
|
if (pattern.includes(" ")) return commandLine === pattern;
|
|
73791
|
-
return
|
|
73991
|
+
return words[0] === pattern;
|
|
73992
|
+
}
|
|
73993
|
+
var written = (segment) => [...segment.assignments, segment.executable, ...segment.args].filter(Boolean);
|
|
73994
|
+
function commandForms(segment) {
|
|
73995
|
+
const words = [segment.executable, ...segment.args].filter(Boolean);
|
|
73996
|
+
const forms = [];
|
|
73997
|
+
const add2 = (command2) => {
|
|
73998
|
+
forms.push(command2);
|
|
73999
|
+
const name = basename2(command2[0]);
|
|
74000
|
+
if (name !== command2[0]) forms.push([name, ...command2.slice(1)]);
|
|
74001
|
+
};
|
|
74002
|
+
if (words.length === 0) return forms;
|
|
74003
|
+
add2(words);
|
|
74004
|
+
if (WRAPPERS.has(basename2(words[0]))) {
|
|
74005
|
+
for (let index = 1; index < words.length; index++)
|
|
74006
|
+
if (!words[index].startsWith("-")) add2(words.slice(index));
|
|
74007
|
+
}
|
|
74008
|
+
return forms;
|
|
73792
74009
|
}
|
|
74010
|
+
var basename2 = (path) => path.slice(path.lastIndexOf("/") + 1);
|
|
73793
74011
|
function isReadOnly(segment) {
|
|
74012
|
+
if (segment.assignments.length) return false;
|
|
73794
74013
|
if (segment.executable === "git") return gitIsReadOnly(segment.args);
|
|
73795
74014
|
if (!READ_ONLY_COMMANDS.has(segment.executable)) return false;
|
|
73796
74015
|
if (segment.executable === "env") return !envRunsACommand(segment.args);
|
|
@@ -73803,13 +74022,13 @@ function isReadOnly(segment) {
|
|
|
73803
74022
|
);
|
|
73804
74023
|
}
|
|
73805
74024
|
var PROGRAM_TEXT_COMMANDS = /* @__PURE__ */ new Set(["awk", "sed"]);
|
|
73806
|
-
var AWK_WRITES = /system\s*\(|\|\s*["']|["']\s
|
|
74025
|
+
var AWK_WRITES = /system\s*\(|\|\s*["']|["']\s*\||\|\s*getline\b|\|&|print[f]?[^;}\n]*[>|]|@(?:include|load)\b/;
|
|
73807
74026
|
var PROGRAM_TEXT_OPTIONS = {
|
|
73808
74027
|
awk: ["-e", "--source"],
|
|
73809
74028
|
sed: ["-e", "--expression"]
|
|
73810
74029
|
};
|
|
73811
74030
|
var PROGRAM_FILE_OPTIONS = {
|
|
73812
|
-
awk: ["-f", "--file", "-E", "--exec"],
|
|
74031
|
+
awk: ["-f", "--file", "-E", "--exec", "-l", "--load"],
|
|
73813
74032
|
sed: ["-f", "--file"]
|
|
73814
74033
|
};
|
|
73815
74034
|
function namesAnOutput(segment) {
|
|
@@ -73980,7 +74199,182 @@ function splitGitSubcommand(args) {
|
|
|
73980
74199
|
return [null, []];
|
|
73981
74200
|
}
|
|
73982
74201
|
|
|
74202
|
+
// src/permissions/sandbox.ts
|
|
74203
|
+
init_pathlib();
|
|
74204
|
+
init_which();
|
|
74205
|
+
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
74206
|
+
import { closeSync as closeSync8, existsSync as existsSync4, mkdirSync as mkdirSync12, openSync as openSync8, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "node:fs";
|
|
74207
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
74208
|
+
var CREDENTIAL_PATHS = [
|
|
74209
|
+
"~/.ssh/",
|
|
74210
|
+
"~/.aws/",
|
|
74211
|
+
"~/.gnupg/",
|
|
74212
|
+
"~/.kube/",
|
|
74213
|
+
"~/.docker/config.json",
|
|
74214
|
+
"~/.config/gh/",
|
|
74215
|
+
"~/.netrc",
|
|
74216
|
+
"~/.npmrc",
|
|
74217
|
+
"~/.pypirc",
|
|
74218
|
+
"~/.hx/auth.json"
|
|
74219
|
+
];
|
|
74220
|
+
var Sandbox = class {
|
|
74221
|
+
constructor(policy, backend) {
|
|
74222
|
+
this.policy = policy;
|
|
74223
|
+
this.backend = backend ?? detectBackend();
|
|
74224
|
+
}
|
|
74225
|
+
policy;
|
|
74226
|
+
backend;
|
|
74227
|
+
profilePath = null;
|
|
74228
|
+
/** False when degraded to `none`. */
|
|
74229
|
+
get active() {
|
|
74230
|
+
return this.backend !== "none";
|
|
74231
|
+
}
|
|
74232
|
+
/** The argv to actually exec. */
|
|
74233
|
+
wrap(argv) {
|
|
74234
|
+
if (this.backend === "seatbelt")
|
|
74235
|
+
return ["/usr/bin/sandbox-exec", "-f", this.writeProfile(), ...argv];
|
|
74236
|
+
if (this.backend === "bubblewrap") return buildBwrapArgv(this.policy, argv);
|
|
74237
|
+
return [...argv];
|
|
74238
|
+
}
|
|
74239
|
+
/** The generated Seatbelt profile - shown by `/permissions`. */
|
|
74240
|
+
profileText() {
|
|
74241
|
+
return buildSeatbeltProfile(this.policy);
|
|
74242
|
+
}
|
|
74243
|
+
writeProfile() {
|
|
74244
|
+
if (this.profilePath === null || !existsSync4(this.profilePath)) {
|
|
74245
|
+
const path = join(tmpdir2(), `hx-sandbox-${randomBytes6(4).toString("hex")}.sb`);
|
|
74246
|
+
writeFileSync9(path, this.profileText(), { mode: 384 });
|
|
74247
|
+
this.profilePath = path;
|
|
74248
|
+
}
|
|
74249
|
+
return this.profilePath;
|
|
74250
|
+
}
|
|
74251
|
+
cleanup() {
|
|
74252
|
+
if (this.profilePath !== null) {
|
|
74253
|
+
rmSync4(this.profilePath, { force: true });
|
|
74254
|
+
this.profilePath = null;
|
|
74255
|
+
}
|
|
74256
|
+
}
|
|
74257
|
+
};
|
|
74258
|
+
function detectBackend() {
|
|
74259
|
+
if (process.platform === "darwin" && existsSync4("/usr/bin/sandbox-exec")) return "seatbelt";
|
|
74260
|
+
if (process.platform === "linux" && which("bwrap")) return "bubblewrap";
|
|
74261
|
+
return "none";
|
|
74262
|
+
}
|
|
74263
|
+
function defaultPolicy(cwd, allowNetwork = false) {
|
|
74264
|
+
const temp = resolve(tmpdir2());
|
|
74265
|
+
const writable = [resolve(cwd), ...tempContainer(temp)];
|
|
74266
|
+
for (const extra of ["~/.cache", "~/Library/Caches"]) {
|
|
74267
|
+
const candidate = expandUser(extra);
|
|
74268
|
+
if (isDir(candidate)) writable.push(resolve(candidate));
|
|
74269
|
+
}
|
|
74270
|
+
return {
|
|
74271
|
+
writablePaths: [...new Set(writable)],
|
|
74272
|
+
readablePaths: ["/"],
|
|
74273
|
+
denyPaths: CREDENTIAL_PATHS.map((p) => ({
|
|
74274
|
+
path: real(absolute(expandUser(p))),
|
|
74275
|
+
isDir: p.endsWith("/")
|
|
74276
|
+
})),
|
|
74277
|
+
allowNetwork,
|
|
74278
|
+
allowSubprocess: true,
|
|
74279
|
+
cwd: resolve(cwd)
|
|
74280
|
+
};
|
|
74281
|
+
}
|
|
74282
|
+
var DARWIN_TEMP_ROOT = "/private/var/folders";
|
|
74283
|
+
var isRelativeTo = (path, root) => path === root || path.startsWith(`${root.replace(/\/$/, "")}/`);
|
|
74284
|
+
function tempContainer(temp) {
|
|
74285
|
+
const paths = [temp];
|
|
74286
|
+
if (isRelativeTo(temp, DARWIN_TEMP_ROOT)) {
|
|
74287
|
+
const container = parent(temp);
|
|
74288
|
+
if (isRelativeTo(container, DARWIN_TEMP_ROOT) && container !== DARWIN_TEMP_ROOT)
|
|
74289
|
+
paths.push(container);
|
|
74290
|
+
}
|
|
74291
|
+
return paths;
|
|
74292
|
+
}
|
|
74293
|
+
function buildSeatbeltProfile(policy) {
|
|
74294
|
+
const lines2 = [
|
|
74295
|
+
"(version 1)",
|
|
74296
|
+
"(deny default)",
|
|
74297
|
+
"",
|
|
74298
|
+
";; Process management",
|
|
74299
|
+
"(allow process-fork)",
|
|
74300
|
+
"(allow signal (target same-sandbox))",
|
|
74301
|
+
"(allow sysctl-read)",
|
|
74302
|
+
"(allow mach-lookup)",
|
|
74303
|
+
"(allow ipc-posix-shm)",
|
|
74304
|
+
policy.allowSubprocess ? "(allow process-exec*)" : "(deny process-exec*)",
|
|
74305
|
+
"",
|
|
74306
|
+
";; Reads"
|
|
74307
|
+
];
|
|
74308
|
+
for (const path of policy.readablePaths.length ? policy.readablePaths : ["/"]) {
|
|
74309
|
+
lines2.push(`(allow file-read* (subpath ${sbplString(real(path))}))`);
|
|
74310
|
+
}
|
|
74311
|
+
lines2.push("", ";; Writes");
|
|
74312
|
+
for (const path of policy.writablePaths)
|
|
74313
|
+
lines2.push(`(allow file-write* (subpath ${sbplString(real(path))}))`);
|
|
74314
|
+
for (const device of ["/dev/null", "/dev/stdout", "/dev/stderr", "/dev/tty", "/dev/urandom"]) {
|
|
74315
|
+
lines2.push(`(allow file-write-data file-read-data (literal ${sbplString(device)}))`);
|
|
74316
|
+
}
|
|
74317
|
+
lines2.push("", ";; Network", policy.allowNetwork ? "(allow network*)" : "(deny network*)");
|
|
74318
|
+
if (policy.denyPaths.length) {
|
|
74319
|
+
lines2.push("", ";; Credentials - last match wins, so these override both allowances");
|
|
74320
|
+
for (const denied of policy.denyPaths) {
|
|
74321
|
+
lines2.push(`(deny file-read* file-write* (subpath ${sbplString(real(denied.path))}))`);
|
|
74322
|
+
}
|
|
74323
|
+
}
|
|
74324
|
+
return `${lines2.join("\n")}
|
|
74325
|
+
`;
|
|
74326
|
+
}
|
|
74327
|
+
function credentialPathOf(path) {
|
|
74328
|
+
for (const entry of CREDENTIAL_PATHS) {
|
|
74329
|
+
const denied = real(absolute(expandUser(entry)));
|
|
74330
|
+
if (path === denied || entry.endsWith("/") && isRelativeTo(path, denied)) return entry;
|
|
74331
|
+
}
|
|
74332
|
+
return null;
|
|
74333
|
+
}
|
|
74334
|
+
var real = (path) => resolve(path);
|
|
74335
|
+
function sbplString(path) {
|
|
74336
|
+
return `"${path.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
74337
|
+
}
|
|
74338
|
+
function buildBwrapArgv(policy, argv) {
|
|
74339
|
+
const command2 = [
|
|
74340
|
+
"bwrap",
|
|
74341
|
+
"--ro-bind",
|
|
74342
|
+
"/",
|
|
74343
|
+
"/",
|
|
74344
|
+
"--dev",
|
|
74345
|
+
"/dev",
|
|
74346
|
+
"--proc",
|
|
74347
|
+
"/proc",
|
|
74348
|
+
"--die-with-parent"
|
|
74349
|
+
];
|
|
74350
|
+
const writable = policy.writablePaths.map(real);
|
|
74351
|
+
for (const path of writable) command2.push("--bind", path, path);
|
|
74352
|
+
for (const denied of policy.denyPaths) {
|
|
74353
|
+
const path = real(denied.path);
|
|
74354
|
+
if (!existsSync4(path) && !ensureMountpoint(denied, writable)) continue;
|
|
74355
|
+
if (isDir(path)) command2.push("--tmpfs", path, "--remount-ro", path);
|
|
74356
|
+
else command2.push("--ro-bind", "/dev/null", path);
|
|
74357
|
+
}
|
|
74358
|
+
if (!policy.allowNetwork) command2.push("--unshare-net");
|
|
74359
|
+
if (policy.cwd !== null) command2.push("--chdir", real(policy.cwd));
|
|
74360
|
+
return [...command2, "--", ...argv];
|
|
74361
|
+
}
|
|
74362
|
+
function ensureMountpoint(denied, writable) {
|
|
74363
|
+
const path = real(denied.path);
|
|
74364
|
+
if (!writable.some((root) => isRelativeTo(path, root))) return false;
|
|
74365
|
+
try {
|
|
74366
|
+
mkdirSync12(parent(path), { recursive: true });
|
|
74367
|
+
if (denied.isDir) mkdirSync12(path, { mode: 448, recursive: true });
|
|
74368
|
+
else closeSync8(openSync8(path, "a", 384));
|
|
74369
|
+
} catch {
|
|
74370
|
+
return false;
|
|
74371
|
+
}
|
|
74372
|
+
return existsSync4(path);
|
|
74373
|
+
}
|
|
74374
|
+
|
|
73983
74375
|
// src/permissions/engine.ts
|
|
74376
|
+
var FILE_TOOLS = /* @__PURE__ */ new Set(["Read", "Write", "Edit", "Grep", "Glob", "Symbols"]);
|
|
74377
|
+
var PROTECTED_DIRS = /* @__PURE__ */ new Set([".git", ".hx"]);
|
|
73984
74378
|
var DEFAULT_MUTATING_TOOLS = /* @__PURE__ */ new Set(["Bash", "Write", "Edit", "Task"]);
|
|
73985
74379
|
function allSpecifiers(request2) {
|
|
73986
74380
|
if (request2.specifiers?.length) return request2.specifiers;
|
|
@@ -74018,6 +74412,12 @@ var PermissionEngine = class _PermissionEngine {
|
|
|
74018
74412
|
if (denial) return outcome("deny", "matched a deny rule", denial);
|
|
74019
74413
|
if (this.sessionGrants.has(this.grantKey(request2)))
|
|
74020
74414
|
return outcome("allow", "granted for this session");
|
|
74415
|
+
const credential = this.credentialTouched(request2);
|
|
74416
|
+
if (credential !== null) {
|
|
74417
|
+
const named = this.matchAllow(request2, true);
|
|
74418
|
+
if (named) return outcome("allow", "matched an allow rule", named);
|
|
74419
|
+
return outcome("ask", `${credential} holds credentials`);
|
|
74420
|
+
}
|
|
74021
74421
|
if (request2.toolName === "Bash" && request2.specifier) {
|
|
74022
74422
|
const parsed = parse4(request2.specifier);
|
|
74023
74423
|
if (parsed.unparseable) {
|
|
@@ -74039,7 +74439,7 @@ var PermissionEngine = class _PermissionEngine {
|
|
|
74039
74439
|
return outcome("allow", "read-only tool in plan mode");
|
|
74040
74440
|
}
|
|
74041
74441
|
if (!request2.mutating) return outcome("allow", "read-only tool");
|
|
74042
|
-
if (this.mode === "acceptEdits" && _PermissionEngine.EDIT_TOOLS.has(request2.toolName)) {
|
|
74442
|
+
if (this.mode === "acceptEdits" && _PermissionEngine.EDIT_TOOLS.has(request2.toolName) && this.editsProject(request2)) {
|
|
74043
74443
|
return outcome("allow", "acceptEdits mode");
|
|
74044
74444
|
}
|
|
74045
74445
|
if (request2.toolName === "Bash" && request2.specifier) {
|
|
@@ -74051,6 +74451,33 @@ var PermissionEngine = class _PermissionEngine {
|
|
|
74051
74451
|
}
|
|
74052
74452
|
return outcome("ask", "mutating tool");
|
|
74053
74453
|
}
|
|
74454
|
+
/** Where `specifier` really is: absolute, with `..` and symlinks resolved. */
|
|
74455
|
+
target(specifier) {
|
|
74456
|
+
return realPath(purePath(expandUser(specifier)), resolve(this.cwd));
|
|
74457
|
+
}
|
|
74458
|
+
/** The credential file or folder a file tool's call reaches, as written in
|
|
74459
|
+
* `CREDENTIAL_PATHS`, or `null`. */
|
|
74460
|
+
credentialTouched(request2) {
|
|
74461
|
+
if (!FILE_TOOLS.has(request2.toolName)) return null;
|
|
74462
|
+
for (const specifier of allSpecifiers(request2)) {
|
|
74463
|
+
const credential = credentialPathOf(this.target(specifier));
|
|
74464
|
+
if (credential !== null) return credential;
|
|
74465
|
+
}
|
|
74466
|
+
return null;
|
|
74467
|
+
}
|
|
74468
|
+
/**
|
|
74469
|
+
* Whether every file an edit changes is the project's own, which is all
|
|
74470
|
+
* acceptEdits covers: inside the working directory once symlinks are
|
|
74471
|
+
* resolved, and not in a folder whose files run code or grant permissions.
|
|
74472
|
+
*/
|
|
74473
|
+
editsProject(request2) {
|
|
74474
|
+
const root = resolve(this.cwd);
|
|
74475
|
+
const prefix = root === "/" ? "/" : `${root}/`;
|
|
74476
|
+
const targets = allSpecifiers(request2).map((specifier) => this.target(specifier));
|
|
74477
|
+
return targets.length > 0 && targets.every(
|
|
74478
|
+
(target) => target.startsWith(prefix) && !target.slice(prefix.length).split("/").some((part) => PROTECTED_DIRS.has(part))
|
|
74479
|
+
);
|
|
74480
|
+
}
|
|
74054
74481
|
applies(rule2, request2) {
|
|
74055
74482
|
return rule2.tool === request2.toolName || rule2.tool === "*";
|
|
74056
74483
|
}
|
|
@@ -74062,13 +74489,14 @@ var PermissionEngine = class _PermissionEngine {
|
|
|
74062
74489
|
return null;
|
|
74063
74490
|
}
|
|
74064
74491
|
/**
|
|
74065
|
-
* Allow requires *every* segment to be covered
|
|
74066
|
-
* right for deny (`safe && danger` must be caught)
|
|
74067
|
-
* allow: `allow: Bash(git status:*)` would green-light
|
|
74492
|
+
* Allow requires *every* segment to be covered, each as it is written.
|
|
74493
|
+
* Any-segment matching is right for deny (`safe && danger` must be caught)
|
|
74494
|
+
* but catastrophic for allow: `allow: Bash(git status:*)` would green-light
|
|
74495
|
+
* `git status && rm -rf /`.
|
|
74068
74496
|
*/
|
|
74069
|
-
matchAllow(request2) {
|
|
74497
|
+
matchAllow(request2, named = false) {
|
|
74070
74498
|
const candidates2 = this.rules.filter(
|
|
74071
|
-
(rule2) => rule2.decision === "allow" && this.applies(rule2, request2)
|
|
74499
|
+
(rule2) => rule2.decision === "allow" && this.applies(rule2, request2) && (!named || rule2.specifier !== null)
|
|
74072
74500
|
);
|
|
74073
74501
|
if (candidates2.length === 0) return null;
|
|
74074
74502
|
if (request2.toolName !== "Bash" || !request2.specifier) {
|
|
@@ -74086,7 +74514,7 @@ var PermissionEngine = class _PermissionEngine {
|
|
|
74086
74514
|
let first2 = null;
|
|
74087
74515
|
for (const segment of parsed.segments) {
|
|
74088
74516
|
const covering = candidates2.find(
|
|
74089
|
-
(rule2) => rule2.specifier === null || matchSpecifier(segment, rule2.specifier)
|
|
74517
|
+
(rule2) => rule2.specifier === null || matchSpecifier(segment, rule2.specifier, true)
|
|
74090
74518
|
);
|
|
74091
74519
|
if (covering === void 0) return null;
|
|
74092
74520
|
first2 ??= covering;
|
|
@@ -74193,6 +74621,7 @@ function persistableRules(toolName, specifier) {
|
|
|
74193
74621
|
return rules;
|
|
74194
74622
|
}
|
|
74195
74623
|
function rulePrefix(segment) {
|
|
74624
|
+
if (segment.assignments.length || !segment.executable) return null;
|
|
74196
74625
|
const first2 = segment.args[0];
|
|
74197
74626
|
if (first2 !== void 0 && !first2.startsWith("-")) return `${segment.executable} ${first2}`;
|
|
74198
74627
|
return null;
|
|
@@ -74361,172 +74790,6 @@ var InvalidRule = class extends Error {
|
|
|
74361
74790
|
name = "InvalidRule";
|
|
74362
74791
|
};
|
|
74363
74792
|
|
|
74364
|
-
// src/permissions/sandbox.ts
|
|
74365
|
-
init_pathlib();
|
|
74366
|
-
init_which();
|
|
74367
|
-
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
74368
|
-
import { closeSync as closeSync8, existsSync as existsSync4, mkdirSync as mkdirSync11, openSync as openSync8, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "node:fs";
|
|
74369
|
-
import { tmpdir as tmpdir2 } from "node:os";
|
|
74370
|
-
var CREDENTIAL_PATHS = [
|
|
74371
|
-
"~/.ssh/",
|
|
74372
|
-
"~/.aws/",
|
|
74373
|
-
"~/.gnupg/",
|
|
74374
|
-
"~/.kube/",
|
|
74375
|
-
"~/.docker/config.json",
|
|
74376
|
-
"~/.config/gh/",
|
|
74377
|
-
"~/.netrc",
|
|
74378
|
-
"~/.npmrc",
|
|
74379
|
-
"~/.pypirc",
|
|
74380
|
-
"~/.hx/auth.json"
|
|
74381
|
-
];
|
|
74382
|
-
var Sandbox = class {
|
|
74383
|
-
constructor(policy, backend) {
|
|
74384
|
-
this.policy = policy;
|
|
74385
|
-
this.backend = backend ?? detectBackend();
|
|
74386
|
-
}
|
|
74387
|
-
policy;
|
|
74388
|
-
backend;
|
|
74389
|
-
profilePath = null;
|
|
74390
|
-
/** False when degraded to `none`. */
|
|
74391
|
-
get active() {
|
|
74392
|
-
return this.backend !== "none";
|
|
74393
|
-
}
|
|
74394
|
-
/** The argv to actually exec. */
|
|
74395
|
-
wrap(argv) {
|
|
74396
|
-
if (this.backend === "seatbelt")
|
|
74397
|
-
return ["/usr/bin/sandbox-exec", "-f", this.writeProfile(), ...argv];
|
|
74398
|
-
if (this.backend === "bubblewrap") return buildBwrapArgv(this.policy, argv);
|
|
74399
|
-
return [...argv];
|
|
74400
|
-
}
|
|
74401
|
-
/** The generated Seatbelt profile - shown by `/permissions`. */
|
|
74402
|
-
profileText() {
|
|
74403
|
-
return buildSeatbeltProfile(this.policy);
|
|
74404
|
-
}
|
|
74405
|
-
writeProfile() {
|
|
74406
|
-
if (this.profilePath === null || !existsSync4(this.profilePath)) {
|
|
74407
|
-
const path = join(tmpdir2(), `hx-sandbox-${randomBytes6(4).toString("hex")}.sb`);
|
|
74408
|
-
writeFileSync8(path, this.profileText(), { mode: 384 });
|
|
74409
|
-
this.profilePath = path;
|
|
74410
|
-
}
|
|
74411
|
-
return this.profilePath;
|
|
74412
|
-
}
|
|
74413
|
-
cleanup() {
|
|
74414
|
-
if (this.profilePath !== null) {
|
|
74415
|
-
rmSync4(this.profilePath, { force: true });
|
|
74416
|
-
this.profilePath = null;
|
|
74417
|
-
}
|
|
74418
|
-
}
|
|
74419
|
-
};
|
|
74420
|
-
function detectBackend() {
|
|
74421
|
-
if (process.platform === "darwin" && existsSync4("/usr/bin/sandbox-exec")) return "seatbelt";
|
|
74422
|
-
if (process.platform === "linux" && which("bwrap")) return "bubblewrap";
|
|
74423
|
-
return "none";
|
|
74424
|
-
}
|
|
74425
|
-
function defaultPolicy(cwd, allowNetwork = false) {
|
|
74426
|
-
const temp = resolve(tmpdir2());
|
|
74427
|
-
const writable = [resolve(cwd), ...tempContainer(temp)];
|
|
74428
|
-
for (const extra of ["~/.cache", "~/Library/Caches"]) {
|
|
74429
|
-
const candidate = expandUser(extra);
|
|
74430
|
-
if (isDir(candidate)) writable.push(resolve(candidate));
|
|
74431
|
-
}
|
|
74432
|
-
return {
|
|
74433
|
-
writablePaths: [...new Set(writable)],
|
|
74434
|
-
readablePaths: ["/"],
|
|
74435
|
-
denyPaths: CREDENTIAL_PATHS.map((p) => ({
|
|
74436
|
-
path: real(absolute(expandUser(p))),
|
|
74437
|
-
isDir: p.endsWith("/")
|
|
74438
|
-
})),
|
|
74439
|
-
allowNetwork,
|
|
74440
|
-
allowSubprocess: true,
|
|
74441
|
-
cwd: resolve(cwd)
|
|
74442
|
-
};
|
|
74443
|
-
}
|
|
74444
|
-
var DARWIN_TEMP_ROOT = "/private/var/folders";
|
|
74445
|
-
var isRelativeTo = (path, root) => path === root || path.startsWith(`${root.replace(/\/$/, "")}/`);
|
|
74446
|
-
function tempContainer(temp) {
|
|
74447
|
-
const paths = [temp];
|
|
74448
|
-
if (isRelativeTo(temp, DARWIN_TEMP_ROOT)) {
|
|
74449
|
-
const container = parent(temp);
|
|
74450
|
-
if (isRelativeTo(container, DARWIN_TEMP_ROOT) && container !== DARWIN_TEMP_ROOT)
|
|
74451
|
-
paths.push(container);
|
|
74452
|
-
}
|
|
74453
|
-
return paths;
|
|
74454
|
-
}
|
|
74455
|
-
function buildSeatbeltProfile(policy) {
|
|
74456
|
-
const lines2 = [
|
|
74457
|
-
"(version 1)",
|
|
74458
|
-
"(deny default)",
|
|
74459
|
-
"",
|
|
74460
|
-
";; Process management",
|
|
74461
|
-
"(allow process-fork)",
|
|
74462
|
-
"(allow signal (target same-sandbox))",
|
|
74463
|
-
"(allow sysctl-read)",
|
|
74464
|
-
"(allow mach-lookup)",
|
|
74465
|
-
"(allow ipc-posix-shm)",
|
|
74466
|
-
policy.allowSubprocess ? "(allow process-exec*)" : "(deny process-exec*)",
|
|
74467
|
-
"",
|
|
74468
|
-
";; Reads"
|
|
74469
|
-
];
|
|
74470
|
-
for (const path of policy.readablePaths.length ? policy.readablePaths : ["/"]) {
|
|
74471
|
-
lines2.push(`(allow file-read* (subpath ${sbplString(real(path))}))`);
|
|
74472
|
-
}
|
|
74473
|
-
lines2.push("", ";; Writes");
|
|
74474
|
-
for (const path of policy.writablePaths)
|
|
74475
|
-
lines2.push(`(allow file-write* (subpath ${sbplString(real(path))}))`);
|
|
74476
|
-
for (const device of ["/dev/null", "/dev/stdout", "/dev/stderr", "/dev/tty", "/dev/urandom"]) {
|
|
74477
|
-
lines2.push(`(allow file-write-data file-read-data (literal ${sbplString(device)}))`);
|
|
74478
|
-
}
|
|
74479
|
-
lines2.push("", ";; Network", policy.allowNetwork ? "(allow network*)" : "(deny network*)");
|
|
74480
|
-
if (policy.denyPaths.length) {
|
|
74481
|
-
lines2.push("", ";; Credentials - last match wins, so these override both allowances");
|
|
74482
|
-
for (const denied of policy.denyPaths) {
|
|
74483
|
-
lines2.push(`(deny file-read* file-write* (subpath ${sbplString(real(denied.path))}))`);
|
|
74484
|
-
}
|
|
74485
|
-
}
|
|
74486
|
-
return `${lines2.join("\n")}
|
|
74487
|
-
`;
|
|
74488
|
-
}
|
|
74489
|
-
var real = (path) => resolve(path);
|
|
74490
|
-
function sbplString(path) {
|
|
74491
|
-
return `"${path.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
74492
|
-
}
|
|
74493
|
-
function buildBwrapArgv(policy, argv) {
|
|
74494
|
-
const command2 = [
|
|
74495
|
-
"bwrap",
|
|
74496
|
-
"--ro-bind",
|
|
74497
|
-
"/",
|
|
74498
|
-
"/",
|
|
74499
|
-
"--dev",
|
|
74500
|
-
"/dev",
|
|
74501
|
-
"--proc",
|
|
74502
|
-
"/proc",
|
|
74503
|
-
"--die-with-parent"
|
|
74504
|
-
];
|
|
74505
|
-
const writable = policy.writablePaths.map(real);
|
|
74506
|
-
for (const path of writable) command2.push("--bind", path, path);
|
|
74507
|
-
for (const denied of policy.denyPaths) {
|
|
74508
|
-
const path = real(denied.path);
|
|
74509
|
-
if (!existsSync4(path) && !ensureMountpoint(denied, writable)) continue;
|
|
74510
|
-
if (isDir(path)) command2.push("--tmpfs", path, "--remount-ro", path);
|
|
74511
|
-
else command2.push("--ro-bind", "/dev/null", path);
|
|
74512
|
-
}
|
|
74513
|
-
if (!policy.allowNetwork) command2.push("--unshare-net");
|
|
74514
|
-
if (policy.cwd !== null) command2.push("--chdir", real(policy.cwd));
|
|
74515
|
-
return [...command2, "--", ...argv];
|
|
74516
|
-
}
|
|
74517
|
-
function ensureMountpoint(denied, writable) {
|
|
74518
|
-
const path = real(denied.path);
|
|
74519
|
-
if (!writable.some((root) => isRelativeTo(path, root))) return false;
|
|
74520
|
-
try {
|
|
74521
|
-
mkdirSync11(parent(path), { recursive: true });
|
|
74522
|
-
if (denied.isDir) mkdirSync11(path, { mode: 448, recursive: true });
|
|
74523
|
-
else closeSync8(openSync8(path, "a", 384));
|
|
74524
|
-
} catch {
|
|
74525
|
-
return false;
|
|
74526
|
-
}
|
|
74527
|
-
return existsSync4(path);
|
|
74528
|
-
}
|
|
74529
|
-
|
|
74530
74793
|
// src/runtime.ts
|
|
74531
74794
|
init_models();
|
|
74532
74795
|
init_registry();
|
|
@@ -74668,7 +74931,7 @@ init_py();
|
|
|
74668
74931
|
init_shlex();
|
|
74669
74932
|
import { execFile as execFile2, spawn as spawn7 } from "node:child_process";
|
|
74670
74933
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
74671
|
-
import { closeSync as closeSync9, mkdirSync as
|
|
74934
|
+
import { closeSync as closeSync9, mkdirSync as mkdirSync13, openSync as openSync9, readFileSync as readFileSync6 } from "node:fs";
|
|
74672
74935
|
import { constants as constants3 } from "node:os";
|
|
74673
74936
|
var DESCRIPTION5 = `Run a shell command in a persistent session.
|
|
74674
74937
|
|
|
@@ -74742,11 +75005,17 @@ var Reader = class {
|
|
|
74742
75005
|
return this.chunks.shift() ?? Buffer.alloc(0);
|
|
74743
75006
|
}
|
|
74744
75007
|
};
|
|
75008
|
+
var POSIX_SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "zsh", "dash", "ksh", "ksh93", "mksh", "ash", "yash"]);
|
|
75009
|
+
function posixShell(shell = null) {
|
|
75010
|
+
const chosen = shell || process.env.SHELL || "";
|
|
75011
|
+
if (POSIX_SHELLS.has(chosen.slice(chosen.lastIndexOf("/") + 1))) return chosen;
|
|
75012
|
+
return isFile("/bin/bash") ? "/bin/bash" : "/bin/sh";
|
|
75013
|
+
}
|
|
74745
75014
|
var PersistentShell = class {
|
|
74746
75015
|
constructor(currentCwd, shell = null, env = {}, sandbox = null) {
|
|
74747
75016
|
this.currentCwd = currentCwd;
|
|
74748
75017
|
this.sandbox = sandbox;
|
|
74749
|
-
this.shell = shell
|
|
75018
|
+
this.shell = posixShell(shell);
|
|
74750
75019
|
this.env = commandEnvironment({ ...env, HX: "1", TERM: "dumb" });
|
|
74751
75020
|
}
|
|
74752
75021
|
currentCwd;
|
|
@@ -74949,7 +75218,7 @@ var BackgroundJobs = class {
|
|
|
74949
75218
|
*/
|
|
74950
75219
|
async start(command2, cwd, env, sandbox = null) {
|
|
74951
75220
|
const jobId = `bg_${randomBytes7(4).toString("hex")}`;
|
|
74952
|
-
|
|
75221
|
+
mkdirSync13(this.logDir, { recursive: true });
|
|
74953
75222
|
const logPath = join(this.logDir, `${jobId}.log`);
|
|
74954
75223
|
let argv = ["/bin/sh", "-c", command2];
|
|
74955
75224
|
if (sandbox !== null) argv = sandbox.wrap(argv);
|
|
@@ -75228,7 +75497,7 @@ init_json();
|
|
|
75228
75497
|
init_py();
|
|
75229
75498
|
|
|
75230
75499
|
// src/tools/glob.ts
|
|
75231
|
-
import { statSync as
|
|
75500
|
+
import { statSync as statSync13 } from "node:fs";
|
|
75232
75501
|
init_pathlib();
|
|
75233
75502
|
init_py();
|
|
75234
75503
|
var DESCRIPTION6 = `Find files by glob pattern (e.g. \`**/*.py\`, \`src/**/*.ts\`).
|
|
@@ -75259,6 +75528,10 @@ var GlobTool = class extends Tool {
|
|
|
75259
75528
|
required: ["pattern"]
|
|
75260
75529
|
};
|
|
75261
75530
|
}
|
|
75531
|
+
/** The folder searched, so a path rule covers a search of it. */
|
|
75532
|
+
permissionSpecifier(params) {
|
|
75533
|
+
return typeof params.path === "string" && params.path ? params.path : null;
|
|
75534
|
+
}
|
|
75262
75535
|
async run(params, ctx) {
|
|
75263
75536
|
const root = searchRoot(params.path, ctx.cwd);
|
|
75264
75537
|
if (!isDir(root)) throw new ToolError(`${root}: not a directory`);
|
|
@@ -75301,7 +75574,7 @@ function checkPattern(pattern) {
|
|
|
75301
75574
|
var ignored = (path) => parts(path).some((part) => IGNORED_DIRS.has(part));
|
|
75302
75575
|
function mtime(path) {
|
|
75303
75576
|
try {
|
|
75304
|
-
return
|
|
75577
|
+
return statSync13(path).mtimeMs;
|
|
75305
75578
|
} catch {
|
|
75306
75579
|
return 0;
|
|
75307
75580
|
}
|
|
@@ -75343,6 +75616,10 @@ var GrepTool = class extends Tool {
|
|
|
75343
75616
|
required: ["pattern"]
|
|
75344
75617
|
};
|
|
75345
75618
|
}
|
|
75619
|
+
/** The file or folder searched, so a path rule covers a search of it. */
|
|
75620
|
+
permissionSpecifier(params) {
|
|
75621
|
+
return typeof params.path === "string" && params.path ? params.path : null;
|
|
75622
|
+
}
|
|
75346
75623
|
async run(params, ctx) {
|
|
75347
75624
|
const pattern = String(params.pattern);
|
|
75348
75625
|
const ripgrep = which("rg");
|
|
@@ -75397,7 +75674,28 @@ function runRipgrep(rg, pattern, root, mode2, params) {
|
|
|
75397
75674
|
}
|
|
75398
75675
|
throw new ToolError(stderr || `ripgrep exited ${result2.status}`);
|
|
75399
75676
|
}
|
|
75400
|
-
|
|
75677
|
+
const hidden = credentialsUnder(root);
|
|
75678
|
+
return splitLines(result2.stdout ?? "").filter(
|
|
75679
|
+
(line) => line && !hidden.some((credential) => credential.covers(line))
|
|
75680
|
+
);
|
|
75681
|
+
}
|
|
75682
|
+
function credentialsUnder(root) {
|
|
75683
|
+
const base = root.replace(/\/$/, "");
|
|
75684
|
+
const real2 = resolve(root);
|
|
75685
|
+
const found = [];
|
|
75686
|
+
for (const entry of CREDENTIAL_PATHS) {
|
|
75687
|
+
const spellings = /* @__PURE__ */ new Set([purePath(absolute(expandUser(entry))), resolve(expandUser(entry))]);
|
|
75688
|
+
for (const path of spellings) {
|
|
75689
|
+
let shown;
|
|
75690
|
+
if (path.startsWith(`${real2}/`)) shown = base + path.slice(real2.length);
|
|
75691
|
+
else if (path.startsWith(`${base}/`)) shown = path;
|
|
75692
|
+
else continue;
|
|
75693
|
+
found.push({
|
|
75694
|
+
covers: entry.endsWith("/") ? (line) => line.startsWith(`${shown}/`) : (line) => line === shown || line.startsWith(`${shown}:`) || line.startsWith(`${shown}-`)
|
|
75695
|
+
});
|
|
75696
|
+
}
|
|
75697
|
+
}
|
|
75698
|
+
return found;
|
|
75401
75699
|
}
|
|
75402
75700
|
function search(regex, root, mode2, params) {
|
|
75403
75701
|
const globFilter = String(params.glob || "");
|
|
@@ -75424,7 +75722,7 @@ function search(regex, root, mode2, params) {
|
|
|
75424
75722
|
function walk(root) {
|
|
75425
75723
|
if (isFile(root)) return [root];
|
|
75426
75724
|
return glob(root, "**/*").filter(
|
|
75427
|
-
(path) => isFile(path) && !parts(path).some((part) => IGNORED_DIRS.has(part))
|
|
75725
|
+
(path) => isFile(path) && !parts(path).some((part) => IGNORED_DIRS.has(part)) && credentialPathOf(resolve(path)) === null
|
|
75428
75726
|
);
|
|
75429
75727
|
}
|
|
75430
75728
|
function renderContext(path, lines2, hits, before2, after2) {
|
|
@@ -75437,7 +75735,7 @@ function renderContext(path, lines2, hits, before2, after2) {
|
|
|
75437
75735
|
}
|
|
75438
75736
|
|
|
75439
75737
|
// src/tools/symbols.ts
|
|
75440
|
-
import { readFileSync as readFileSync8, statSync as
|
|
75738
|
+
import { readFileSync as readFileSync8, statSync as statSync14 } from "node:fs";
|
|
75441
75739
|
import { createRequire } from "node:module";
|
|
75442
75740
|
import { dirname } from "node:path";
|
|
75443
75741
|
init_json();
|
|
@@ -75569,6 +75867,12 @@ var SymbolsTool = class extends Tool {
|
|
|
75569
75867
|
required: ["mode"]
|
|
75570
75868
|
};
|
|
75571
75869
|
}
|
|
75870
|
+
/** The file or folder read, so a path rule covers it. */
|
|
75871
|
+
permissionSpecifier(params) {
|
|
75872
|
+
for (const key2 of ["file_path", "path"])
|
|
75873
|
+
if (typeof params[key2] === "string" && params[key2]) return params[key2];
|
|
75874
|
+
return null;
|
|
75875
|
+
}
|
|
75572
75876
|
async run(params, ctx) {
|
|
75573
75877
|
const mode2 = String(params.mode);
|
|
75574
75878
|
const limit = toInt(params.limit || DEFAULT_LIMIT3);
|
|
@@ -75677,7 +75981,7 @@ function candidates(params, ctx) {
|
|
|
75677
75981
|
}
|
|
75678
75982
|
function mtime2(path) {
|
|
75679
75983
|
try {
|
|
75680
|
-
return
|
|
75984
|
+
return statSync14(path).mtimeMs;
|
|
75681
75985
|
} catch {
|
|
75682
75986
|
return 0;
|
|
75683
75987
|
}
|
|
@@ -75706,7 +76010,7 @@ function specFor(path) {
|
|
|
75706
76010
|
async function parse5(path, spec2) {
|
|
75707
76011
|
let size3;
|
|
75708
76012
|
try {
|
|
75709
|
-
size3 =
|
|
76013
|
+
size3 = statSync14(path).size;
|
|
75710
76014
|
} catch (error) {
|
|
75711
76015
|
throw new ToolError(`${path}: ${osErrorText(error)}`);
|
|
75712
76016
|
}
|
|
@@ -76413,7 +76717,7 @@ ${listed2}
|
|
|
76413
76717
|
They still apply to this project on this machine. Earlier versions of HX wrote them into the checkout; it no longer writes there at all.`
|
|
76414
76718
|
];
|
|
76415
76719
|
}
|
|
76416
|
-
function buildRuntime(parsed, resume = null) {
|
|
76720
|
+
function buildRuntime(parsed, resume = null, signedOut = false) {
|
|
76417
76721
|
ensureUserDirs();
|
|
76418
76722
|
const settings = loadSettings(parsed.cwd, parsed.overrides);
|
|
76419
76723
|
const bus = new EventBus();
|
|
@@ -76424,10 +76728,16 @@ function buildRuntime(parsed, resume = null) {
|
|
|
76424
76728
|
models.loadCache();
|
|
76425
76729
|
const modelInfo2 = models.getOrDefault(settings.models.model);
|
|
76426
76730
|
const session = resume ? loadSession(resume) : newSession(settings.cwd, settings.models.model);
|
|
76427
|
-
|
|
76428
|
-
|
|
76429
|
-
models
|
|
76430
|
-
|
|
76731
|
+
let provider;
|
|
76732
|
+
try {
|
|
76733
|
+
provider = buildProvider(settings.models.model, auth, {
|
|
76734
|
+
sessionId: session.meta.sessionId,
|
|
76735
|
+
models
|
|
76736
|
+
});
|
|
76737
|
+
} catch (error) {
|
|
76738
|
+
if (!(signedOut && error instanceof MissingCredential)) throw error;
|
|
76739
|
+
provider = new DeferredProvider(settings.models.model, auth, session.meta.sessionId, models);
|
|
76740
|
+
}
|
|
76431
76741
|
const sandbox = settings.permissions.sandbox ? new Sandbox(defaultPolicy(settings.cwd, settings.permissions.allowNetwork)) : null;
|
|
76432
76742
|
const shell = new PersistentShell(settings.cwd, settings.bash.shell, {}, sandbox);
|
|
76433
76743
|
const jobs = new BackgroundJobs(join(sessionOutputsDir(session.meta.sessionId), "jobs"));
|
|
@@ -76460,6 +76770,13 @@ function buildRuntime(parsed, resume = null) {
|
|
|
76460
76770
|
`Ignored ${hooks.ignored.length} hook(s) declared in the project's shared settings - hooks run shell commands, so they are only loaded from your own settings. See /hooks.`
|
|
76461
76771
|
);
|
|
76462
76772
|
}
|
|
76773
|
+
if (settings.bash.shell && posixShell(settings.bash.shell) !== settings.bash.shell)
|
|
76774
|
+
notices.push(
|
|
76775
|
+
`bash.shell is ${settings.bash.shell}, which does not speak POSIX sh; commands run in ${shell.shell}.`
|
|
76776
|
+
);
|
|
76777
|
+
const [mcpServers, waitingServers] = partition(loadConfigs(settings.cwd), settings.cwd);
|
|
76778
|
+
const waiting = waitingServers.filter((server) => server.config.enabled);
|
|
76779
|
+
for (const server of waiting) notices.push(waitingNotice(server));
|
|
76463
76780
|
const agents = new Map(discover2(settings.cwd).map((agent) => [agent.name, agent]));
|
|
76464
76781
|
const subagents = new SubagentRunner({
|
|
76465
76782
|
definitions: agents,
|
|
@@ -76520,7 +76837,7 @@ function buildRuntime(parsed, resume = null) {
|
|
|
76520
76837
|
tools,
|
|
76521
76838
|
checkpoints,
|
|
76522
76839
|
hooks,
|
|
76523
|
-
new MCPManager(
|
|
76840
|
+
new MCPManager(mcpServers, void 0, waiting),
|
|
76524
76841
|
notices
|
|
76525
76842
|
);
|
|
76526
76843
|
}
|
|
@@ -76540,7 +76857,7 @@ ${listed2}`,
|
|
|
76540
76857
|
|
|
76541
76858
|
// src/trace.ts
|
|
76542
76859
|
import { spawn as spawn8 } from "node:child_process";
|
|
76543
|
-
import { mkdirSync as
|
|
76860
|
+
import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync10 } from "node:fs";
|
|
76544
76861
|
import { pathToFileURL } from "node:url";
|
|
76545
76862
|
init_messages();
|
|
76546
76863
|
init_json();
|
|
@@ -76584,8 +76901,8 @@ function renderHtml(trace) {
|
|
|
76584
76901
|
return trace_template_default.replace(/__HX_TRACE_(?:TITLE|DATA)__/g, (match4) => filled[match4]);
|
|
76585
76902
|
}
|
|
76586
76903
|
function writeTrace(trace, path) {
|
|
76587
|
-
|
|
76588
|
-
|
|
76904
|
+
mkdirSync14(parent(path), { recursive: true });
|
|
76905
|
+
writeFileSync10(path, renderHtml(trace), "utf8");
|
|
76589
76906
|
return path;
|
|
76590
76907
|
}
|
|
76591
76908
|
var defaultTracePath = (session) => join(sessionDir(session.meta.sessionId), TRACE_FILENAME);
|
|
@@ -77883,8 +78200,8 @@ var ProcessTerminal = class {
|
|
|
77883
78200
|
let buffer = Buffer.from(data);
|
|
77884
78201
|
while (buffer.length) {
|
|
77885
78202
|
try {
|
|
77886
|
-
const
|
|
77887
|
-
buffer = buffer.subarray(
|
|
78203
|
+
const written2 = writeSync5(1, buffer);
|
|
78204
|
+
buffer = buffer.subarray(written2);
|
|
77888
78205
|
} catch (error) {
|
|
77889
78206
|
if (error.code === "EAGAIN") continue;
|
|
77890
78207
|
return;
|
|
@@ -78292,13 +78609,302 @@ var ShiftEnter = class {
|
|
|
78292
78609
|
}
|
|
78293
78610
|
};
|
|
78294
78611
|
|
|
78612
|
+
// src/term/component.ts
|
|
78613
|
+
var Widget = class {
|
|
78614
|
+
cache = [];
|
|
78615
|
+
cacheWidth = -1;
|
|
78616
|
+
isDirty = true;
|
|
78617
|
+
render(width) {
|
|
78618
|
+
if (this.isDirty || width !== this.cacheWidth) {
|
|
78619
|
+
this.cache = this.draw(width);
|
|
78620
|
+
this.cacheWidth = width;
|
|
78621
|
+
this.isDirty = false;
|
|
78622
|
+
}
|
|
78623
|
+
return this.cache;
|
|
78624
|
+
}
|
|
78625
|
+
invalidate() {
|
|
78626
|
+
this.isDirty = true;
|
|
78627
|
+
}
|
|
78628
|
+
get dirty() {
|
|
78629
|
+
return this.isDirty;
|
|
78630
|
+
}
|
|
78631
|
+
};
|
|
78632
|
+
var Container = class extends Widget {
|
|
78633
|
+
children;
|
|
78634
|
+
constructor(...children) {
|
|
78635
|
+
super();
|
|
78636
|
+
this.children = children;
|
|
78637
|
+
}
|
|
78638
|
+
add(child) {
|
|
78639
|
+
this.children.push(child);
|
|
78640
|
+
this.invalidate();
|
|
78641
|
+
return child;
|
|
78642
|
+
}
|
|
78643
|
+
extend(children) {
|
|
78644
|
+
this.children.push(...children);
|
|
78645
|
+
this.invalidate();
|
|
78646
|
+
}
|
|
78647
|
+
clear() {
|
|
78648
|
+
this.children = [];
|
|
78649
|
+
this.invalidate();
|
|
78650
|
+
}
|
|
78651
|
+
remove(child) {
|
|
78652
|
+
const index = this.children.indexOf(child);
|
|
78653
|
+
if (index >= 0) {
|
|
78654
|
+
this.children.splice(index, 1);
|
|
78655
|
+
this.invalidate();
|
|
78656
|
+
}
|
|
78657
|
+
}
|
|
78658
|
+
/** Concatenate the children, asking each: a clean child returns its cache,
|
|
78659
|
+
* so a frame costs what actually changed. */
|
|
78660
|
+
render(width) {
|
|
78661
|
+
const lines2 = [];
|
|
78662
|
+
for (const child of this.children) lines2.push(...child.render(width));
|
|
78663
|
+
this.isDirty = false;
|
|
78664
|
+
return lines2;
|
|
78665
|
+
}
|
|
78666
|
+
draw(width) {
|
|
78667
|
+
return this.render(width);
|
|
78668
|
+
}
|
|
78669
|
+
/** Offer the key to each child until one takes it. */
|
|
78670
|
+
handleInput(key2, data) {
|
|
78671
|
+
for (const child of this.children) {
|
|
78672
|
+
const handler = child.handleInput;
|
|
78673
|
+
if (handler?.call(child, key2, data)) return true;
|
|
78674
|
+
}
|
|
78675
|
+
return false;
|
|
78676
|
+
}
|
|
78677
|
+
};
|
|
78678
|
+
|
|
78679
|
+
// src/term/primitives.ts
|
|
78680
|
+
var Text = class extends Widget {
|
|
78681
|
+
constructor(content = "", paddingX = 1, paddingY = 0, tint2 = null) {
|
|
78682
|
+
super();
|
|
78683
|
+
this.content = content;
|
|
78684
|
+
this.paddingX = paddingX;
|
|
78685
|
+
this.paddingY = paddingY;
|
|
78686
|
+
this.tint = tint2;
|
|
78687
|
+
}
|
|
78688
|
+
content;
|
|
78689
|
+
paddingX;
|
|
78690
|
+
paddingY;
|
|
78691
|
+
tint;
|
|
78692
|
+
get text() {
|
|
78693
|
+
return this.content;
|
|
78694
|
+
}
|
|
78695
|
+
setText(text3) {
|
|
78696
|
+
if (text3 !== this.content) {
|
|
78697
|
+
this.content = text3;
|
|
78698
|
+
this.invalidate();
|
|
78699
|
+
}
|
|
78700
|
+
}
|
|
78701
|
+
draw(width) {
|
|
78702
|
+
const inner = Math.max(1, width - 2 * this.paddingX);
|
|
78703
|
+
const pad = " ".repeat(this.paddingX);
|
|
78704
|
+
const body = wrap2(this.content, inner).map((line) => `${pad}${line}`);
|
|
78705
|
+
const blank = Array(this.paddingY).fill("");
|
|
78706
|
+
return [...blank, ...body, ...blank].map(
|
|
78707
|
+
(line) => this.tint === null ? fillLine(line, width) : terminate(this.tint(padTo(line, width)))
|
|
78708
|
+
);
|
|
78709
|
+
}
|
|
78710
|
+
};
|
|
78711
|
+
var COLUMN = /^(\s*\S.*?[^\s.!?]\s{2,})\S/;
|
|
78712
|
+
function wrapAligned(paragraph2, width) {
|
|
78713
|
+
const lines2 = wrap2(paragraph2, width);
|
|
78714
|
+
if (lines2.length < 2) return lines2;
|
|
78715
|
+
const plain2 = stripAnsi(paragraph2);
|
|
78716
|
+
const column = COLUMN.exec(plain2);
|
|
78717
|
+
const lead = column ? cellWidth(column[1]) : plain2.length - plain2.replace(/^ +/, "").length;
|
|
78718
|
+
if (!(lead > 0 && lead <= Math.floor(width / 2))) return lines2;
|
|
78719
|
+
const [head4, body] = splitAtCell(paragraph2, lead);
|
|
78720
|
+
const rest = wrap2(body, width - lead);
|
|
78721
|
+
return [head4 + rest[0], ...rest.slice(1).map((line) => " ".repeat(lead) + line)];
|
|
78722
|
+
}
|
|
78723
|
+
function splitAtCell(text3, cells) {
|
|
78724
|
+
const tokens = tokenize(text3);
|
|
78725
|
+
let seen = 0;
|
|
78726
|
+
for (const [index, [isEscape, chunk]] of tokens.entries()) {
|
|
78727
|
+
if (isEscape) continue;
|
|
78728
|
+
if (seen >= cells) {
|
|
78729
|
+
const head4 = tokens.slice(0, index);
|
|
78730
|
+
const style = head4.filter(([isEscape2]) => isEscape2).map(([, c]) => c).join("");
|
|
78731
|
+
return [
|
|
78732
|
+
head4.map(([, c]) => c).join(""),
|
|
78733
|
+
style + tokens.slice(index).map(([, c]) => c).join("")
|
|
78734
|
+
];
|
|
78735
|
+
}
|
|
78736
|
+
seen += cellWidth(chunk);
|
|
78737
|
+
}
|
|
78738
|
+
return [text3, ""];
|
|
78739
|
+
}
|
|
78740
|
+
var HangingText = class extends Widget {
|
|
78741
|
+
constructor(prefix, body, paddingX = 1, tint2 = null) {
|
|
78742
|
+
super();
|
|
78743
|
+
this.prefix = prefix;
|
|
78744
|
+
this.body = body;
|
|
78745
|
+
this.paddingX = paddingX;
|
|
78746
|
+
this.tint = tint2;
|
|
78747
|
+
this.prefixWidth = cellWidth(prefix);
|
|
78748
|
+
}
|
|
78749
|
+
prefix;
|
|
78750
|
+
body;
|
|
78751
|
+
paddingX;
|
|
78752
|
+
tint;
|
|
78753
|
+
prefixWidth;
|
|
78754
|
+
setBody(body) {
|
|
78755
|
+
if (body !== this.body) {
|
|
78756
|
+
this.body = body;
|
|
78757
|
+
this.invalidate();
|
|
78758
|
+
}
|
|
78759
|
+
}
|
|
78760
|
+
draw(width) {
|
|
78761
|
+
const pad = " ".repeat(this.paddingX);
|
|
78762
|
+
const inner = Math.max(1, width - 2 * this.paddingX - this.prefixWidth);
|
|
78763
|
+
const hang = " ".repeat(this.prefixWidth);
|
|
78764
|
+
const out = [];
|
|
78765
|
+
for (const paragraph2 of this.body.split("\n")) {
|
|
78766
|
+
wrapAligned(paragraph2, inner).forEach((line, index) => {
|
|
78767
|
+
out.push(`${pad}${out.length === 0 && index === 0 ? this.prefix : hang}${line}`);
|
|
78768
|
+
});
|
|
78769
|
+
}
|
|
78770
|
+
return out.map(
|
|
78771
|
+
(line) => this.tint === null ? fillLine(line, width) : terminate(this.tint(padTo(line, width)))
|
|
78772
|
+
);
|
|
78773
|
+
}
|
|
78774
|
+
};
|
|
78775
|
+
var Box = class extends Widget {
|
|
78776
|
+
constructor(paddingX = 1, paddingY = 1, tint2 = null, ...children) {
|
|
78777
|
+
super();
|
|
78778
|
+
this.paddingX = paddingX;
|
|
78779
|
+
this.paddingY = paddingY;
|
|
78780
|
+
this.tint = tint2;
|
|
78781
|
+
this.children = children;
|
|
78782
|
+
}
|
|
78783
|
+
paddingX;
|
|
78784
|
+
paddingY;
|
|
78785
|
+
tint;
|
|
78786
|
+
children;
|
|
78787
|
+
add(child) {
|
|
78788
|
+
this.children.push(child);
|
|
78789
|
+
this.invalidate();
|
|
78790
|
+
return child;
|
|
78791
|
+
}
|
|
78792
|
+
clear() {
|
|
78793
|
+
this.children = [];
|
|
78794
|
+
this.invalidate();
|
|
78795
|
+
}
|
|
78796
|
+
render(width) {
|
|
78797
|
+
const inner = Math.max(1, width - 2 * this.paddingX);
|
|
78798
|
+
const pad = " ".repeat(this.paddingX);
|
|
78799
|
+
const body = this.children.flatMap(
|
|
78800
|
+
(child) => child.render(inner).map((line) => `${pad}${stripTerminator(line)}`)
|
|
78801
|
+
);
|
|
78802
|
+
const blank = Array(this.paddingY).fill("");
|
|
78803
|
+
return [...blank, ...body, ...blank].map(
|
|
78804
|
+
(line) => this.tint === null ? fillLine(line, width) : terminate(this.tint(padTo(line, width)))
|
|
78805
|
+
);
|
|
78806
|
+
}
|
|
78807
|
+
draw(width) {
|
|
78808
|
+
return this.render(width);
|
|
78809
|
+
}
|
|
78810
|
+
};
|
|
78811
|
+
var Spacer = class extends Widget {
|
|
78812
|
+
constructor(lines2 = 1) {
|
|
78813
|
+
super();
|
|
78814
|
+
this.lines = lines2;
|
|
78815
|
+
}
|
|
78816
|
+
lines;
|
|
78817
|
+
draw() {
|
|
78818
|
+
return Array(this.lines).fill("");
|
|
78819
|
+
}
|
|
78820
|
+
};
|
|
78821
|
+
var Rule = class extends Widget {
|
|
78822
|
+
constructor(color2 = null, char = "\u2500") {
|
|
78823
|
+
super();
|
|
78824
|
+
this.color = color2;
|
|
78825
|
+
this.char = char;
|
|
78826
|
+
}
|
|
78827
|
+
color;
|
|
78828
|
+
char;
|
|
78829
|
+
/** What the rule is painted with. */
|
|
78830
|
+
get paint() {
|
|
78831
|
+
return this.color;
|
|
78832
|
+
}
|
|
78833
|
+
setColor(color2) {
|
|
78834
|
+
this.color = color2;
|
|
78835
|
+
this.invalidate();
|
|
78836
|
+
}
|
|
78837
|
+
draw(width) {
|
|
78838
|
+
const line = this.char.repeat(Math.max(1, width));
|
|
78839
|
+
return [terminate(this.color ? this.color(line) : line)];
|
|
78840
|
+
}
|
|
78841
|
+
};
|
|
78842
|
+
var LabelledRule = class extends Rule {
|
|
78843
|
+
constructor(color2 = null, label = "", align = "left", char = "\u2500") {
|
|
78844
|
+
super(color2, char);
|
|
78845
|
+
this.label = label;
|
|
78846
|
+
this.align = align;
|
|
78847
|
+
}
|
|
78848
|
+
label;
|
|
78849
|
+
align;
|
|
78850
|
+
setLabel(label) {
|
|
78851
|
+
if (label !== this.label) {
|
|
78852
|
+
this.label = label;
|
|
78853
|
+
this.invalidate();
|
|
78854
|
+
}
|
|
78855
|
+
}
|
|
78856
|
+
draw(width) {
|
|
78857
|
+
const w = Math.max(1, width);
|
|
78858
|
+
if (!this.label) return super.draw(w);
|
|
78859
|
+
const label = ` ${this.label} `;
|
|
78860
|
+
const labelWidth = cellWidth(label);
|
|
78861
|
+
if (labelWidth + 4 > w) return super.draw(w);
|
|
78862
|
+
const left = this.align === "center" ? Math.floor((w - labelWidth) / 2) : 2;
|
|
78863
|
+
const right = w - labelWidth - left;
|
|
78864
|
+
const paint = this.color ?? ((text3) => text3);
|
|
78865
|
+
return [terminate(paint(this.char.repeat(left)) + label + paint(this.char.repeat(right)))];
|
|
78866
|
+
}
|
|
78867
|
+
};
|
|
78868
|
+
var Lines = class extends Widget {
|
|
78869
|
+
constructor(lines2, paddingX = 1, tint2 = null) {
|
|
78870
|
+
super();
|
|
78871
|
+
this.lines = lines2;
|
|
78872
|
+
this.paddingX = paddingX;
|
|
78873
|
+
this.tint = tint2;
|
|
78874
|
+
}
|
|
78875
|
+
lines;
|
|
78876
|
+
paddingX;
|
|
78877
|
+
tint;
|
|
78878
|
+
setLines(lines2) {
|
|
78879
|
+
this.lines = lines2;
|
|
78880
|
+
this.invalidate();
|
|
78881
|
+
}
|
|
78882
|
+
draw(width) {
|
|
78883
|
+
const pad = " ".repeat(this.paddingX);
|
|
78884
|
+
const inner = Math.max(1, width - 2 * this.paddingX);
|
|
78885
|
+
return this.lines.map((line) => `${pad}${truncateToWidth(stripTerminator(line), inner)}`).map(
|
|
78886
|
+
(line) => this.tint === null ? fillLine(line, width) : terminate(this.tint(padTo(line, width)))
|
|
78887
|
+
);
|
|
78888
|
+
}
|
|
78889
|
+
};
|
|
78890
|
+
var stripTerminator = (line) => line.endsWith(SEGMENT_RESET) ? line.slice(0, -SEGMENT_RESET.length) : line;
|
|
78891
|
+
function padTo(line, width) {
|
|
78892
|
+
let fitted = line;
|
|
78893
|
+
let used2 = cellWidth(fitted);
|
|
78894
|
+
if (used2 > width) {
|
|
78895
|
+
fitted = truncateToWidth(fitted, width);
|
|
78896
|
+
used2 = cellWidth(fitted);
|
|
78897
|
+
}
|
|
78898
|
+
return fitted + " ".repeat(Math.max(0, width - used2));
|
|
78899
|
+
}
|
|
78900
|
+
|
|
78295
78901
|
// src/tui/clipboard.ts
|
|
78296
78902
|
init_images();
|
|
78297
78903
|
init_pathlib();
|
|
78298
78904
|
init_py();
|
|
78299
78905
|
import { spawn as spawn9 } from "node:child_process";
|
|
78300
78906
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
78301
|
-
import { readFileSync as readFileSync9, rmSync as rmSync5, statSync as
|
|
78907
|
+
import { readFileSync as readFileSync9, rmSync as rmSync5, statSync as statSync15, writeFileSync as writeFileSync11 } from "node:fs";
|
|
78302
78908
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
78303
78909
|
var MAX_OSC52_ENCODED = 1e5;
|
|
78304
78910
|
var TIMEOUT_MS = 5e3;
|
|
@@ -78403,7 +79009,7 @@ async function readImage2(platform = process.platform, env = process.env) {
|
|
|
78403
79009
|
}
|
|
78404
79010
|
async function withScratch(fn) {
|
|
78405
79011
|
const target = join(tmpdir3(), `hx-clipboard-${randomBytes8(4).toString("hex")}.img`);
|
|
78406
|
-
|
|
79012
|
+
writeFileSync11(target, "");
|
|
78407
79013
|
try {
|
|
78408
79014
|
return await fn(target);
|
|
78409
79015
|
} finally {
|
|
@@ -78465,7 +79071,7 @@ async function readLinux(env) {
|
|
|
78465
79071
|
function copiedFile(path) {
|
|
78466
79072
|
if (!isImagePath(path)) return null;
|
|
78467
79073
|
try {
|
|
78468
|
-
if (
|
|
79074
|
+
if (statSync15(path).size > MAX_SOURCE_BYTES)
|
|
78469
79075
|
throw new ClipboardError(`${basename(path)} is too large to attach`);
|
|
78470
79076
|
return { data: readFileSync9(path), name: basename(path) };
|
|
78471
79077
|
} catch (error) {
|
|
@@ -78481,7 +79087,7 @@ init_resolve();
|
|
|
78481
79087
|
init_store();
|
|
78482
79088
|
init_config();
|
|
78483
79089
|
init_context();
|
|
78484
|
-
import { statSync as
|
|
79090
|
+
import { statSync as statSync16 } from "node:fs";
|
|
78485
79091
|
init_usage();
|
|
78486
79092
|
init_json();
|
|
78487
79093
|
init_net();
|
|
@@ -78944,295 +79550,6 @@ var THEME = new Theme();
|
|
|
78944
79550
|
// src/tui/views/login.ts
|
|
78945
79551
|
init_py();
|
|
78946
79552
|
|
|
78947
|
-
// src/term/component.ts
|
|
78948
|
-
var Widget = class {
|
|
78949
|
-
cache = [];
|
|
78950
|
-
cacheWidth = -1;
|
|
78951
|
-
isDirty = true;
|
|
78952
|
-
render(width) {
|
|
78953
|
-
if (this.isDirty || width !== this.cacheWidth) {
|
|
78954
|
-
this.cache = this.draw(width);
|
|
78955
|
-
this.cacheWidth = width;
|
|
78956
|
-
this.isDirty = false;
|
|
78957
|
-
}
|
|
78958
|
-
return this.cache;
|
|
78959
|
-
}
|
|
78960
|
-
invalidate() {
|
|
78961
|
-
this.isDirty = true;
|
|
78962
|
-
}
|
|
78963
|
-
get dirty() {
|
|
78964
|
-
return this.isDirty;
|
|
78965
|
-
}
|
|
78966
|
-
};
|
|
78967
|
-
var Container = class extends Widget {
|
|
78968
|
-
children;
|
|
78969
|
-
constructor(...children) {
|
|
78970
|
-
super();
|
|
78971
|
-
this.children = children;
|
|
78972
|
-
}
|
|
78973
|
-
add(child) {
|
|
78974
|
-
this.children.push(child);
|
|
78975
|
-
this.invalidate();
|
|
78976
|
-
return child;
|
|
78977
|
-
}
|
|
78978
|
-
extend(children) {
|
|
78979
|
-
this.children.push(...children);
|
|
78980
|
-
this.invalidate();
|
|
78981
|
-
}
|
|
78982
|
-
clear() {
|
|
78983
|
-
this.children = [];
|
|
78984
|
-
this.invalidate();
|
|
78985
|
-
}
|
|
78986
|
-
remove(child) {
|
|
78987
|
-
const index = this.children.indexOf(child);
|
|
78988
|
-
if (index >= 0) {
|
|
78989
|
-
this.children.splice(index, 1);
|
|
78990
|
-
this.invalidate();
|
|
78991
|
-
}
|
|
78992
|
-
}
|
|
78993
|
-
/** Concatenate the children, asking each: a clean child returns its cache,
|
|
78994
|
-
* so a frame costs what actually changed. */
|
|
78995
|
-
render(width) {
|
|
78996
|
-
const lines2 = [];
|
|
78997
|
-
for (const child of this.children) lines2.push(...child.render(width));
|
|
78998
|
-
this.isDirty = false;
|
|
78999
|
-
return lines2;
|
|
79000
|
-
}
|
|
79001
|
-
draw(width) {
|
|
79002
|
-
return this.render(width);
|
|
79003
|
-
}
|
|
79004
|
-
/** Offer the key to each child until one takes it. */
|
|
79005
|
-
handleInput(key2, data) {
|
|
79006
|
-
for (const child of this.children) {
|
|
79007
|
-
const handler = child.handleInput;
|
|
79008
|
-
if (handler?.call(child, key2, data)) return true;
|
|
79009
|
-
}
|
|
79010
|
-
return false;
|
|
79011
|
-
}
|
|
79012
|
-
};
|
|
79013
|
-
|
|
79014
|
-
// src/term/primitives.ts
|
|
79015
|
-
var Text = class extends Widget {
|
|
79016
|
-
constructor(content = "", paddingX = 1, paddingY = 0, tint2 = null) {
|
|
79017
|
-
super();
|
|
79018
|
-
this.content = content;
|
|
79019
|
-
this.paddingX = paddingX;
|
|
79020
|
-
this.paddingY = paddingY;
|
|
79021
|
-
this.tint = tint2;
|
|
79022
|
-
}
|
|
79023
|
-
content;
|
|
79024
|
-
paddingX;
|
|
79025
|
-
paddingY;
|
|
79026
|
-
tint;
|
|
79027
|
-
get text() {
|
|
79028
|
-
return this.content;
|
|
79029
|
-
}
|
|
79030
|
-
setText(text3) {
|
|
79031
|
-
if (text3 !== this.content) {
|
|
79032
|
-
this.content = text3;
|
|
79033
|
-
this.invalidate();
|
|
79034
|
-
}
|
|
79035
|
-
}
|
|
79036
|
-
draw(width) {
|
|
79037
|
-
const inner = Math.max(1, width - 2 * this.paddingX);
|
|
79038
|
-
const pad = " ".repeat(this.paddingX);
|
|
79039
|
-
const body = wrap2(this.content, inner).map((line) => `${pad}${line}`);
|
|
79040
|
-
const blank = Array(this.paddingY).fill("");
|
|
79041
|
-
return [...blank, ...body, ...blank].map(
|
|
79042
|
-
(line) => this.tint === null ? fillLine(line, width) : terminate(this.tint(padTo(line, width)))
|
|
79043
|
-
);
|
|
79044
|
-
}
|
|
79045
|
-
};
|
|
79046
|
-
var COLUMN = /^(\s*\S.*?[^\s.!?]\s{2,})\S/;
|
|
79047
|
-
function wrapAligned(paragraph2, width) {
|
|
79048
|
-
const lines2 = wrap2(paragraph2, width);
|
|
79049
|
-
if (lines2.length < 2) return lines2;
|
|
79050
|
-
const plain2 = stripAnsi(paragraph2);
|
|
79051
|
-
const column = COLUMN.exec(plain2);
|
|
79052
|
-
const lead = column ? cellWidth(column[1]) : plain2.length - plain2.replace(/^ +/, "").length;
|
|
79053
|
-
if (!(lead > 0 && lead <= Math.floor(width / 2))) return lines2;
|
|
79054
|
-
const [head4, body] = splitAtCell(paragraph2, lead);
|
|
79055
|
-
const rest = wrap2(body, width - lead);
|
|
79056
|
-
return [head4 + rest[0], ...rest.slice(1).map((line) => " ".repeat(lead) + line)];
|
|
79057
|
-
}
|
|
79058
|
-
function splitAtCell(text3, cells) {
|
|
79059
|
-
const tokens = tokenize(text3);
|
|
79060
|
-
let seen = 0;
|
|
79061
|
-
for (const [index, [isEscape, chunk]] of tokens.entries()) {
|
|
79062
|
-
if (isEscape) continue;
|
|
79063
|
-
if (seen >= cells) {
|
|
79064
|
-
const head4 = tokens.slice(0, index);
|
|
79065
|
-
const style = head4.filter(([isEscape2]) => isEscape2).map(([, c]) => c).join("");
|
|
79066
|
-
return [
|
|
79067
|
-
head4.map(([, c]) => c).join(""),
|
|
79068
|
-
style + tokens.slice(index).map(([, c]) => c).join("")
|
|
79069
|
-
];
|
|
79070
|
-
}
|
|
79071
|
-
seen += cellWidth(chunk);
|
|
79072
|
-
}
|
|
79073
|
-
return [text3, ""];
|
|
79074
|
-
}
|
|
79075
|
-
var HangingText = class extends Widget {
|
|
79076
|
-
constructor(prefix, body, paddingX = 1, tint2 = null) {
|
|
79077
|
-
super();
|
|
79078
|
-
this.prefix = prefix;
|
|
79079
|
-
this.body = body;
|
|
79080
|
-
this.paddingX = paddingX;
|
|
79081
|
-
this.tint = tint2;
|
|
79082
|
-
this.prefixWidth = cellWidth(prefix);
|
|
79083
|
-
}
|
|
79084
|
-
prefix;
|
|
79085
|
-
body;
|
|
79086
|
-
paddingX;
|
|
79087
|
-
tint;
|
|
79088
|
-
prefixWidth;
|
|
79089
|
-
setBody(body) {
|
|
79090
|
-
if (body !== this.body) {
|
|
79091
|
-
this.body = body;
|
|
79092
|
-
this.invalidate();
|
|
79093
|
-
}
|
|
79094
|
-
}
|
|
79095
|
-
draw(width) {
|
|
79096
|
-
const pad = " ".repeat(this.paddingX);
|
|
79097
|
-
const inner = Math.max(1, width - 2 * this.paddingX - this.prefixWidth);
|
|
79098
|
-
const hang = " ".repeat(this.prefixWidth);
|
|
79099
|
-
const out = [];
|
|
79100
|
-
for (const paragraph2 of this.body.split("\n")) {
|
|
79101
|
-
wrapAligned(paragraph2, inner).forEach((line, index) => {
|
|
79102
|
-
out.push(`${pad}${out.length === 0 && index === 0 ? this.prefix : hang}${line}`);
|
|
79103
|
-
});
|
|
79104
|
-
}
|
|
79105
|
-
return out.map(
|
|
79106
|
-
(line) => this.tint === null ? fillLine(line, width) : terminate(this.tint(padTo(line, width)))
|
|
79107
|
-
);
|
|
79108
|
-
}
|
|
79109
|
-
};
|
|
79110
|
-
var Box = class extends Widget {
|
|
79111
|
-
constructor(paddingX = 1, paddingY = 1, tint2 = null, ...children) {
|
|
79112
|
-
super();
|
|
79113
|
-
this.paddingX = paddingX;
|
|
79114
|
-
this.paddingY = paddingY;
|
|
79115
|
-
this.tint = tint2;
|
|
79116
|
-
this.children = children;
|
|
79117
|
-
}
|
|
79118
|
-
paddingX;
|
|
79119
|
-
paddingY;
|
|
79120
|
-
tint;
|
|
79121
|
-
children;
|
|
79122
|
-
add(child) {
|
|
79123
|
-
this.children.push(child);
|
|
79124
|
-
this.invalidate();
|
|
79125
|
-
return child;
|
|
79126
|
-
}
|
|
79127
|
-
clear() {
|
|
79128
|
-
this.children = [];
|
|
79129
|
-
this.invalidate();
|
|
79130
|
-
}
|
|
79131
|
-
render(width) {
|
|
79132
|
-
const inner = Math.max(1, width - 2 * this.paddingX);
|
|
79133
|
-
const pad = " ".repeat(this.paddingX);
|
|
79134
|
-
const body = this.children.flatMap(
|
|
79135
|
-
(child) => child.render(inner).map((line) => `${pad}${stripTerminator(line)}`)
|
|
79136
|
-
);
|
|
79137
|
-
const blank = Array(this.paddingY).fill("");
|
|
79138
|
-
return [...blank, ...body, ...blank].map(
|
|
79139
|
-
(line) => this.tint === null ? fillLine(line, width) : terminate(this.tint(padTo(line, width)))
|
|
79140
|
-
);
|
|
79141
|
-
}
|
|
79142
|
-
draw(width) {
|
|
79143
|
-
return this.render(width);
|
|
79144
|
-
}
|
|
79145
|
-
};
|
|
79146
|
-
var Spacer = class extends Widget {
|
|
79147
|
-
constructor(lines2 = 1) {
|
|
79148
|
-
super();
|
|
79149
|
-
this.lines = lines2;
|
|
79150
|
-
}
|
|
79151
|
-
lines;
|
|
79152
|
-
draw() {
|
|
79153
|
-
return Array(this.lines).fill("");
|
|
79154
|
-
}
|
|
79155
|
-
};
|
|
79156
|
-
var Rule = class extends Widget {
|
|
79157
|
-
constructor(color2 = null, char = "\u2500") {
|
|
79158
|
-
super();
|
|
79159
|
-
this.color = color2;
|
|
79160
|
-
this.char = char;
|
|
79161
|
-
}
|
|
79162
|
-
color;
|
|
79163
|
-
char;
|
|
79164
|
-
/** What the rule is painted with. */
|
|
79165
|
-
get paint() {
|
|
79166
|
-
return this.color;
|
|
79167
|
-
}
|
|
79168
|
-
setColor(color2) {
|
|
79169
|
-
this.color = color2;
|
|
79170
|
-
this.invalidate();
|
|
79171
|
-
}
|
|
79172
|
-
draw(width) {
|
|
79173
|
-
const line = this.char.repeat(Math.max(1, width));
|
|
79174
|
-
return [terminate(this.color ? this.color(line) : line)];
|
|
79175
|
-
}
|
|
79176
|
-
};
|
|
79177
|
-
var LabelledRule = class extends Rule {
|
|
79178
|
-
constructor(color2 = null, label = "", align = "left", char = "\u2500") {
|
|
79179
|
-
super(color2, char);
|
|
79180
|
-
this.label = label;
|
|
79181
|
-
this.align = align;
|
|
79182
|
-
}
|
|
79183
|
-
label;
|
|
79184
|
-
align;
|
|
79185
|
-
setLabel(label) {
|
|
79186
|
-
if (label !== this.label) {
|
|
79187
|
-
this.label = label;
|
|
79188
|
-
this.invalidate();
|
|
79189
|
-
}
|
|
79190
|
-
}
|
|
79191
|
-
draw(width) {
|
|
79192
|
-
const w = Math.max(1, width);
|
|
79193
|
-
if (!this.label) return super.draw(w);
|
|
79194
|
-
const label = ` ${this.label} `;
|
|
79195
|
-
const labelWidth = cellWidth(label);
|
|
79196
|
-
if (labelWidth + 4 > w) return super.draw(w);
|
|
79197
|
-
const left = this.align === "center" ? Math.floor((w - labelWidth) / 2) : 2;
|
|
79198
|
-
const right = w - labelWidth - left;
|
|
79199
|
-
const paint = this.color ?? ((text3) => text3);
|
|
79200
|
-
return [terminate(paint(this.char.repeat(left)) + label + paint(this.char.repeat(right)))];
|
|
79201
|
-
}
|
|
79202
|
-
};
|
|
79203
|
-
var Lines = class extends Widget {
|
|
79204
|
-
constructor(lines2, paddingX = 1, tint2 = null) {
|
|
79205
|
-
super();
|
|
79206
|
-
this.lines = lines2;
|
|
79207
|
-
this.paddingX = paddingX;
|
|
79208
|
-
this.tint = tint2;
|
|
79209
|
-
}
|
|
79210
|
-
lines;
|
|
79211
|
-
paddingX;
|
|
79212
|
-
tint;
|
|
79213
|
-
setLines(lines2) {
|
|
79214
|
-
this.lines = lines2;
|
|
79215
|
-
this.invalidate();
|
|
79216
|
-
}
|
|
79217
|
-
draw(width) {
|
|
79218
|
-
const pad = " ".repeat(this.paddingX);
|
|
79219
|
-
const inner = Math.max(1, width - 2 * this.paddingX);
|
|
79220
|
-
return this.lines.map((line) => `${pad}${truncateToWidth(stripTerminator(line), inner)}`).map(
|
|
79221
|
-
(line) => this.tint === null ? fillLine(line, width) : terminate(this.tint(padTo(line, width)))
|
|
79222
|
-
);
|
|
79223
|
-
}
|
|
79224
|
-
};
|
|
79225
|
-
var stripTerminator = (line) => line.endsWith(SEGMENT_RESET) ? line.slice(0, -SEGMENT_RESET.length) : line;
|
|
79226
|
-
function padTo(line, width) {
|
|
79227
|
-
let fitted = line;
|
|
79228
|
-
let used2 = cellWidth(fitted);
|
|
79229
|
-
if (used2 > width) {
|
|
79230
|
-
fitted = truncateToWidth(fitted, width);
|
|
79231
|
-
used2 = cellWidth(fitted);
|
|
79232
|
-
}
|
|
79233
|
-
return fitted + " ".repeat(Math.max(0, width - used2));
|
|
79234
|
-
}
|
|
79235
|
-
|
|
79236
79553
|
// src/tui/format.ts
|
|
79237
79554
|
init_py();
|
|
79238
79555
|
|
|
@@ -86255,7 +86572,7 @@ async function cmdAgents(ctx, _args) {
|
|
|
86255
86572
|
}
|
|
86256
86573
|
async function cmdMcp(ctx, args) {
|
|
86257
86574
|
const manager = ctx.app.mcp;
|
|
86258
|
-
if (manager === null || !manager.configs.length) {
|
|
86575
|
+
if (manager === null || !manager.configs.length && !manager.waiting.length) {
|
|
86259
86576
|
ctx.app.notice("No MCP servers configured. Add one with `hx mcp add`.", "warning");
|
|
86260
86577
|
return;
|
|
86261
86578
|
}
|
|
@@ -86297,7 +86614,10 @@ async function cmdMcp(ctx, args) {
|
|
|
86297
86614
|
lines2.push(` ${ljust(status3.name, 20)} ${status3.error} - /mcp login ${status3.name}`);
|
|
86298
86615
|
else lines2.push(` ${ljust(status3.name, 20)} unavailable - ${status3.error}`);
|
|
86299
86616
|
}
|
|
86300
|
-
|
|
86617
|
+
for (const { config: config2 } of manager.waiting)
|
|
86618
|
+
lines2.push(` ${ljust(config2.name, 20)} not approved - run \`hx mcp approve ${config2.name}\``);
|
|
86619
|
+
const healthy = manager.status().every((s) => s.connected) && !manager.waiting.length;
|
|
86620
|
+
ctx.app.notice(lines2.join("\n"), healthy ? "info" : "warning");
|
|
86301
86621
|
}
|
|
86302
86622
|
function onlyServerNeedingLogin(manager) {
|
|
86303
86623
|
const waiting = manager.status().filter((s) => s.needsLogin);
|
|
@@ -86663,18 +86983,18 @@ async function cmdCopy(ctx, _args) {
|
|
|
86663
86983
|
}
|
|
86664
86984
|
async function cmdTrace(ctx, args) {
|
|
86665
86985
|
const [target, openAfter] = traceArgs(args);
|
|
86666
|
-
let
|
|
86986
|
+
let written2;
|
|
86667
86987
|
try {
|
|
86668
|
-
|
|
86988
|
+
written2 = writeSessionTrace(ctx, target);
|
|
86669
86989
|
} catch (error) {
|
|
86670
86990
|
if (!error.code) throw error;
|
|
86671
86991
|
ctx.app.notice(`Could not write the trace: ${osErrorText(error)}`, "error");
|
|
86672
86992
|
return;
|
|
86673
86993
|
}
|
|
86674
|
-
const opened = openAfter && await openInBrowser(
|
|
86994
|
+
const opened = openAfter && await openInBrowser(written2);
|
|
86675
86995
|
const trailer = openAfter && !opened ? " No browser could be launched here; open it yourself." : "";
|
|
86676
86996
|
ctx.app.notice(
|
|
86677
|
-
`Trace written to ${
|
|
86997
|
+
`Trace written to ${written2} (${formatBytes(statSync16(written2).size)}).${trailer}`,
|
|
86678
86998
|
"success"
|
|
86679
86999
|
);
|
|
86680
87000
|
}
|
|
@@ -87771,7 +88091,7 @@ var PermissionPrompt = class extends Framed {
|
|
|
87771
88091
|
|
|
87772
88092
|
// src/tui/views/prompt.ts
|
|
87773
88093
|
init_images();
|
|
87774
|
-
import { statSync as
|
|
88094
|
+
import { statSync as statSync17 } from "node:fs";
|
|
87775
88095
|
init_pathlib();
|
|
87776
88096
|
init_py();
|
|
87777
88097
|
|
|
@@ -88249,7 +88569,7 @@ var FileCompleter = class _FileCompleter {
|
|
|
88249
88569
|
}
|
|
88250
88570
|
const mtime3 = (path) => {
|
|
88251
88571
|
try {
|
|
88252
|
-
return
|
|
88572
|
+
return statSync17(path).mtimeMs;
|
|
88253
88573
|
} catch {
|
|
88254
88574
|
return 0;
|
|
88255
88575
|
}
|
|
@@ -89042,6 +89362,13 @@ var HintsBar = class extends Widget {
|
|
|
89042
89362
|
return [fillLine(` ${parts2.join(fg2("dim", SEPARATOR2))}`, width)];
|
|
89043
89363
|
}
|
|
89044
89364
|
};
|
|
89365
|
+
var LOGO = [
|
|
89366
|
+
" __ __ _ __",
|
|
89367
|
+
" / / / / | |/ /",
|
|
89368
|
+
" / /_/ / | /",
|
|
89369
|
+
" / __ / / |",
|
|
89370
|
+
"/_/ /_/ /_/|_|"
|
|
89371
|
+
];
|
|
89045
89372
|
var Header = class extends Widget {
|
|
89046
89373
|
constructor(version = "", quiet = false) {
|
|
89047
89374
|
super();
|
|
@@ -89051,6 +89378,7 @@ var Header = class extends Widget {
|
|
|
89051
89378
|
quiet;
|
|
89052
89379
|
versionText;
|
|
89053
89380
|
isExpanded = false;
|
|
89381
|
+
showLogo = false;
|
|
89054
89382
|
get version() {
|
|
89055
89383
|
return this.versionText;
|
|
89056
89384
|
}
|
|
@@ -89068,10 +89396,19 @@ var Header = class extends Widget {
|
|
|
89068
89396
|
toggle() {
|
|
89069
89397
|
this.expanded = !this.isExpanded;
|
|
89070
89398
|
}
|
|
89399
|
+
/** The logo above the title: on the first screen of someone not signed in. */
|
|
89400
|
+
get logo() {
|
|
89401
|
+
return this.showLogo;
|
|
89402
|
+
}
|
|
89403
|
+
set logo(value) {
|
|
89404
|
+
this.showLogo = value;
|
|
89405
|
+
this.invalidate();
|
|
89406
|
+
}
|
|
89071
89407
|
draw(width) {
|
|
89072
89408
|
if (this.quiet) return [];
|
|
89073
89409
|
const keys = keymap();
|
|
89074
|
-
const lines2 = [fg2("accent",
|
|
89410
|
+
const lines2 = this.showLogo ? [...LOGO.map((row) => fg2("accent", row, { bold: true })), ""] : [];
|
|
89411
|
+
lines2.push(fg2("accent", "hx", { bold: true }) + fg2("dim", ` v${this.versionText}`));
|
|
89075
89412
|
if (!this.isExpanded) {
|
|
89076
89413
|
const inner = Math.max(1, width - 2);
|
|
89077
89414
|
lines2.push(
|
|
@@ -89342,6 +89679,18 @@ var HXSession = class {
|
|
|
89342
89679
|
this.view.dock.hints.setHints(HINTS);
|
|
89343
89680
|
this.refreshStatus();
|
|
89344
89681
|
if (this.loop.session.activeMessages().length) this.replayTranscript();
|
|
89682
|
+
if (this.extra.signIn) this.showSignIn(this.extra.signIn);
|
|
89683
|
+
void this.extra.update?.then((outcome2) => {
|
|
89684
|
+
if (outcome2 === null) return;
|
|
89685
|
+
const level = outcome2.ok ? "success" : "warning";
|
|
89686
|
+
if (this.view.transcript.blocks.length) {
|
|
89687
|
+
this.notice(outcome2.message, level);
|
|
89688
|
+
return;
|
|
89689
|
+
}
|
|
89690
|
+
this.view.transcript.add(new Spacer(1));
|
|
89691
|
+
this.view.transcript.add(new Notice(outcome2.message, level));
|
|
89692
|
+
this.runner.requestRender();
|
|
89693
|
+
});
|
|
89345
89694
|
if (this.loop.permissions !== null)
|
|
89346
89695
|
this.loop.permissions.asker = (request2) => this.askPermission(request2);
|
|
89347
89696
|
const events = this.consumeEvents();
|
|
@@ -89385,6 +89734,13 @@ var HXSession = class {
|
|
|
89385
89734
|
this.runner.requestRender();
|
|
89386
89735
|
}
|
|
89387
89736
|
}
|
|
89737
|
+
/** Say how to sign in, a row below the header, under the logo - unless a
|
|
89738
|
+
* resumed conversation is already on screen. */
|
|
89739
|
+
showSignIn(hint2) {
|
|
89740
|
+
if (!this.view.transcript.blocks.length) this.view.header.logo = true;
|
|
89741
|
+
this.view.transcript.add(new Spacer(1));
|
|
89742
|
+
this.view.transcript.add(new Notice(hint2, "warning"));
|
|
89743
|
+
}
|
|
89388
89744
|
// -- the surface the slash commands expect ---------------------------------
|
|
89389
89745
|
get status() {
|
|
89390
89746
|
return this.view.dock.status;
|
|
@@ -90337,6 +90693,145 @@ async function runSession(loop, bus, settings, extra = {}) {
|
|
|
90337
90693
|
return session.relaunch;
|
|
90338
90694
|
}
|
|
90339
90695
|
|
|
90696
|
+
// src/update.ts
|
|
90697
|
+
init_json();
|
|
90698
|
+
init_net();
|
|
90699
|
+
init_pathlib();
|
|
90700
|
+
init_paths();
|
|
90701
|
+
init_py();
|
|
90702
|
+
import { spawn as spawn10 } from "node:child_process";
|
|
90703
|
+
import { mkdirSync as mkdirSync15, openSync as openSync10, readFileSync as readFileSync10, realpathSync as realpathSync3, writeFileSync as writeFileSync12 } from "node:fs";
|
|
90704
|
+
init_which();
|
|
90705
|
+
var RELEASES_URL2 = `https://registry.npmjs.org/-/package/${PACKAGE}/dist-tags`;
|
|
90706
|
+
var RELEASES_ENV2 = "HX_RELEASES_URL";
|
|
90707
|
+
var DISABLE_ENV = "HX_DISABLE_AUTOUPDATER";
|
|
90708
|
+
var RELEASES_TIMEOUT2 = 5;
|
|
90709
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
90710
|
+
var stateFile = () => join(userHome(), "update.json");
|
|
90711
|
+
async function latestRelease(signal) {
|
|
90712
|
+
const url = strip(process.env[RELEASES_ENV2] ?? "") || RELEASES_URL2;
|
|
90713
|
+
try {
|
|
90714
|
+
const response = await request(url, {
|
|
90715
|
+
timeout: RELEASES_TIMEOUT2,
|
|
90716
|
+
...signal ? { signal } : {}
|
|
90717
|
+
});
|
|
90718
|
+
try {
|
|
90719
|
+
if (response.status < 200 || response.status >= 300) return null;
|
|
90720
|
+
const tags = await response.json();
|
|
90721
|
+
const latest = typeof tags === "object" && tags !== null ? tags.latest : null;
|
|
90722
|
+
return typeof latest === "string" && RELEASE2.test(latest) ? latest : null;
|
|
90723
|
+
} finally {
|
|
90724
|
+
response.close();
|
|
90725
|
+
}
|
|
90726
|
+
} catch {
|
|
90727
|
+
return null;
|
|
90728
|
+
}
|
|
90729
|
+
}
|
|
90730
|
+
var RELEASE2 = /^\d+\.\d+\.\d+$/;
|
|
90731
|
+
function compareReleases(a, b) {
|
|
90732
|
+
const left = a.split(".").map(Number);
|
|
90733
|
+
const right = b.split(".").map(Number);
|
|
90734
|
+
for (let index = 0; index < 3; index++) {
|
|
90735
|
+
const difference = (left[index] ?? 0) - (right[index] ?? 0);
|
|
90736
|
+
if (difference) return difference;
|
|
90737
|
+
}
|
|
90738
|
+
return 0;
|
|
90739
|
+
}
|
|
90740
|
+
function notAnNpmInstall() {
|
|
90741
|
+
const script = process.argv[1] ?? "";
|
|
90742
|
+
let real2;
|
|
90743
|
+
try {
|
|
90744
|
+
real2 = realpathSync3(script);
|
|
90745
|
+
} catch {
|
|
90746
|
+
return script;
|
|
90747
|
+
}
|
|
90748
|
+
return real2.includes(`/node_modules/${PACKAGE}/`) ? null : parent(parent(real2));
|
|
90749
|
+
}
|
|
90750
|
+
function npm() {
|
|
90751
|
+
return which("npm") ?? join(parent(process.execPath), "npm");
|
|
90752
|
+
}
|
|
90753
|
+
var INSTALL_COMMAND = `npm install --global ${PACKAGE}`;
|
|
90754
|
+
function runNpm(version, background) {
|
|
90755
|
+
const args = ["install", "--global", `${PACKAGE}@${version}`];
|
|
90756
|
+
let stdio = "inherit";
|
|
90757
|
+
if (background) {
|
|
90758
|
+
mkdirSync15(logsDir(), { recursive: true });
|
|
90759
|
+
const log = openSync10(join(logsDir(), "update.log"), "a");
|
|
90760
|
+
stdio = ["ignore", log, log];
|
|
90761
|
+
}
|
|
90762
|
+
return new Promise((resolve2) => {
|
|
90763
|
+
const child = spawn10(npm(), args, { stdio, detached: background });
|
|
90764
|
+
child.once("error", () => resolve2(null));
|
|
90765
|
+
child.once("exit", (code2, signal) => resolve2(code2 ?? (signal ? 1 : 0)));
|
|
90766
|
+
if (background) child.unref();
|
|
90767
|
+
});
|
|
90768
|
+
}
|
|
90769
|
+
async function runUpdate(print2) {
|
|
90770
|
+
const elsewhere2 = notAnNpmInstall();
|
|
90771
|
+
if (elsewhere2 !== null) {
|
|
90772
|
+
print2(
|
|
90773
|
+
`hx is running from ${elsewhere2}, not from an npm install, so it cannot update itself. Update a checkout with git pull and npm run build, or install the release: ${INSTALL_COMMAND}`,
|
|
90774
|
+
process.stderr
|
|
90775
|
+
);
|
|
90776
|
+
return 1;
|
|
90777
|
+
}
|
|
90778
|
+
const latest = await latestRelease();
|
|
90779
|
+
if (latest === null) {
|
|
90780
|
+
print2("Could not reach the npm registry to check for a newer hx.", process.stderr);
|
|
90781
|
+
return 1;
|
|
90782
|
+
}
|
|
90783
|
+
if (compareReleases(latest, VERSION) <= 0) {
|
|
90784
|
+
print2(`hx ${VERSION} is up to date.`);
|
|
90785
|
+
return 0;
|
|
90786
|
+
}
|
|
90787
|
+
print2(`Updating hx ${VERSION} to ${latest}...`);
|
|
90788
|
+
const status3 = await runNpm(latest, false);
|
|
90789
|
+
if (status3 === null) {
|
|
90790
|
+
print2(
|
|
90791
|
+
`npm is not installed, so hx cannot update itself. See https://nodejs.org/`,
|
|
90792
|
+
process.stderr
|
|
90793
|
+
);
|
|
90794
|
+
return 1;
|
|
90795
|
+
}
|
|
90796
|
+
if (status3 !== 0) {
|
|
90797
|
+
print2(`npm could not install ${PACKAGE}@${latest} (exit ${status3}).`, process.stderr);
|
|
90798
|
+
return 1;
|
|
90799
|
+
}
|
|
90800
|
+
print2(`Updated hx to ${latest}.`);
|
|
90801
|
+
return 0;
|
|
90802
|
+
}
|
|
90803
|
+
function lastChecked() {
|
|
90804
|
+
try {
|
|
90805
|
+
const state = JSON.parse(readFileSync10(stateFile(), "utf8"));
|
|
90806
|
+
const at = typeof state === "object" && state !== null ? state.checkedAt : 0;
|
|
90807
|
+
return typeof at === "number" ? at : 0;
|
|
90808
|
+
} catch {
|
|
90809
|
+
return 0;
|
|
90810
|
+
}
|
|
90811
|
+
}
|
|
90812
|
+
async function autoUpdate(enabled, signal) {
|
|
90813
|
+
const disabled = strip(process.env[DISABLE_ENV] ?? "").toLowerCase();
|
|
90814
|
+
if (!enabled || ["1", "true", "yes"].includes(disabled)) return null;
|
|
90815
|
+
if (notAnNpmInstall() !== null) return null;
|
|
90816
|
+
const now3 = Date.now();
|
|
90817
|
+
if (now3 - lastChecked() < CHECK_INTERVAL_MS) return null;
|
|
90818
|
+
try {
|
|
90819
|
+
writeFileSync12(stateFile(), `${dumps({ checkedAt: now3 })}
|
|
90820
|
+
`);
|
|
90821
|
+
} catch {
|
|
90822
|
+
return null;
|
|
90823
|
+
}
|
|
90824
|
+
const latest = await latestRelease(signal);
|
|
90825
|
+
if (signal.aborted || latest === null || compareReleases(latest, VERSION) <= 0) return null;
|
|
90826
|
+
const status3 = await runNpm(latest, true);
|
|
90827
|
+
if (status3 === 0) return { message: `Updated hx to ${latest}. Restart hx to use it.`, ok: true };
|
|
90828
|
+
const why = status3 === null ? "npm could not be started" : `npm exited ${status3}`;
|
|
90829
|
+
return {
|
|
90830
|
+
message: `Could not update hx to ${latest}: ${why}. Run hx update to see why.`,
|
|
90831
|
+
ok: false
|
|
90832
|
+
};
|
|
90833
|
+
}
|
|
90834
|
+
|
|
90340
90835
|
// src/cli.ts
|
|
90341
90836
|
var USAGE = `hx - an agent harness for the terminal
|
|
90342
90837
|
|
|
@@ -90352,7 +90847,7 @@ Usage:
|
|
|
90352
90847
|
hx auth [set|clear] Show or change the OpenRouter API key
|
|
90353
90848
|
hx docs [SECTION|--all] Print the manual, or one section of it
|
|
90354
90849
|
hx changelog [VERSION] Print what shipped in each version
|
|
90355
|
-
hx
|
|
90850
|
+
hx update Update hx to the newest release
|
|
90356
90851
|
hx --version Show version
|
|
90357
90852
|
hx --help Show this message
|
|
90358
90853
|
|
|
@@ -90416,25 +90911,13 @@ ${USAGE}`, process.stderr);
|
|
|
90416
90911
|
return runMcpCommand(parsed.rest);
|
|
90417
90912
|
case "trace":
|
|
90418
90913
|
return runTraceCommand(parsed);
|
|
90914
|
+
case "update":
|
|
90419
90915
|
case "upgrade":
|
|
90420
|
-
return
|
|
90916
|
+
return runUpdate(print);
|
|
90421
90917
|
default:
|
|
90422
90918
|
return runTuiCommand(parsed);
|
|
90423
90919
|
}
|
|
90424
90920
|
}
|
|
90425
|
-
function runUpgradeCommand() {
|
|
90426
|
-
const channel = VERSION.includes("-") ? "next" : "latest";
|
|
90427
|
-
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
90428
|
-
const result2 = spawnSync5(npm, ["install", "--global", `${PACKAGE}@${channel}`], {
|
|
90429
|
-
stdio: "inherit"
|
|
90430
|
-
});
|
|
90431
|
-
if (result2.error?.code === "ENOENT") {
|
|
90432
|
-
print("npm is not installed. See https://nodejs.org/", process.stderr);
|
|
90433
|
-
return 1;
|
|
90434
|
-
}
|
|
90435
|
-
if (result2.error) throw result2.error;
|
|
90436
|
-
return result2.status ?? 1;
|
|
90437
|
-
}
|
|
90438
90921
|
function parseArgs(args) {
|
|
90439
90922
|
const parsed = {
|
|
90440
90923
|
command: "tui",
|
|
@@ -90489,7 +90972,7 @@ function parseArgs(args) {
|
|
|
90489
90972
|
}
|
|
90490
90973
|
if (positional.length) {
|
|
90491
90974
|
const [head4, ...tail3] = positional;
|
|
90492
|
-
if (["resume", "prompt", "trace", "mcp", "
|
|
90975
|
+
if (["resume", "prompt", "trace", "mcp", "auth", "update", "upgrade"].includes(head4)) {
|
|
90493
90976
|
parsed.command = head4;
|
|
90494
90977
|
parsed.rest = tail3;
|
|
90495
90978
|
if (head4 === "resume" && tail3.length) parsed.sessionId = tail3[0];
|
|
@@ -90952,25 +91435,28 @@ function runTraceCommand(parsed) {
|
|
|
90952
91435
|
destination = purePath(expandUser(target));
|
|
90953
91436
|
if (isDir(destination)) destination = join(destination, TRACE_FILENAME);
|
|
90954
91437
|
}
|
|
90955
|
-
let
|
|
91438
|
+
let written2;
|
|
90956
91439
|
try {
|
|
90957
|
-
|
|
91440
|
+
written2 = writeTrace(buildTrace(session), destination);
|
|
90958
91441
|
} catch (error) {
|
|
90959
91442
|
if (!error.code) throw error;
|
|
90960
91443
|
print(`error: could not write the trace: ${osErrorText(error)}`, process.stderr);
|
|
90961
91444
|
return 1;
|
|
90962
91445
|
}
|
|
90963
|
-
print(
|
|
91446
|
+
print(written2);
|
|
90964
91447
|
return 0;
|
|
90965
91448
|
}
|
|
90966
91449
|
var MCP_USAGE = `hx mcp list Show configured servers
|
|
90967
91450
|
hx mcp add NAME COMMAND [ARGS...] Add a stdio server
|
|
90968
91451
|
hx mcp add NAME --url URL [--header K:V]... Add an HTTP server
|
|
91452
|
+
hx mcp approve NAME Let a server from the project's .hx/mcp.json start
|
|
90969
91453
|
hx mcp login NAME Sign in to an HTTP server in the browser
|
|
90970
91454
|
hx mcp logout NAME Forget a server's sign-in
|
|
90971
91455
|
hx mcp remove NAME Remove a server
|
|
90972
91456
|
|
|
90973
91457
|
Add --user to write to ~/.hx/mcp.json instead of the project's .hx/mcp.json.
|
|
91458
|
+
A server in the project's .hx/mcp.json came with the project, so it starts only
|
|
91459
|
+
once approved: \`hx mcp add\` approves what it adds, and \`hx mcp approve\` the rest.
|
|
90974
91460
|
An HTTP server that asks for sign-in gets it over OAuth unless a --header
|
|
90975
91461
|
already sends Authorization.
|
|
90976
91462
|
`;
|
|
@@ -90989,7 +91475,14 @@ async function runMcpCommand(given) {
|
|
|
90989
91475
|
print(MCP_USAGE);
|
|
90990
91476
|
return 0;
|
|
90991
91477
|
}
|
|
90992
|
-
|
|
91478
|
+
const [startable, waiting] = partition(configs, cwd);
|
|
91479
|
+
for (const { config: config2, changed } of waiting) {
|
|
91480
|
+
const why = changed ? "changed since approved" : "not approved";
|
|
91481
|
+
print(
|
|
91482
|
+
`${ljust2(config2.name, 20)} ${why} - runs ${describeServer(config2)}; run \`hx mcp approve ${config2.name}\``
|
|
91483
|
+
);
|
|
91484
|
+
}
|
|
91485
|
+
for (const status3 of await probeServers(startable)) {
|
|
90993
91486
|
if (status3.needsLogin) {
|
|
90994
91487
|
print(`${ljust2(status3.name, 20)} ${status3.error} - run \`hx mcp login ${status3.name}\``);
|
|
90995
91488
|
continue;
|
|
@@ -91029,9 +91522,10 @@ ${MCP_USAGE}`, process.stderr);
|
|
|
91029
91522
|
});
|
|
91030
91523
|
}
|
|
91031
91524
|
print(`Added ${name} to ${saveConfig(config2, cwd, userLevel)}`);
|
|
91525
|
+
if (!userLevel) approve({ ...config2, source: "project" }, cwd);
|
|
91032
91526
|
return 0;
|
|
91033
91527
|
}
|
|
91034
|
-
if (args[0] === "
|
|
91528
|
+
if (args[0] === "approve") {
|
|
91035
91529
|
if (args.length < 2) {
|
|
91036
91530
|
print(MCP_USAGE, process.stderr);
|
|
91037
91531
|
return 2;
|
|
@@ -91041,6 +91535,33 @@ ${MCP_USAGE}`, process.stderr);
|
|
|
91041
91535
|
print(`No server named ${args[1]}.`, process.stderr);
|
|
91042
91536
|
return 1;
|
|
91043
91537
|
}
|
|
91538
|
+
if (server.source === "user") {
|
|
91539
|
+
print(`${server.name} is in your own ~/.hx/mcp.json; it needs no approval.`);
|
|
91540
|
+
return 0;
|
|
91541
|
+
}
|
|
91542
|
+
approve(server, cwd);
|
|
91543
|
+
const does = server.url ? "connects to" : "runs";
|
|
91544
|
+
print(`Approved ${server.name}. It ${does}: ${describeServer(server)}`);
|
|
91545
|
+
return 0;
|
|
91546
|
+
}
|
|
91547
|
+
if (args[0] === "login" || args[0] === "logout") {
|
|
91548
|
+
if (args.length < 2) {
|
|
91549
|
+
print(MCP_USAGE, process.stderr);
|
|
91550
|
+
return 2;
|
|
91551
|
+
}
|
|
91552
|
+
const configs = loadConfigs(cwd);
|
|
91553
|
+
const server = configs.find((c) => c.name === args[1]);
|
|
91554
|
+
if (server === void 0) {
|
|
91555
|
+
print(`No server named ${args[1]}.`, process.stderr);
|
|
91556
|
+
return 1;
|
|
91557
|
+
}
|
|
91558
|
+
if (args[0] === "login" && partition([server], cwd)[1].length) {
|
|
91559
|
+
print(
|
|
91560
|
+
`${server.name} has not been approved. Run \`hx mcp approve ${server.name}\` first.`,
|
|
91561
|
+
process.stderr
|
|
91562
|
+
);
|
|
91563
|
+
return 1;
|
|
91564
|
+
}
|
|
91044
91565
|
return args[0] === "login" ? mcpLogin2(server) : mcpLogout(server);
|
|
91045
91566
|
}
|
|
91046
91567
|
if (args[0] === "remove") {
|
|
@@ -91049,6 +91570,7 @@ ${MCP_USAGE}`, process.stderr);
|
|
|
91049
91570
|
return 2;
|
|
91050
91571
|
}
|
|
91051
91572
|
if (removeConfig(args[1], cwd, userLevel)) {
|
|
91573
|
+
if (!userLevel) revoke(args[1], cwd);
|
|
91052
91574
|
print(`Removed ${args[1]}.`);
|
|
91053
91575
|
return 0;
|
|
91054
91576
|
}
|
|
@@ -91132,19 +91654,16 @@ async function runTuiCommand(parsed) {
|
|
|
91132
91654
|
try {
|
|
91133
91655
|
resume = resumeTarget(parsed);
|
|
91134
91656
|
moved = adoptSessionDirectory(parsed, resume);
|
|
91135
|
-
runtime2 = buildRuntime(parsed, resume);
|
|
91657
|
+
runtime2 = buildRuntime(parsed, resume, true);
|
|
91136
91658
|
} catch (error) {
|
|
91137
|
-
|
|
91138
|
-
if (await runLogin(error.providerId) !== 0) return report(error);
|
|
91139
|
-
try {
|
|
91140
|
-
runtime2 = buildRuntime(parsed, resume);
|
|
91141
|
-
} catch (retry) {
|
|
91142
|
-
return report(retry);
|
|
91143
|
-
}
|
|
91659
|
+
return report(error);
|
|
91144
91660
|
}
|
|
91145
91661
|
if (moved) runtime2.notices.push(moved);
|
|
91662
|
+
const signIn2 = runtime2.provider instanceof DeferredProvider ? signInHint(runtime2.loop.model, runtime2.auth) : null;
|
|
91146
91663
|
let relaunch = null;
|
|
91664
|
+
const sessionOver = new AbortController();
|
|
91147
91665
|
try {
|
|
91666
|
+
const update = autoUpdate(runtime2.settings.autoUpdates, sessionOver.signal);
|
|
91148
91667
|
await runtime2.connectMcp();
|
|
91149
91668
|
await runtime2.refreshModelsIfStale();
|
|
91150
91669
|
relaunch = await runSession(runtime2.loop, runtime2.bus, runtime2.settings, {
|
|
@@ -91157,9 +91676,12 @@ async function runTuiCommand(parsed) {
|
|
|
91157
91676
|
mcp: runtime2.mcp,
|
|
91158
91677
|
notices: runtime2.notices,
|
|
91159
91678
|
checkpoints: runtime2.checkpoints,
|
|
91160
|
-
renameLeft
|
|
91679
|
+
renameLeft,
|
|
91680
|
+
signIn: signIn2,
|
|
91681
|
+
update
|
|
91161
91682
|
});
|
|
91162
91683
|
} finally {
|
|
91684
|
+
sessionOver.abort();
|
|
91163
91685
|
runtime2.bus.close();
|
|
91164
91686
|
await runtime2.aclose();
|
|
91165
91687
|
}
|