@sunilaleti/hx 0.3.0-beta.1 → 1.0.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 +51 -1
- package/README.md +105 -81
- package/dist/hx.js +959 -597
- 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
|
}
|
|
@@ -16962,7 +16969,7 @@ var require_abnf = __commonJS({
|
|
|
16962
16969
|
scope: "attribute",
|
|
16963
16970
|
match: regex.concat(IDENT, /(?=\s*=)/)
|
|
16964
16971
|
};
|
|
16965
|
-
const
|
|
16972
|
+
const ASSIGNMENT2 = {
|
|
16966
16973
|
scope: "operator",
|
|
16967
16974
|
match: /=\/?/
|
|
16968
16975
|
};
|
|
@@ -16971,7 +16978,7 @@ var require_abnf = __commonJS({
|
|
|
16971
16978
|
illegal: /[!@#$^&',?+~`|:]/,
|
|
16972
16979
|
keywords: KEYWORDS,
|
|
16973
16980
|
contains: [
|
|
16974
|
-
|
|
16981
|
+
ASSIGNMENT2,
|
|
16975
16982
|
RULE_DECLARATION,
|
|
16976
16983
|
COMMENT,
|
|
16977
16984
|
TERMINAL_BINARY,
|
|
@@ -30310,7 +30317,7 @@ var require_gams = __commonJS({
|
|
|
30310
30317
|
illegal: "\\n",
|
|
30311
30318
|
contains: [hljs.BACKSLASH_ESCAPE]
|
|
30312
30319
|
};
|
|
30313
|
-
const
|
|
30320
|
+
const ASSIGNMENT2 = {
|
|
30314
30321
|
begin: "/",
|
|
30315
30322
|
end: "/",
|
|
30316
30323
|
keywords: KEYWORDS,
|
|
@@ -30332,7 +30339,7 @@ var require_gams = __commonJS({
|
|
|
30332
30339
|
endsWithParent: true,
|
|
30333
30340
|
contains: [
|
|
30334
30341
|
QSTR,
|
|
30335
|
-
|
|
30342
|
+
ASSIGNMENT2,
|
|
30336
30343
|
{
|
|
30337
30344
|
className: "comment",
|
|
30338
30345
|
// one comment word, then possibly more
|
|
@@ -30379,7 +30386,7 @@ var require_gams = __commonJS({
|
|
|
30379
30386
|
hljs.C_BLOCK_COMMENT_MODE,
|
|
30380
30387
|
hljs.QUOTE_STRING_MODE,
|
|
30381
30388
|
hljs.APOS_STRING_MODE,
|
|
30382
|
-
|
|
30389
|
+
ASSIGNMENT2,
|
|
30383
30390
|
DESCTEXT
|
|
30384
30391
|
]
|
|
30385
30392
|
},
|
|
@@ -40434,7 +40441,7 @@ var require_makefile = __commonJS({
|
|
|
40434
40441
|
// Added QUOTE_STRING as they can be a part of functions
|
|
40435
40442
|
]
|
|
40436
40443
|
};
|
|
40437
|
-
const
|
|
40444
|
+
const ASSIGNMENT2 = { begin: "^" + hljs.UNDERSCORE_IDENT_RE + "\\s*(?=[:+?]?=)" };
|
|
40438
40445
|
const META = {
|
|
40439
40446
|
className: "meta",
|
|
40440
40447
|
begin: /^\.PHONY:/,
|
|
@@ -40466,7 +40473,7 @@ var require_makefile = __commonJS({
|
|
|
40466
40473
|
VARIABLE,
|
|
40467
40474
|
QUOTE_STRING,
|
|
40468
40475
|
FUNC,
|
|
40469
|
-
|
|
40476
|
+
ASSIGNMENT2,
|
|
40470
40477
|
META,
|
|
40471
40478
|
TARGET
|
|
40472
40479
|
]
|
|
@@ -67725,8 +67732,7 @@ init_net();
|
|
|
67725
67732
|
init_py();
|
|
67726
67733
|
|
|
67727
67734
|
// src/version.ts
|
|
67728
|
-
var VERSION = "0.
|
|
67729
|
-
var PACKAGE = "@sunilaleti/hx";
|
|
67735
|
+
var VERSION = "1.0.0";
|
|
67730
67736
|
|
|
67731
67737
|
// src/providers/codex.ts
|
|
67732
67738
|
init_base();
|
|
@@ -68165,12 +68171,12 @@ init_images();
|
|
|
68165
68171
|
|
|
68166
68172
|
// src/core/session.ts
|
|
68167
68173
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
68168
|
-
import { appendFileSync, mkdirSync as mkdirSync7, statSync as
|
|
68174
|
+
import { appendFileSync, mkdirSync as mkdirSync7, statSync as statSync9, writeFileSync as writeFileSync4 } from "node:fs";
|
|
68169
68175
|
|
|
68170
68176
|
// src/glob.ts
|
|
68171
68177
|
init_pathlib();
|
|
68172
68178
|
init_py();
|
|
68173
|
-
import { opendirSync, statSync as
|
|
68179
|
+
import { opendirSync, statSync as statSync7 } from "node:fs";
|
|
68174
68180
|
function translate(pattern) {
|
|
68175
68181
|
let out = "";
|
|
68176
68182
|
const chars = [...pattern];
|
|
@@ -68222,7 +68228,7 @@ function isDirEntry(directory, entry) {
|
|
|
68222
68228
|
if (entry.isDirectory()) return true;
|
|
68223
68229
|
if (!entry.isSymbolicLink()) return false;
|
|
68224
68230
|
try {
|
|
68225
|
-
return
|
|
68231
|
+
return statSync7(join(directory, entry.name)).isDirectory();
|
|
68226
68232
|
} catch {
|
|
68227
68233
|
return false;
|
|
68228
68234
|
}
|
|
@@ -68259,7 +68265,7 @@ function select(path, rest, out) {
|
|
|
68259
68265
|
const child = join(path, part);
|
|
68260
68266
|
if (tail3.length === 0) {
|
|
68261
68267
|
try {
|
|
68262
|
-
|
|
68268
|
+
statSync7(child);
|
|
68263
68269
|
out.push(child);
|
|
68264
68270
|
} catch {
|
|
68265
68271
|
}
|
|
@@ -68477,7 +68483,7 @@ function digestOf(path) {
|
|
|
68477
68483
|
init_messages();
|
|
68478
68484
|
|
|
68479
68485
|
// src/core/reads.ts
|
|
68480
|
-
import { statSync as
|
|
68486
|
+
import { statSync as statSync8 } from "node:fs";
|
|
68481
68487
|
var fileReadToDict = (read2) => ({
|
|
68482
68488
|
index: read2.index,
|
|
68483
68489
|
path: read2.path,
|
|
@@ -68516,7 +68522,7 @@ var STAT_DIGESTS = /* @__PURE__ */ new Map();
|
|
|
68516
68522
|
function cachedDigest(key2) {
|
|
68517
68523
|
let stamp;
|
|
68518
68524
|
try {
|
|
68519
|
-
const info =
|
|
68525
|
+
const info = statSync8(key2, { bigint: true });
|
|
68520
68526
|
stamp = `${info.ino}:${info.size}:${info.mtimeNs}`;
|
|
68521
68527
|
} catch {
|
|
68522
68528
|
STAT_DIGESTS.delete(key2);
|
|
@@ -68908,7 +68914,7 @@ var BACKFILL_MAX_BYTES = 4e6;
|
|
|
68908
68914
|
function backfilled(meta) {
|
|
68909
68915
|
if (meta.promptCount || meta.messageCount <= 0) return meta;
|
|
68910
68916
|
try {
|
|
68911
|
-
if (
|
|
68917
|
+
if (statSync9(sessionTranscriptFile(meta.sessionId)).size > BACKFILL_MAX_BYTES) return meta;
|
|
68912
68918
|
} catch {
|
|
68913
68919
|
return meta;
|
|
68914
68920
|
}
|
|
@@ -69034,6 +69040,75 @@ function changelog(version = null) {
|
|
|
69034
69040
|
);
|
|
69035
69041
|
}
|
|
69036
69042
|
|
|
69043
|
+
// src/mcp/approval.ts
|
|
69044
|
+
init_json();
|
|
69045
|
+
init_pathlib();
|
|
69046
|
+
init_paths();
|
|
69047
|
+
init_shlex();
|
|
69048
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
69049
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "node:fs";
|
|
69050
|
+
var approvalsFile = (cwd) => join(projectStateDir(cwd), "mcp-approved.json");
|
|
69051
|
+
function fingerprint2(config2) {
|
|
69052
|
+
const fields = {
|
|
69053
|
+
transport: config2.transport,
|
|
69054
|
+
command: config2.command,
|
|
69055
|
+
args: config2.args,
|
|
69056
|
+
env: config2.env,
|
|
69057
|
+
url: config2.url,
|
|
69058
|
+
headers: config2.headers
|
|
69059
|
+
};
|
|
69060
|
+
return createHash8("sha256").update(dumps(fields, { sortKeys: true })).digest("hex");
|
|
69061
|
+
}
|
|
69062
|
+
function readApprovals(cwd) {
|
|
69063
|
+
const path = approvalsFile(cwd);
|
|
69064
|
+
if (!isFile(path)) return {};
|
|
69065
|
+
try {
|
|
69066
|
+
const data = JSON.parse(readText(path));
|
|
69067
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) return {};
|
|
69068
|
+
return Object.fromEntries(
|
|
69069
|
+
Object.entries(data).filter(
|
|
69070
|
+
(entry) => typeof entry[1] === "string"
|
|
69071
|
+
)
|
|
69072
|
+
);
|
|
69073
|
+
} catch {
|
|
69074
|
+
return {};
|
|
69075
|
+
}
|
|
69076
|
+
}
|
|
69077
|
+
function writeApprovals(cwd, approvals) {
|
|
69078
|
+
const path = approvalsFile(cwd);
|
|
69079
|
+
mkdirSync8(parent(path), { recursive: true });
|
|
69080
|
+
writeFileSync5(path, `${dumps(approvals, { indent: 2, sortKeys: true })}
|
|
69081
|
+
`);
|
|
69082
|
+
}
|
|
69083
|
+
function approve(config2, cwd) {
|
|
69084
|
+
writeApprovals(cwd, { ...readApprovals(cwd), [config2.name]: fingerprint2(config2) });
|
|
69085
|
+
}
|
|
69086
|
+
function revoke(name, cwd) {
|
|
69087
|
+
const approvals = readApprovals(cwd);
|
|
69088
|
+
if (!(name in approvals)) return;
|
|
69089
|
+
delete approvals[name];
|
|
69090
|
+
writeApprovals(cwd, approvals);
|
|
69091
|
+
}
|
|
69092
|
+
function partition(configs, cwd) {
|
|
69093
|
+
const approvals = readApprovals(cwd);
|
|
69094
|
+
const startable = [];
|
|
69095
|
+
const waiting = [];
|
|
69096
|
+
for (const config2 of configs) {
|
|
69097
|
+
const approved = approvals[config2.name];
|
|
69098
|
+
if (config2.source === "user" || approved === fingerprint2(config2)) startable.push(config2);
|
|
69099
|
+
else waiting.push({ config: config2, changed: approved !== void 0 });
|
|
69100
|
+
}
|
|
69101
|
+
return [startable, waiting];
|
|
69102
|
+
}
|
|
69103
|
+
function describeServer(config2) {
|
|
69104
|
+
if (config2.url) return config2.url;
|
|
69105
|
+
return join2([config2.command ?? "", ...config2.args]);
|
|
69106
|
+
}
|
|
69107
|
+
function waitingNotice({ config: config2, changed }) {
|
|
69108
|
+
const why = changed ? "has changed since you approved it" : "came with the project and has not been approved";
|
|
69109
|
+
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.`;
|
|
69110
|
+
}
|
|
69111
|
+
|
|
69037
69112
|
// src/mcp/client.ts
|
|
69038
69113
|
init_json();
|
|
69039
69114
|
init_net();
|
|
@@ -69614,7 +69689,7 @@ init_json();
|
|
|
69614
69689
|
init_pathlib();
|
|
69615
69690
|
init_paths();
|
|
69616
69691
|
init_py();
|
|
69617
|
-
import { mkdirSync as
|
|
69692
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "node:fs";
|
|
69618
69693
|
|
|
69619
69694
|
// src/tools/base.ts
|
|
69620
69695
|
function toolResult(content, fields = {}) {
|
|
@@ -69671,7 +69746,7 @@ function isAbort(error, signal) {
|
|
|
69671
69746
|
init_pathlib();
|
|
69672
69747
|
init_paths();
|
|
69673
69748
|
init_py();
|
|
69674
|
-
import { mkdirSync as
|
|
69749
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "node:fs";
|
|
69675
69750
|
var MIN_BODY_CHARS = 200;
|
|
69676
69751
|
function elision(omittedLines, omittedChars, path) {
|
|
69677
69752
|
return `
|
|
@@ -69726,10 +69801,10 @@ function split2(lines2, charCap, lineCap, headRatio) {
|
|
|
69726
69801
|
}
|
|
69727
69802
|
function spill(text3, sessionId, toolUseId) {
|
|
69728
69803
|
const directory = sessionOutputsDir(sessionId);
|
|
69729
|
-
|
|
69804
|
+
mkdirSync9(directory, { recursive: true });
|
|
69730
69805
|
const safe = toolUseId.replace(/[^A-Za-z0-9_.-]/g, "_") || "output";
|
|
69731
69806
|
const path = join(directory, `${safe}.txt`);
|
|
69732
|
-
|
|
69807
|
+
writeFileSync6(path, text3, "utf8");
|
|
69733
69808
|
return path;
|
|
69734
69809
|
}
|
|
69735
69810
|
function summarizeForUi(text3, maxLen = 120) {
|
|
@@ -70295,6 +70370,7 @@ function serverConfig(fields) {
|
|
|
70295
70370
|
enabled: true,
|
|
70296
70371
|
timeout: 30,
|
|
70297
70372
|
oauth: noOptions(),
|
|
70373
|
+
source: "user",
|
|
70298
70374
|
...fields
|
|
70299
70375
|
};
|
|
70300
70376
|
}
|
|
@@ -70369,11 +70445,13 @@ async function normalise(raw, tool) {
|
|
|
70369
70445
|
return [images2, problems];
|
|
70370
70446
|
}
|
|
70371
70447
|
var MCPManager = class {
|
|
70372
|
-
constructor(configs, store = new AuthStore()) {
|
|
70448
|
+
constructor(configs, store = new AuthStore(), waiting = []) {
|
|
70373
70449
|
this.store = store;
|
|
70450
|
+
this.waiting = waiting;
|
|
70374
70451
|
this.configs = configs.filter((config2) => config2.enabled);
|
|
70375
70452
|
}
|
|
70376
70453
|
store;
|
|
70454
|
+
waiting;
|
|
70377
70455
|
configs;
|
|
70378
70456
|
clients = /* @__PURE__ */ new Map();
|
|
70379
70457
|
statuses = /* @__PURE__ */ new Map();
|
|
@@ -70585,13 +70663,16 @@ function buildTransport(config2, store) {
|
|
|
70585
70663
|
var userMcpFile = () => join(userHome(), "mcp.json");
|
|
70586
70664
|
function loadConfigs(cwd) {
|
|
70587
70665
|
const merged = /* @__PURE__ */ new Map();
|
|
70588
|
-
for (const path of [
|
|
70589
|
-
|
|
70666
|
+
for (const [path, source] of [
|
|
70667
|
+
[userMcpFile(), "user"],
|
|
70668
|
+
[projectMcpFile(cwd), "project"]
|
|
70669
|
+
])
|
|
70670
|
+
for (const config2 of readFile(path, source)) merged.set(config2.name, config2);
|
|
70590
70671
|
return [...merged.keys()].sort(compareCodePoints).map((name) => merged.get(name));
|
|
70591
70672
|
}
|
|
70592
70673
|
var isObject7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
70593
70674
|
var strings = (raw) => Object.fromEntries(Object.entries(isObject7(raw) ? raw : {}).map(([k, v]) => [k, String(v)]));
|
|
70594
|
-
function readFile(path) {
|
|
70675
|
+
function readFile(path, source) {
|
|
70595
70676
|
if (!isFile(path)) return [];
|
|
70596
70677
|
let data;
|
|
70597
70678
|
try {
|
|
@@ -70617,7 +70698,8 @@ function readFile(path) {
|
|
|
70617
70698
|
headers: strings(raw.headers),
|
|
70618
70699
|
enabled: "enabled" in raw ? truthy(raw.enabled) : true,
|
|
70619
70700
|
timeout: "timeout" in raw ? toFloat(raw.timeout) : 30,
|
|
70620
|
-
oauth: optionsFromJson(raw.oauth)
|
|
70701
|
+
oauth: optionsFromJson(raw.oauth),
|
|
70702
|
+
source
|
|
70621
70703
|
})
|
|
70622
70704
|
);
|
|
70623
70705
|
}
|
|
@@ -70632,7 +70714,7 @@ function readJson(path) {
|
|
|
70632
70714
|
return {};
|
|
70633
70715
|
}
|
|
70634
70716
|
}
|
|
70635
|
-
var writeJson = (path, data) =>
|
|
70717
|
+
var writeJson = (path, data) => writeFileSync7(path, `${dumps(data, { indent: 2, sortKeys: true })}
|
|
70636
70718
|
`);
|
|
70637
70719
|
function saveConfig(config2, cwd, userLevel = false) {
|
|
70638
70720
|
const path = userLevel ? userMcpFile() : projectMcpFile(cwd);
|
|
@@ -70648,7 +70730,7 @@ function saveConfig(config2, cwd, userLevel = false) {
|
|
|
70648
70730
|
const block2 = optionsToJson(config2.oauth);
|
|
70649
70731
|
if (Object.keys(block2).length) entry.oauth = block2;
|
|
70650
70732
|
servers[config2.name] = entry;
|
|
70651
|
-
|
|
70733
|
+
mkdirSync10(parent(path), { recursive: true });
|
|
70652
70734
|
writeJson(path, data);
|
|
70653
70735
|
return path;
|
|
70654
70736
|
}
|
|
@@ -70673,6 +70755,69 @@ function removeConfig(name, cwd, userLevel = false) {
|
|
|
70673
70755
|
init_net();
|
|
70674
70756
|
init_pathlib();
|
|
70675
70757
|
init_paths();
|
|
70758
|
+
|
|
70759
|
+
// src/providers/deferred.ts
|
|
70760
|
+
init_resolve();
|
|
70761
|
+
init_base();
|
|
70762
|
+
init_registry();
|
|
70763
|
+
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.";
|
|
70764
|
+
var NEEDS = {
|
|
70765
|
+
openrouter: "an OpenRouter API key",
|
|
70766
|
+
"openai-codex": "a ChatGPT Plus/Pro sign-in",
|
|
70767
|
+
"claude-code": "a Claude Pro/Max sign-in",
|
|
70768
|
+
devin: "a Devin sign-in"
|
|
70769
|
+
};
|
|
70770
|
+
function signInHint(modelId, resolver) {
|
|
70771
|
+
if (!available(resolver).length) return NOT_SIGNED_IN;
|
|
70772
|
+
const route = providerFor(modelId);
|
|
70773
|
+
const needs = NEEDS[route.id] ?? `a ${route.label} sign-in`;
|
|
70774
|
+
return `${modelId} needs ${needs}, which is not set up yet. Type /login to sign in, or /model to pick a model you can use.`;
|
|
70775
|
+
}
|
|
70776
|
+
var DeferredProvider = class {
|
|
70777
|
+
constructor(modelId, resolver, sessionId, models) {
|
|
70778
|
+
this.modelId = modelId;
|
|
70779
|
+
this.resolver = resolver;
|
|
70780
|
+
this.sessionId = sessionId;
|
|
70781
|
+
this.models = models;
|
|
70782
|
+
}
|
|
70783
|
+
modelId;
|
|
70784
|
+
resolver;
|
|
70785
|
+
sessionId;
|
|
70786
|
+
models;
|
|
70787
|
+
inner = null;
|
|
70788
|
+
get name() {
|
|
70789
|
+
return this.inner?.name ?? providerFor(this.modelId).id;
|
|
70790
|
+
}
|
|
70791
|
+
/** The prompt-cache key, kept for the provider this becomes. */
|
|
70792
|
+
setSessionId(sessionId) {
|
|
70793
|
+
this.sessionId = sessionId;
|
|
70794
|
+
this.inner?.setSessionId?.(sessionId);
|
|
70795
|
+
}
|
|
70796
|
+
/** A key saved now is read from the store when the provider is built. */
|
|
70797
|
+
setApiKey(key2) {
|
|
70798
|
+
this.inner?.setApiKey?.(key2);
|
|
70799
|
+
}
|
|
70800
|
+
async *astream(request2, signal) {
|
|
70801
|
+
if (this.inner === null) {
|
|
70802
|
+
try {
|
|
70803
|
+
this.inner = buildProvider(request2.model, this.resolver, {
|
|
70804
|
+
sessionId: this.sessionId,
|
|
70805
|
+
models: this.models
|
|
70806
|
+
});
|
|
70807
|
+
} catch (error) {
|
|
70808
|
+
if (error instanceof MissingCredential || error instanceof ExpiredCredential)
|
|
70809
|
+
throw new ProviderError(signInHint(request2.model, this.resolver));
|
|
70810
|
+
throw error;
|
|
70811
|
+
}
|
|
70812
|
+
}
|
|
70813
|
+
yield* this.inner.astream(request2, signal);
|
|
70814
|
+
}
|
|
70815
|
+
async aclose() {
|
|
70816
|
+
await this.inner?.aclose();
|
|
70817
|
+
}
|
|
70818
|
+
};
|
|
70819
|
+
|
|
70820
|
+
// src/cli.ts
|
|
70676
70821
|
init_models();
|
|
70677
70822
|
init_openrouter();
|
|
70678
70823
|
init_registry();
|
|
@@ -71228,7 +71373,7 @@ init_py();
|
|
|
71228
71373
|
|
|
71229
71374
|
// src/tools/anchors.ts
|
|
71230
71375
|
init_py();
|
|
71231
|
-
import { createHash as
|
|
71376
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
71232
71377
|
var WIDTHS = [4, 6, 8];
|
|
71233
71378
|
var SEPARATOR = "\0";
|
|
71234
71379
|
function split3(content) {
|
|
@@ -71243,7 +71388,7 @@ function window2(lines2, index) {
|
|
|
71243
71388
|
return [previous, lines2[index], following].join(SEPARATOR);
|
|
71244
71389
|
}
|
|
71245
71390
|
function digests(lines2) {
|
|
71246
|
-
return lines2.map((_, i) =>
|
|
71391
|
+
return lines2.map((_, i) => createHash9("sha1").update(window2(lines2, i), "utf8").digest("hex"));
|
|
71247
71392
|
}
|
|
71248
71393
|
function anchorWidth(full) {
|
|
71249
71394
|
const distinct = new Set(full).size;
|
|
@@ -71289,7 +71434,7 @@ var AnchorError = class extends Error {
|
|
|
71289
71434
|
init_images();
|
|
71290
71435
|
init_pathlib();
|
|
71291
71436
|
init_py();
|
|
71292
|
-
import { closeSync as closeSync6, existsSync as existsSync2, openSync as openSync6, readFileSync as readFileSync4, readSync as readSync2, statSync as
|
|
71437
|
+
import { closeSync as closeSync6, existsSync as existsSync2, openSync as openSync6, readFileSync as readFileSync4, readSync as readSync2, statSync as statSync10 } from "node:fs";
|
|
71293
71438
|
var DESCRIPTION = `Read a file from the local filesystem.
|
|
71294
71439
|
|
|
71295
71440
|
Returns numbered lines, each labelled with a short content anchor:
|
|
@@ -71326,7 +71471,7 @@ var ReadTool = class extends Tool {
|
|
|
71326
71471
|
if (isImagePath(path) || isBinary(path) && looksLikeImage(path)) return readImage(path);
|
|
71327
71472
|
if (isBinary(path)) {
|
|
71328
71473
|
throw new ToolError(
|
|
71329
|
-
`${path}: binary file (${count(
|
|
71474
|
+
`${path}: binary file (${count(statSync10(path).size, "byte")}), not read as text`
|
|
71330
71475
|
);
|
|
71331
71476
|
}
|
|
71332
71477
|
const offset = Math.max(toInt(params.offset || 1), 1);
|
|
@@ -71417,16 +71562,16 @@ function clip2(line) {
|
|
|
71417
71562
|
init_pathlib();
|
|
71418
71563
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
71419
71564
|
import {
|
|
71420
|
-
chmodSync as
|
|
71565
|
+
chmodSync as chmodSync3,
|
|
71421
71566
|
closeSync as closeSync7,
|
|
71422
71567
|
existsSync as existsSync3,
|
|
71423
71568
|
lstatSync as lstatSync2,
|
|
71424
|
-
mkdirSync as
|
|
71569
|
+
mkdirSync as mkdirSync11,
|
|
71425
71570
|
openSync as openSync7,
|
|
71426
71571
|
renameSync as renameSync5,
|
|
71427
71572
|
rmSync as rmSync3,
|
|
71428
|
-
statSync as
|
|
71429
|
-
writeFileSync as
|
|
71573
|
+
statSync as statSync11,
|
|
71574
|
+
writeFileSync as writeFileSync8
|
|
71430
71575
|
} from "node:fs";
|
|
71431
71576
|
var DESCRIPTION2 = `Write a file, overwriting it if it exists.
|
|
71432
71577
|
|
|
@@ -71472,7 +71617,7 @@ var WriteTool = class extends Tool {
|
|
|
71472
71617
|
}
|
|
71473
71618
|
}
|
|
71474
71619
|
const existed = existsSync3(path);
|
|
71475
|
-
|
|
71620
|
+
mkdirSync11(parent(path), { recursive: true });
|
|
71476
71621
|
this.checkpoints?.capture(path);
|
|
71477
71622
|
writeAtomic(path, content);
|
|
71478
71623
|
ctx.reads.markRead(path);
|
|
@@ -71506,7 +71651,7 @@ function writeAtomic(path, content) {
|
|
|
71506
71651
|
}
|
|
71507
71652
|
let mode2;
|
|
71508
71653
|
try {
|
|
71509
|
-
mode2 =
|
|
71654
|
+
mode2 = statSync11(target).mode & 4095;
|
|
71510
71655
|
} catch (error) {
|
|
71511
71656
|
if (error.code !== "ENOENT") throw error;
|
|
71512
71657
|
mode2 = null;
|
|
@@ -71515,11 +71660,11 @@ function writeAtomic(path, content) {
|
|
|
71515
71660
|
const fd = openSync7(tmp, "wx", mode2 ?? 438);
|
|
71516
71661
|
try {
|
|
71517
71662
|
try {
|
|
71518
|
-
|
|
71663
|
+
writeFileSync8(fd, content, "utf8");
|
|
71519
71664
|
} finally {
|
|
71520
71665
|
closeSync7(fd);
|
|
71521
71666
|
}
|
|
71522
|
-
if (mode2 !== null)
|
|
71667
|
+
if (mode2 !== null) chmodSync3(tmp, mode2);
|
|
71523
71668
|
renameSync5(tmp, target);
|
|
71524
71669
|
} catch (error) {
|
|
71525
71670
|
rmSync3(tmp, { force: true });
|
|
@@ -72415,7 +72560,7 @@ ${gate.contextText}`;
|
|
|
72415
72560
|
};
|
|
72416
72561
|
for (const call of calls) {
|
|
72417
72562
|
if (this.runsSerially(call)) {
|
|
72418
|
-
|
|
72563
|
+
if (concurrent.length) await gather(concurrent);
|
|
72419
72564
|
concurrent = [];
|
|
72420
72565
|
await run(call);
|
|
72421
72566
|
} else concurrent.push(call);
|
|
@@ -72950,7 +73095,7 @@ init_lateinject();
|
|
|
72950
73095
|
init_pathlib();
|
|
72951
73096
|
init_py();
|
|
72952
73097
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
72953
|
-
import { statSync as
|
|
73098
|
+
import { statSync as statSync12 } from "node:fs";
|
|
72954
73099
|
var STATUS_COMMAND = ["git", "status", "--porcelain=v2", "--branch", "-z"];
|
|
72955
73100
|
var ROOT_COMMAND = ["git", "rev-parse", "--show-toplevel"];
|
|
72956
73101
|
var TIMEOUT_SECONDS = 2;
|
|
@@ -73089,7 +73234,7 @@ var BranchWatcher = class {
|
|
|
73089
73234
|
if (head4 === null) return null;
|
|
73090
73235
|
let stamp;
|
|
73091
73236
|
try {
|
|
73092
|
-
const info =
|
|
73237
|
+
const info = statSync12(head4, { bigint: true });
|
|
73093
73238
|
stamp = `${info.mtimeNs}:${info.size}`;
|
|
73094
73239
|
} catch {
|
|
73095
73240
|
return this.current;
|
|
@@ -73193,7 +73338,7 @@ function statusCode(xy) {
|
|
|
73193
73338
|
}
|
|
73194
73339
|
function signature(path) {
|
|
73195
73340
|
try {
|
|
73196
|
-
const info =
|
|
73341
|
+
const info = statSync12(path, { bigint: true });
|
|
73197
73342
|
return `${info.mtimeNs}:${info.size}`;
|
|
73198
73343
|
} catch {
|
|
73199
73344
|
return "none";
|
|
@@ -73383,6 +73528,29 @@ init_py();
|
|
|
73383
73528
|
init_shlex();
|
|
73384
73529
|
var OPERATORS = ["&&", "||", ";;", ";", "|&", "|", "\n", "&"];
|
|
73385
73530
|
var DYNAMIC_EXECUTABLES = /* @__PURE__ */ new Set(["eval", "exec", "source", ".", "env"]);
|
|
73531
|
+
var WRAPPERS = /* @__PURE__ */ new Set([
|
|
73532
|
+
"builtin",
|
|
73533
|
+
"caffeinate",
|
|
73534
|
+
"chrt",
|
|
73535
|
+
"command",
|
|
73536
|
+
"doas",
|
|
73537
|
+
"flock",
|
|
73538
|
+
"gtimeout",
|
|
73539
|
+
"ionice",
|
|
73540
|
+
"nice",
|
|
73541
|
+
"nohup",
|
|
73542
|
+
"setsid",
|
|
73543
|
+
"stdbuf",
|
|
73544
|
+
"strace",
|
|
73545
|
+
"sudo",
|
|
73546
|
+
"taskset",
|
|
73547
|
+
"time",
|
|
73548
|
+
"timeout",
|
|
73549
|
+
"unbuffer",
|
|
73550
|
+
"watch",
|
|
73551
|
+
"xargs"
|
|
73552
|
+
]);
|
|
73553
|
+
var ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*\+?=/;
|
|
73386
73554
|
var FD_DUP = /^\d*>&\d*-?$/;
|
|
73387
73555
|
var OUT_OP = /^(?:\d*>>?|&>>?)$/;
|
|
73388
73556
|
var IN_OP = /^\d*<<?-?$/;
|
|
@@ -73468,7 +73636,17 @@ var READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
73468
73636
|
var MUTATING_FLAGS = {
|
|
73469
73637
|
sed: /* @__PURE__ */ new Set(["-i", "--in-place"]),
|
|
73470
73638
|
awk: /* @__PURE__ */ new Set(["-i", "--include"]),
|
|
73471
|
-
find: /* @__PURE__ */ new Set([
|
|
73639
|
+
find: /* @__PURE__ */ new Set([
|
|
73640
|
+
"-delete",
|
|
73641
|
+
"-exec",
|
|
73642
|
+
"-execdir",
|
|
73643
|
+
"-ok",
|
|
73644
|
+
"-okdir",
|
|
73645
|
+
"-fprint",
|
|
73646
|
+
"-fprint0",
|
|
73647
|
+
"-fprintf",
|
|
73648
|
+
"-fls"
|
|
73649
|
+
]),
|
|
73472
73650
|
fd: /* @__PURE__ */ new Set(["-x", "--exec", "-X", "--exec-batch"]),
|
|
73473
73651
|
sort: /* @__PURE__ */ new Set(["-o", "--output", "--compress-program"]),
|
|
73474
73652
|
tree: /* @__PURE__ */ new Set(["-o"]),
|
|
@@ -73643,10 +73821,18 @@ function toSegment(text3, inSub) {
|
|
|
73643
73821
|
if (tokens.length === 0) return [null, [], true];
|
|
73644
73822
|
const [words, redirects] = splitRedirections(tokens);
|
|
73645
73823
|
if (words.length === 0) return [null, redirects, true];
|
|
73646
|
-
|
|
73824
|
+
let first2 = 0;
|
|
73825
|
+
while (first2 < words.length && ASSIGNMENT.test(words[first2])) first2++;
|
|
73826
|
+
const executable = words[first2] ?? "";
|
|
73647
73827
|
const ok = !executable.includes("$") && !executable.includes("`");
|
|
73648
73828
|
return [
|
|
73649
|
-
{
|
|
73829
|
+
{
|
|
73830
|
+
raw: stripped,
|
|
73831
|
+
executable,
|
|
73832
|
+
args: words.slice(first2 + 1),
|
|
73833
|
+
assignments: words.slice(0, first2),
|
|
73834
|
+
inSubstitution: inSub
|
|
73835
|
+
},
|
|
73650
73836
|
redirects,
|
|
73651
73837
|
ok
|
|
73652
73838
|
];
|
|
@@ -73726,13 +73912,20 @@ function scan(text3) {
|
|
|
73726
73912
|
index++;
|
|
73727
73913
|
continue;
|
|
73728
73914
|
}
|
|
73729
|
-
if (text3.startsWith("$(", index) || char === "`") {
|
|
73915
|
+
if (text3.startsWith("$(", index) || text3.startsWith("<(", index) || text3.startsWith(">(", index) || char === "`") {
|
|
73730
73916
|
const [body, consumed, closed] = readSubstitution(text3, index);
|
|
73731
73917
|
substitutions.push(...recurse(body, substitutions));
|
|
73732
73918
|
ok = ok && closed;
|
|
73733
73919
|
index += consumed;
|
|
73734
73920
|
continue;
|
|
73735
73921
|
}
|
|
73922
|
+
if (char === "(" && strip(buffer)) {
|
|
73923
|
+
const [body, consumed] = readSubstitution(text3, index);
|
|
73924
|
+
substitutions.push(...recurse(body, substitutions));
|
|
73925
|
+
ok = false;
|
|
73926
|
+
index += consumed;
|
|
73927
|
+
continue;
|
|
73928
|
+
}
|
|
73736
73929
|
if (char === "(" || char === ")") {
|
|
73737
73930
|
index++;
|
|
73738
73931
|
continue;
|
|
@@ -73770,27 +73963,51 @@ function readSubstitution(text3, index) {
|
|
|
73770
73963
|
if (end === -1) return [text3.slice(index + 1), text3.length - index, false];
|
|
73771
73964
|
return [text3.slice(index + 1, end), end - index + 1, true];
|
|
73772
73965
|
}
|
|
73966
|
+
const open = text3.indexOf("(", index);
|
|
73773
73967
|
let depth = 0;
|
|
73774
|
-
for (let cursor =
|
|
73968
|
+
for (let cursor = open; cursor < text3.length; cursor++) {
|
|
73775
73969
|
if (text3[cursor] === "(") depth++;
|
|
73776
73970
|
else if (text3[cursor] === ")") {
|
|
73777
73971
|
depth--;
|
|
73778
|
-
if (depth === 0) return [text3.slice(
|
|
73972
|
+
if (depth === 0) return [text3.slice(open + 1, cursor), cursor - index + 1, true];
|
|
73779
73973
|
}
|
|
73780
73974
|
}
|
|
73781
|
-
return [text3.slice(
|
|
73975
|
+
return [text3.slice(open + 1), text3.length - index, false];
|
|
73782
73976
|
}
|
|
73783
|
-
function matchSpecifier(segment, pattern) {
|
|
73977
|
+
function matchSpecifier(segment, pattern, exact = false) {
|
|
73784
73978
|
if (pattern === "*" || pattern === ":*") return true;
|
|
73785
|
-
|
|
73979
|
+
if (exact) return matchWords(written(segment), pattern);
|
|
73980
|
+
return commandForms(segment).some((words) => matchWords(words, pattern));
|
|
73981
|
+
}
|
|
73982
|
+
function matchWords(words, pattern) {
|
|
73983
|
+
const commandLine = words.join(" ");
|
|
73786
73984
|
if (pattern.endsWith(":*")) {
|
|
73787
73985
|
const prefix = strip(pattern.slice(0, -2));
|
|
73788
73986
|
return commandLine === prefix || commandLine.startsWith(`${prefix} `);
|
|
73789
73987
|
}
|
|
73790
73988
|
if (pattern.includes(" ")) return commandLine === pattern;
|
|
73791
|
-
return
|
|
73989
|
+
return words[0] === pattern;
|
|
73990
|
+
}
|
|
73991
|
+
var written = (segment) => [...segment.assignments, segment.executable, ...segment.args].filter(Boolean);
|
|
73992
|
+
function commandForms(segment) {
|
|
73993
|
+
const words = [segment.executable, ...segment.args].filter(Boolean);
|
|
73994
|
+
const forms = [];
|
|
73995
|
+
const add2 = (command2) => {
|
|
73996
|
+
forms.push(command2);
|
|
73997
|
+
const name = basename2(command2[0]);
|
|
73998
|
+
if (name !== command2[0]) forms.push([name, ...command2.slice(1)]);
|
|
73999
|
+
};
|
|
74000
|
+
if (words.length === 0) return forms;
|
|
74001
|
+
add2(words);
|
|
74002
|
+
if (WRAPPERS.has(basename2(words[0]))) {
|
|
74003
|
+
for (let index = 1; index < words.length; index++)
|
|
74004
|
+
if (!words[index].startsWith("-")) add2(words.slice(index));
|
|
74005
|
+
}
|
|
74006
|
+
return forms;
|
|
73792
74007
|
}
|
|
74008
|
+
var basename2 = (path) => path.slice(path.lastIndexOf("/") + 1);
|
|
73793
74009
|
function isReadOnly(segment) {
|
|
74010
|
+
if (segment.assignments.length) return false;
|
|
73794
74011
|
if (segment.executable === "git") return gitIsReadOnly(segment.args);
|
|
73795
74012
|
if (!READ_ONLY_COMMANDS.has(segment.executable)) return false;
|
|
73796
74013
|
if (segment.executable === "env") return !envRunsACommand(segment.args);
|
|
@@ -73803,13 +74020,13 @@ function isReadOnly(segment) {
|
|
|
73803
74020
|
);
|
|
73804
74021
|
}
|
|
73805
74022
|
var PROGRAM_TEXT_COMMANDS = /* @__PURE__ */ new Set(["awk", "sed"]);
|
|
73806
|
-
var AWK_WRITES = /system\s*\(|\|\s*["']|["']\s
|
|
74023
|
+
var AWK_WRITES = /system\s*\(|\|\s*["']|["']\s*\||\|\s*getline\b|\|&|print[f]?[^;}\n]*[>|]|@(?:include|load)\b/;
|
|
73807
74024
|
var PROGRAM_TEXT_OPTIONS = {
|
|
73808
74025
|
awk: ["-e", "--source"],
|
|
73809
74026
|
sed: ["-e", "--expression"]
|
|
73810
74027
|
};
|
|
73811
74028
|
var PROGRAM_FILE_OPTIONS = {
|
|
73812
|
-
awk: ["-f", "--file", "-E", "--exec"],
|
|
74029
|
+
awk: ["-f", "--file", "-E", "--exec", "-l", "--load"],
|
|
73813
74030
|
sed: ["-f", "--file"]
|
|
73814
74031
|
};
|
|
73815
74032
|
function namesAnOutput(segment) {
|
|
@@ -73980,7 +74197,182 @@ function splitGitSubcommand(args) {
|
|
|
73980
74197
|
return [null, []];
|
|
73981
74198
|
}
|
|
73982
74199
|
|
|
74200
|
+
// src/permissions/sandbox.ts
|
|
74201
|
+
init_pathlib();
|
|
74202
|
+
init_which();
|
|
74203
|
+
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
74204
|
+
import { closeSync as closeSync8, existsSync as existsSync4, mkdirSync as mkdirSync12, openSync as openSync8, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "node:fs";
|
|
74205
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
74206
|
+
var CREDENTIAL_PATHS = [
|
|
74207
|
+
"~/.ssh/",
|
|
74208
|
+
"~/.aws/",
|
|
74209
|
+
"~/.gnupg/",
|
|
74210
|
+
"~/.kube/",
|
|
74211
|
+
"~/.docker/config.json",
|
|
74212
|
+
"~/.config/gh/",
|
|
74213
|
+
"~/.netrc",
|
|
74214
|
+
"~/.npmrc",
|
|
74215
|
+
"~/.pypirc",
|
|
74216
|
+
"~/.hx/auth.json"
|
|
74217
|
+
];
|
|
74218
|
+
var Sandbox = class {
|
|
74219
|
+
constructor(policy, backend) {
|
|
74220
|
+
this.policy = policy;
|
|
74221
|
+
this.backend = backend ?? detectBackend();
|
|
74222
|
+
}
|
|
74223
|
+
policy;
|
|
74224
|
+
backend;
|
|
74225
|
+
profilePath = null;
|
|
74226
|
+
/** False when degraded to `none`. */
|
|
74227
|
+
get active() {
|
|
74228
|
+
return this.backend !== "none";
|
|
74229
|
+
}
|
|
74230
|
+
/** The argv to actually exec. */
|
|
74231
|
+
wrap(argv) {
|
|
74232
|
+
if (this.backend === "seatbelt")
|
|
74233
|
+
return ["/usr/bin/sandbox-exec", "-f", this.writeProfile(), ...argv];
|
|
74234
|
+
if (this.backend === "bubblewrap") return buildBwrapArgv(this.policy, argv);
|
|
74235
|
+
return [...argv];
|
|
74236
|
+
}
|
|
74237
|
+
/** The generated Seatbelt profile - shown by `/permissions`. */
|
|
74238
|
+
profileText() {
|
|
74239
|
+
return buildSeatbeltProfile(this.policy);
|
|
74240
|
+
}
|
|
74241
|
+
writeProfile() {
|
|
74242
|
+
if (this.profilePath === null || !existsSync4(this.profilePath)) {
|
|
74243
|
+
const path = join(tmpdir2(), `hx-sandbox-${randomBytes6(4).toString("hex")}.sb`);
|
|
74244
|
+
writeFileSync9(path, this.profileText(), { mode: 384 });
|
|
74245
|
+
this.profilePath = path;
|
|
74246
|
+
}
|
|
74247
|
+
return this.profilePath;
|
|
74248
|
+
}
|
|
74249
|
+
cleanup() {
|
|
74250
|
+
if (this.profilePath !== null) {
|
|
74251
|
+
rmSync4(this.profilePath, { force: true });
|
|
74252
|
+
this.profilePath = null;
|
|
74253
|
+
}
|
|
74254
|
+
}
|
|
74255
|
+
};
|
|
74256
|
+
function detectBackend() {
|
|
74257
|
+
if (process.platform === "darwin" && existsSync4("/usr/bin/sandbox-exec")) return "seatbelt";
|
|
74258
|
+
if (process.platform === "linux" && which("bwrap")) return "bubblewrap";
|
|
74259
|
+
return "none";
|
|
74260
|
+
}
|
|
74261
|
+
function defaultPolicy(cwd, allowNetwork = false) {
|
|
74262
|
+
const temp = resolve(tmpdir2());
|
|
74263
|
+
const writable = [resolve(cwd), ...tempContainer(temp)];
|
|
74264
|
+
for (const extra of ["~/.cache", "~/Library/Caches"]) {
|
|
74265
|
+
const candidate = expandUser(extra);
|
|
74266
|
+
if (isDir(candidate)) writable.push(resolve(candidate));
|
|
74267
|
+
}
|
|
74268
|
+
return {
|
|
74269
|
+
writablePaths: [...new Set(writable)],
|
|
74270
|
+
readablePaths: ["/"],
|
|
74271
|
+
denyPaths: CREDENTIAL_PATHS.map((p) => ({
|
|
74272
|
+
path: real(absolute(expandUser(p))),
|
|
74273
|
+
isDir: p.endsWith("/")
|
|
74274
|
+
})),
|
|
74275
|
+
allowNetwork,
|
|
74276
|
+
allowSubprocess: true,
|
|
74277
|
+
cwd: resolve(cwd)
|
|
74278
|
+
};
|
|
74279
|
+
}
|
|
74280
|
+
var DARWIN_TEMP_ROOT = "/private/var/folders";
|
|
74281
|
+
var isRelativeTo = (path, root) => path === root || path.startsWith(`${root.replace(/\/$/, "")}/`);
|
|
74282
|
+
function tempContainer(temp) {
|
|
74283
|
+
const paths = [temp];
|
|
74284
|
+
if (isRelativeTo(temp, DARWIN_TEMP_ROOT)) {
|
|
74285
|
+
const container = parent(temp);
|
|
74286
|
+
if (isRelativeTo(container, DARWIN_TEMP_ROOT) && container !== DARWIN_TEMP_ROOT)
|
|
74287
|
+
paths.push(container);
|
|
74288
|
+
}
|
|
74289
|
+
return paths;
|
|
74290
|
+
}
|
|
74291
|
+
function buildSeatbeltProfile(policy) {
|
|
74292
|
+
const lines2 = [
|
|
74293
|
+
"(version 1)",
|
|
74294
|
+
"(deny default)",
|
|
74295
|
+
"",
|
|
74296
|
+
";; Process management",
|
|
74297
|
+
"(allow process-fork)",
|
|
74298
|
+
"(allow signal (target same-sandbox))",
|
|
74299
|
+
"(allow sysctl-read)",
|
|
74300
|
+
"(allow mach-lookup)",
|
|
74301
|
+
"(allow ipc-posix-shm)",
|
|
74302
|
+
policy.allowSubprocess ? "(allow process-exec*)" : "(deny process-exec*)",
|
|
74303
|
+
"",
|
|
74304
|
+
";; Reads"
|
|
74305
|
+
];
|
|
74306
|
+
for (const path of policy.readablePaths.length ? policy.readablePaths : ["/"]) {
|
|
74307
|
+
lines2.push(`(allow file-read* (subpath ${sbplString(real(path))}))`);
|
|
74308
|
+
}
|
|
74309
|
+
lines2.push("", ";; Writes");
|
|
74310
|
+
for (const path of policy.writablePaths)
|
|
74311
|
+
lines2.push(`(allow file-write* (subpath ${sbplString(real(path))}))`);
|
|
74312
|
+
for (const device of ["/dev/null", "/dev/stdout", "/dev/stderr", "/dev/tty", "/dev/urandom"]) {
|
|
74313
|
+
lines2.push(`(allow file-write-data file-read-data (literal ${sbplString(device)}))`);
|
|
74314
|
+
}
|
|
74315
|
+
lines2.push("", ";; Network", policy.allowNetwork ? "(allow network*)" : "(deny network*)");
|
|
74316
|
+
if (policy.denyPaths.length) {
|
|
74317
|
+
lines2.push("", ";; Credentials - last match wins, so these override both allowances");
|
|
74318
|
+
for (const denied of policy.denyPaths) {
|
|
74319
|
+
lines2.push(`(deny file-read* file-write* (subpath ${sbplString(real(denied.path))}))`);
|
|
74320
|
+
}
|
|
74321
|
+
}
|
|
74322
|
+
return `${lines2.join("\n")}
|
|
74323
|
+
`;
|
|
74324
|
+
}
|
|
74325
|
+
function credentialPathOf(path) {
|
|
74326
|
+
for (const entry of CREDENTIAL_PATHS) {
|
|
74327
|
+
const denied = real(absolute(expandUser(entry)));
|
|
74328
|
+
if (path === denied || entry.endsWith("/") && isRelativeTo(path, denied)) return entry;
|
|
74329
|
+
}
|
|
74330
|
+
return null;
|
|
74331
|
+
}
|
|
74332
|
+
var real = (path) => resolve(path);
|
|
74333
|
+
function sbplString(path) {
|
|
74334
|
+
return `"${path.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
74335
|
+
}
|
|
74336
|
+
function buildBwrapArgv(policy, argv) {
|
|
74337
|
+
const command2 = [
|
|
74338
|
+
"bwrap",
|
|
74339
|
+
"--ro-bind",
|
|
74340
|
+
"/",
|
|
74341
|
+
"/",
|
|
74342
|
+
"--dev",
|
|
74343
|
+
"/dev",
|
|
74344
|
+
"--proc",
|
|
74345
|
+
"/proc",
|
|
74346
|
+
"--die-with-parent"
|
|
74347
|
+
];
|
|
74348
|
+
const writable = policy.writablePaths.map(real);
|
|
74349
|
+
for (const path of writable) command2.push("--bind", path, path);
|
|
74350
|
+
for (const denied of policy.denyPaths) {
|
|
74351
|
+
const path = real(denied.path);
|
|
74352
|
+
if (!existsSync4(path) && !ensureMountpoint(denied, writable)) continue;
|
|
74353
|
+
if (isDir(path)) command2.push("--tmpfs", path, "--remount-ro", path);
|
|
74354
|
+
else command2.push("--ro-bind", "/dev/null", path);
|
|
74355
|
+
}
|
|
74356
|
+
if (!policy.allowNetwork) command2.push("--unshare-net");
|
|
74357
|
+
if (policy.cwd !== null) command2.push("--chdir", real(policy.cwd));
|
|
74358
|
+
return [...command2, "--", ...argv];
|
|
74359
|
+
}
|
|
74360
|
+
function ensureMountpoint(denied, writable) {
|
|
74361
|
+
const path = real(denied.path);
|
|
74362
|
+
if (!writable.some((root) => isRelativeTo(path, root))) return false;
|
|
74363
|
+
try {
|
|
74364
|
+
mkdirSync12(parent(path), { recursive: true });
|
|
74365
|
+
if (denied.isDir) mkdirSync12(path, { mode: 448, recursive: true });
|
|
74366
|
+
else closeSync8(openSync8(path, "a", 384));
|
|
74367
|
+
} catch {
|
|
74368
|
+
return false;
|
|
74369
|
+
}
|
|
74370
|
+
return existsSync4(path);
|
|
74371
|
+
}
|
|
74372
|
+
|
|
73983
74373
|
// src/permissions/engine.ts
|
|
74374
|
+
var FILE_TOOLS = /* @__PURE__ */ new Set(["Read", "Write", "Edit", "Grep", "Glob", "Symbols"]);
|
|
74375
|
+
var PROTECTED_DIRS = /* @__PURE__ */ new Set([".git", ".hx"]);
|
|
73984
74376
|
var DEFAULT_MUTATING_TOOLS = /* @__PURE__ */ new Set(["Bash", "Write", "Edit", "Task"]);
|
|
73985
74377
|
function allSpecifiers(request2) {
|
|
73986
74378
|
if (request2.specifiers?.length) return request2.specifiers;
|
|
@@ -74018,6 +74410,12 @@ var PermissionEngine = class _PermissionEngine {
|
|
|
74018
74410
|
if (denial) return outcome("deny", "matched a deny rule", denial);
|
|
74019
74411
|
if (this.sessionGrants.has(this.grantKey(request2)))
|
|
74020
74412
|
return outcome("allow", "granted for this session");
|
|
74413
|
+
const credential = this.credentialTouched(request2);
|
|
74414
|
+
if (credential !== null) {
|
|
74415
|
+
const named = this.matchAllow(request2, true);
|
|
74416
|
+
if (named) return outcome("allow", "matched an allow rule", named);
|
|
74417
|
+
return outcome("ask", `${credential} holds credentials`);
|
|
74418
|
+
}
|
|
74021
74419
|
if (request2.toolName === "Bash" && request2.specifier) {
|
|
74022
74420
|
const parsed = parse4(request2.specifier);
|
|
74023
74421
|
if (parsed.unparseable) {
|
|
@@ -74039,7 +74437,7 @@ var PermissionEngine = class _PermissionEngine {
|
|
|
74039
74437
|
return outcome("allow", "read-only tool in plan mode");
|
|
74040
74438
|
}
|
|
74041
74439
|
if (!request2.mutating) return outcome("allow", "read-only tool");
|
|
74042
|
-
if (this.mode === "acceptEdits" && _PermissionEngine.EDIT_TOOLS.has(request2.toolName)) {
|
|
74440
|
+
if (this.mode === "acceptEdits" && _PermissionEngine.EDIT_TOOLS.has(request2.toolName) && this.editsProject(request2)) {
|
|
74043
74441
|
return outcome("allow", "acceptEdits mode");
|
|
74044
74442
|
}
|
|
74045
74443
|
if (request2.toolName === "Bash" && request2.specifier) {
|
|
@@ -74051,6 +74449,33 @@ var PermissionEngine = class _PermissionEngine {
|
|
|
74051
74449
|
}
|
|
74052
74450
|
return outcome("ask", "mutating tool");
|
|
74053
74451
|
}
|
|
74452
|
+
/** Where `specifier` really is: absolute, with `..` and symlinks resolved. */
|
|
74453
|
+
target(specifier) {
|
|
74454
|
+
return realPath(purePath(expandUser(specifier)), resolve(this.cwd));
|
|
74455
|
+
}
|
|
74456
|
+
/** The credential file or folder a file tool's call reaches, as written in
|
|
74457
|
+
* `CREDENTIAL_PATHS`, or `null`. */
|
|
74458
|
+
credentialTouched(request2) {
|
|
74459
|
+
if (!FILE_TOOLS.has(request2.toolName)) return null;
|
|
74460
|
+
for (const specifier of allSpecifiers(request2)) {
|
|
74461
|
+
const credential = credentialPathOf(this.target(specifier));
|
|
74462
|
+
if (credential !== null) return credential;
|
|
74463
|
+
}
|
|
74464
|
+
return null;
|
|
74465
|
+
}
|
|
74466
|
+
/**
|
|
74467
|
+
* Whether every file an edit changes is the project's own, which is all
|
|
74468
|
+
* acceptEdits covers: inside the working directory once symlinks are
|
|
74469
|
+
* resolved, and not in a folder whose files run code or grant permissions.
|
|
74470
|
+
*/
|
|
74471
|
+
editsProject(request2) {
|
|
74472
|
+
const root = resolve(this.cwd);
|
|
74473
|
+
const prefix = root === "/" ? "/" : `${root}/`;
|
|
74474
|
+
const targets = allSpecifiers(request2).map((specifier) => this.target(specifier));
|
|
74475
|
+
return targets.length > 0 && targets.every(
|
|
74476
|
+
(target) => target.startsWith(prefix) && !target.slice(prefix.length).split("/").some((part) => PROTECTED_DIRS.has(part))
|
|
74477
|
+
);
|
|
74478
|
+
}
|
|
74054
74479
|
applies(rule2, request2) {
|
|
74055
74480
|
return rule2.tool === request2.toolName || rule2.tool === "*";
|
|
74056
74481
|
}
|
|
@@ -74062,13 +74487,14 @@ var PermissionEngine = class _PermissionEngine {
|
|
|
74062
74487
|
return null;
|
|
74063
74488
|
}
|
|
74064
74489
|
/**
|
|
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
|
|
74490
|
+
* Allow requires *every* segment to be covered, each as it is written.
|
|
74491
|
+
* Any-segment matching is right for deny (`safe && danger` must be caught)
|
|
74492
|
+
* but catastrophic for allow: `allow: Bash(git status:*)` would green-light
|
|
74493
|
+
* `git status && rm -rf /`.
|
|
74068
74494
|
*/
|
|
74069
|
-
matchAllow(request2) {
|
|
74495
|
+
matchAllow(request2, named = false) {
|
|
74070
74496
|
const candidates2 = this.rules.filter(
|
|
74071
|
-
(rule2) => rule2.decision === "allow" && this.applies(rule2, request2)
|
|
74497
|
+
(rule2) => rule2.decision === "allow" && this.applies(rule2, request2) && (!named || rule2.specifier !== null)
|
|
74072
74498
|
);
|
|
74073
74499
|
if (candidates2.length === 0) return null;
|
|
74074
74500
|
if (request2.toolName !== "Bash" || !request2.specifier) {
|
|
@@ -74086,7 +74512,7 @@ var PermissionEngine = class _PermissionEngine {
|
|
|
74086
74512
|
let first2 = null;
|
|
74087
74513
|
for (const segment of parsed.segments) {
|
|
74088
74514
|
const covering = candidates2.find(
|
|
74089
|
-
(rule2) => rule2.specifier === null || matchSpecifier(segment, rule2.specifier)
|
|
74515
|
+
(rule2) => rule2.specifier === null || matchSpecifier(segment, rule2.specifier, true)
|
|
74090
74516
|
);
|
|
74091
74517
|
if (covering === void 0) return null;
|
|
74092
74518
|
first2 ??= covering;
|
|
@@ -74193,6 +74619,7 @@ function persistableRules(toolName, specifier) {
|
|
|
74193
74619
|
return rules;
|
|
74194
74620
|
}
|
|
74195
74621
|
function rulePrefix(segment) {
|
|
74622
|
+
if (segment.assignments.length || !segment.executable) return null;
|
|
74196
74623
|
const first2 = segment.args[0];
|
|
74197
74624
|
if (first2 !== void 0 && !first2.startsWith("-")) return `${segment.executable} ${first2}`;
|
|
74198
74625
|
return null;
|
|
@@ -74361,172 +74788,6 @@ var InvalidRule = class extends Error {
|
|
|
74361
74788
|
name = "InvalidRule";
|
|
74362
74789
|
};
|
|
74363
74790
|
|
|
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
74791
|
// src/runtime.ts
|
|
74531
74792
|
init_models();
|
|
74532
74793
|
init_registry();
|
|
@@ -74668,7 +74929,7 @@ init_py();
|
|
|
74668
74929
|
init_shlex();
|
|
74669
74930
|
import { execFile as execFile2, spawn as spawn7 } from "node:child_process";
|
|
74670
74931
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
74671
|
-
import { closeSync as closeSync9, mkdirSync as
|
|
74932
|
+
import { closeSync as closeSync9, mkdirSync as mkdirSync13, openSync as openSync9, readFileSync as readFileSync6 } from "node:fs";
|
|
74672
74933
|
import { constants as constants3 } from "node:os";
|
|
74673
74934
|
var DESCRIPTION5 = `Run a shell command in a persistent session.
|
|
74674
74935
|
|
|
@@ -74742,11 +75003,17 @@ var Reader = class {
|
|
|
74742
75003
|
return this.chunks.shift() ?? Buffer.alloc(0);
|
|
74743
75004
|
}
|
|
74744
75005
|
};
|
|
75006
|
+
var POSIX_SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "zsh", "dash", "ksh", "ksh93", "mksh", "ash", "yash"]);
|
|
75007
|
+
function posixShell(shell = null) {
|
|
75008
|
+
const chosen = shell || process.env.SHELL || "";
|
|
75009
|
+
if (POSIX_SHELLS.has(chosen.slice(chosen.lastIndexOf("/") + 1))) return chosen;
|
|
75010
|
+
return isFile("/bin/bash") ? "/bin/bash" : "/bin/sh";
|
|
75011
|
+
}
|
|
74745
75012
|
var PersistentShell = class {
|
|
74746
75013
|
constructor(currentCwd, shell = null, env = {}, sandbox = null) {
|
|
74747
75014
|
this.currentCwd = currentCwd;
|
|
74748
75015
|
this.sandbox = sandbox;
|
|
74749
|
-
this.shell = shell
|
|
75016
|
+
this.shell = posixShell(shell);
|
|
74750
75017
|
this.env = commandEnvironment({ ...env, HX: "1", TERM: "dumb" });
|
|
74751
75018
|
}
|
|
74752
75019
|
currentCwd;
|
|
@@ -74949,7 +75216,7 @@ var BackgroundJobs = class {
|
|
|
74949
75216
|
*/
|
|
74950
75217
|
async start(command2, cwd, env, sandbox = null) {
|
|
74951
75218
|
const jobId = `bg_${randomBytes7(4).toString("hex")}`;
|
|
74952
|
-
|
|
75219
|
+
mkdirSync13(this.logDir, { recursive: true });
|
|
74953
75220
|
const logPath = join(this.logDir, `${jobId}.log`);
|
|
74954
75221
|
let argv = ["/bin/sh", "-c", command2];
|
|
74955
75222
|
if (sandbox !== null) argv = sandbox.wrap(argv);
|
|
@@ -75228,7 +75495,7 @@ init_json();
|
|
|
75228
75495
|
init_py();
|
|
75229
75496
|
|
|
75230
75497
|
// src/tools/glob.ts
|
|
75231
|
-
import { statSync as
|
|
75498
|
+
import { statSync as statSync13 } from "node:fs";
|
|
75232
75499
|
init_pathlib();
|
|
75233
75500
|
init_py();
|
|
75234
75501
|
var DESCRIPTION6 = `Find files by glob pattern (e.g. \`**/*.py\`, \`src/**/*.ts\`).
|
|
@@ -75259,6 +75526,10 @@ var GlobTool = class extends Tool {
|
|
|
75259
75526
|
required: ["pattern"]
|
|
75260
75527
|
};
|
|
75261
75528
|
}
|
|
75529
|
+
/** The folder searched, so a path rule covers a search of it. */
|
|
75530
|
+
permissionSpecifier(params) {
|
|
75531
|
+
return typeof params.path === "string" && params.path ? params.path : null;
|
|
75532
|
+
}
|
|
75262
75533
|
async run(params, ctx) {
|
|
75263
75534
|
const root = searchRoot(params.path, ctx.cwd);
|
|
75264
75535
|
if (!isDir(root)) throw new ToolError(`${root}: not a directory`);
|
|
@@ -75301,7 +75572,7 @@ function checkPattern(pattern) {
|
|
|
75301
75572
|
var ignored = (path) => parts(path).some((part) => IGNORED_DIRS.has(part));
|
|
75302
75573
|
function mtime(path) {
|
|
75303
75574
|
try {
|
|
75304
|
-
return
|
|
75575
|
+
return statSync13(path).mtimeMs;
|
|
75305
75576
|
} catch {
|
|
75306
75577
|
return 0;
|
|
75307
75578
|
}
|
|
@@ -75343,6 +75614,10 @@ var GrepTool = class extends Tool {
|
|
|
75343
75614
|
required: ["pattern"]
|
|
75344
75615
|
};
|
|
75345
75616
|
}
|
|
75617
|
+
/** The file or folder searched, so a path rule covers a search of it. */
|
|
75618
|
+
permissionSpecifier(params) {
|
|
75619
|
+
return typeof params.path === "string" && params.path ? params.path : null;
|
|
75620
|
+
}
|
|
75346
75621
|
async run(params, ctx) {
|
|
75347
75622
|
const pattern = String(params.pattern);
|
|
75348
75623
|
const ripgrep = which("rg");
|
|
@@ -75397,7 +75672,28 @@ function runRipgrep(rg, pattern, root, mode2, params) {
|
|
|
75397
75672
|
}
|
|
75398
75673
|
throw new ToolError(stderr || `ripgrep exited ${result2.status}`);
|
|
75399
75674
|
}
|
|
75400
|
-
|
|
75675
|
+
const hidden = credentialsUnder(root);
|
|
75676
|
+
return splitLines(result2.stdout ?? "").filter(
|
|
75677
|
+
(line) => line && !hidden.some((credential) => credential.covers(line))
|
|
75678
|
+
);
|
|
75679
|
+
}
|
|
75680
|
+
function credentialsUnder(root) {
|
|
75681
|
+
const base = root.replace(/\/$/, "");
|
|
75682
|
+
const real2 = resolve(root);
|
|
75683
|
+
const found = [];
|
|
75684
|
+
for (const entry of CREDENTIAL_PATHS) {
|
|
75685
|
+
const spellings = /* @__PURE__ */ new Set([purePath(absolute(expandUser(entry))), resolve(expandUser(entry))]);
|
|
75686
|
+
for (const path of spellings) {
|
|
75687
|
+
let shown;
|
|
75688
|
+
if (path.startsWith(`${real2}/`)) shown = base + path.slice(real2.length);
|
|
75689
|
+
else if (path.startsWith(`${base}/`)) shown = path;
|
|
75690
|
+
else continue;
|
|
75691
|
+
found.push({
|
|
75692
|
+
covers: entry.endsWith("/") ? (line) => line.startsWith(`${shown}/`) : (line) => line === shown || line.startsWith(`${shown}:`) || line.startsWith(`${shown}-`)
|
|
75693
|
+
});
|
|
75694
|
+
}
|
|
75695
|
+
}
|
|
75696
|
+
return found;
|
|
75401
75697
|
}
|
|
75402
75698
|
function search(regex, root, mode2, params) {
|
|
75403
75699
|
const globFilter = String(params.glob || "");
|
|
@@ -75424,7 +75720,7 @@ function search(regex, root, mode2, params) {
|
|
|
75424
75720
|
function walk(root) {
|
|
75425
75721
|
if (isFile(root)) return [root];
|
|
75426
75722
|
return glob(root, "**/*").filter(
|
|
75427
|
-
(path) => isFile(path) && !parts(path).some((part) => IGNORED_DIRS.has(part))
|
|
75723
|
+
(path) => isFile(path) && !parts(path).some((part) => IGNORED_DIRS.has(part)) && credentialPathOf(resolve(path)) === null
|
|
75428
75724
|
);
|
|
75429
75725
|
}
|
|
75430
75726
|
function renderContext(path, lines2, hits, before2, after2) {
|
|
@@ -75437,7 +75733,7 @@ function renderContext(path, lines2, hits, before2, after2) {
|
|
|
75437
75733
|
}
|
|
75438
75734
|
|
|
75439
75735
|
// src/tools/symbols.ts
|
|
75440
|
-
import { readFileSync as readFileSync8, statSync as
|
|
75736
|
+
import { readFileSync as readFileSync8, statSync as statSync14 } from "node:fs";
|
|
75441
75737
|
import { createRequire } from "node:module";
|
|
75442
75738
|
import { dirname } from "node:path";
|
|
75443
75739
|
init_json();
|
|
@@ -75511,11 +75807,11 @@ var LANGUAGES = {
|
|
|
75511
75807
|
".go": GO,
|
|
75512
75808
|
".rs": RUST
|
|
75513
75809
|
};
|
|
75514
|
-
var
|
|
75810
|
+
var PACKAGE = "@vscode/tree-sitter-wasm";
|
|
75515
75811
|
var load = createRequire(import.meta.url);
|
|
75516
75812
|
function packageDir() {
|
|
75517
75813
|
try {
|
|
75518
|
-
return dirname(load.resolve(
|
|
75814
|
+
return dirname(load.resolve(PACKAGE));
|
|
75519
75815
|
} catch {
|
|
75520
75816
|
return null;
|
|
75521
75817
|
}
|
|
@@ -75529,9 +75825,9 @@ function parserFor(grammar) {
|
|
|
75529
75825
|
parser = (async () => {
|
|
75530
75826
|
const directory = packageDir();
|
|
75531
75827
|
if (directory === null)
|
|
75532
|
-
throw new ToolError(`tree-sitter is not installed: npm install -g ${
|
|
75828
|
+
throw new ToolError(`tree-sitter is not installed: npm install -g ${PACKAGE}`);
|
|
75533
75829
|
runtime ??= (async () => {
|
|
75534
|
-
const module = load(
|
|
75830
|
+
const module = load(PACKAGE);
|
|
75535
75831
|
await module.Parser.init({ locateFile: (file) => join(directory, file) });
|
|
75536
75832
|
return module;
|
|
75537
75833
|
})();
|
|
@@ -75569,6 +75865,12 @@ var SymbolsTool = class extends Tool {
|
|
|
75569
75865
|
required: ["mode"]
|
|
75570
75866
|
};
|
|
75571
75867
|
}
|
|
75868
|
+
/** The file or folder read, so a path rule covers it. */
|
|
75869
|
+
permissionSpecifier(params) {
|
|
75870
|
+
for (const key2 of ["file_path", "path"])
|
|
75871
|
+
if (typeof params[key2] === "string" && params[key2]) return params[key2];
|
|
75872
|
+
return null;
|
|
75873
|
+
}
|
|
75572
75874
|
async run(params, ctx) {
|
|
75573
75875
|
const mode2 = String(params.mode);
|
|
75574
75876
|
const limit = toInt(params.limit || DEFAULT_LIMIT3);
|
|
@@ -75677,7 +75979,7 @@ function candidates(params, ctx) {
|
|
|
75677
75979
|
}
|
|
75678
75980
|
function mtime2(path) {
|
|
75679
75981
|
try {
|
|
75680
|
-
return
|
|
75982
|
+
return statSync14(path).mtimeMs;
|
|
75681
75983
|
} catch {
|
|
75682
75984
|
return 0;
|
|
75683
75985
|
}
|
|
@@ -75706,7 +76008,7 @@ function specFor(path) {
|
|
|
75706
76008
|
async function parse5(path, spec2) {
|
|
75707
76009
|
let size3;
|
|
75708
76010
|
try {
|
|
75709
|
-
size3 =
|
|
76011
|
+
size3 = statSync14(path).size;
|
|
75710
76012
|
} catch (error) {
|
|
75711
76013
|
throw new ToolError(`${path}: ${osErrorText(error)}`);
|
|
75712
76014
|
}
|
|
@@ -76413,7 +76715,7 @@ ${listed2}
|
|
|
76413
76715
|
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
76716
|
];
|
|
76415
76717
|
}
|
|
76416
|
-
function buildRuntime(parsed, resume = null) {
|
|
76718
|
+
function buildRuntime(parsed, resume = null, signedOut = false) {
|
|
76417
76719
|
ensureUserDirs();
|
|
76418
76720
|
const settings = loadSettings(parsed.cwd, parsed.overrides);
|
|
76419
76721
|
const bus = new EventBus();
|
|
@@ -76424,10 +76726,16 @@ function buildRuntime(parsed, resume = null) {
|
|
|
76424
76726
|
models.loadCache();
|
|
76425
76727
|
const modelInfo2 = models.getOrDefault(settings.models.model);
|
|
76426
76728
|
const session = resume ? loadSession(resume) : newSession(settings.cwd, settings.models.model);
|
|
76427
|
-
|
|
76428
|
-
|
|
76429
|
-
models
|
|
76430
|
-
|
|
76729
|
+
let provider;
|
|
76730
|
+
try {
|
|
76731
|
+
provider = buildProvider(settings.models.model, auth, {
|
|
76732
|
+
sessionId: session.meta.sessionId,
|
|
76733
|
+
models
|
|
76734
|
+
});
|
|
76735
|
+
} catch (error) {
|
|
76736
|
+
if (!(signedOut && error instanceof MissingCredential)) throw error;
|
|
76737
|
+
provider = new DeferredProvider(settings.models.model, auth, session.meta.sessionId, models);
|
|
76738
|
+
}
|
|
76431
76739
|
const sandbox = settings.permissions.sandbox ? new Sandbox(defaultPolicy(settings.cwd, settings.permissions.allowNetwork)) : null;
|
|
76432
76740
|
const shell = new PersistentShell(settings.cwd, settings.bash.shell, {}, sandbox);
|
|
76433
76741
|
const jobs = new BackgroundJobs(join(sessionOutputsDir(session.meta.sessionId), "jobs"));
|
|
@@ -76460,6 +76768,13 @@ function buildRuntime(parsed, resume = null) {
|
|
|
76460
76768
|
`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
76769
|
);
|
|
76462
76770
|
}
|
|
76771
|
+
if (settings.bash.shell && posixShell(settings.bash.shell) !== settings.bash.shell)
|
|
76772
|
+
notices.push(
|
|
76773
|
+
`bash.shell is ${settings.bash.shell}, which does not speak POSIX sh; commands run in ${shell.shell}.`
|
|
76774
|
+
);
|
|
76775
|
+
const [mcpServers, waitingServers] = partition(loadConfigs(settings.cwd), settings.cwd);
|
|
76776
|
+
const waiting = waitingServers.filter((server) => server.config.enabled);
|
|
76777
|
+
for (const server of waiting) notices.push(waitingNotice(server));
|
|
76463
76778
|
const agents = new Map(discover2(settings.cwd).map((agent) => [agent.name, agent]));
|
|
76464
76779
|
const subagents = new SubagentRunner({
|
|
76465
76780
|
definitions: agents,
|
|
@@ -76520,7 +76835,7 @@ function buildRuntime(parsed, resume = null) {
|
|
|
76520
76835
|
tools,
|
|
76521
76836
|
checkpoints,
|
|
76522
76837
|
hooks,
|
|
76523
|
-
new MCPManager(
|
|
76838
|
+
new MCPManager(mcpServers, void 0, waiting),
|
|
76524
76839
|
notices
|
|
76525
76840
|
);
|
|
76526
76841
|
}
|
|
@@ -76540,7 +76855,7 @@ ${listed2}`,
|
|
|
76540
76855
|
|
|
76541
76856
|
// src/trace.ts
|
|
76542
76857
|
import { spawn as spawn8 } from "node:child_process";
|
|
76543
|
-
import { mkdirSync as
|
|
76858
|
+
import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync10 } from "node:fs";
|
|
76544
76859
|
import { pathToFileURL } from "node:url";
|
|
76545
76860
|
init_messages();
|
|
76546
76861
|
init_json();
|
|
@@ -76584,8 +76899,8 @@ function renderHtml(trace) {
|
|
|
76584
76899
|
return trace_template_default.replace(/__HX_TRACE_(?:TITLE|DATA)__/g, (match4) => filled[match4]);
|
|
76585
76900
|
}
|
|
76586
76901
|
function writeTrace(trace, path) {
|
|
76587
|
-
|
|
76588
|
-
|
|
76902
|
+
mkdirSync14(parent(path), { recursive: true });
|
|
76903
|
+
writeFileSync10(path, renderHtml(trace), "utf8");
|
|
76589
76904
|
return path;
|
|
76590
76905
|
}
|
|
76591
76906
|
var defaultTracePath = (session) => join(sessionDir(session.meta.sessionId), TRACE_FILENAME);
|
|
@@ -77883,8 +78198,8 @@ var ProcessTerminal = class {
|
|
|
77883
78198
|
let buffer = Buffer.from(data);
|
|
77884
78199
|
while (buffer.length) {
|
|
77885
78200
|
try {
|
|
77886
|
-
const
|
|
77887
|
-
buffer = buffer.subarray(
|
|
78201
|
+
const written2 = writeSync5(1, buffer);
|
|
78202
|
+
buffer = buffer.subarray(written2);
|
|
77888
78203
|
} catch (error) {
|
|
77889
78204
|
if (error.code === "EAGAIN") continue;
|
|
77890
78205
|
return;
|
|
@@ -78292,13 +78607,302 @@ var ShiftEnter = class {
|
|
|
78292
78607
|
}
|
|
78293
78608
|
};
|
|
78294
78609
|
|
|
78610
|
+
// src/term/component.ts
|
|
78611
|
+
var Widget = class {
|
|
78612
|
+
cache = [];
|
|
78613
|
+
cacheWidth = -1;
|
|
78614
|
+
isDirty = true;
|
|
78615
|
+
render(width) {
|
|
78616
|
+
if (this.isDirty || width !== this.cacheWidth) {
|
|
78617
|
+
this.cache = this.draw(width);
|
|
78618
|
+
this.cacheWidth = width;
|
|
78619
|
+
this.isDirty = false;
|
|
78620
|
+
}
|
|
78621
|
+
return this.cache;
|
|
78622
|
+
}
|
|
78623
|
+
invalidate() {
|
|
78624
|
+
this.isDirty = true;
|
|
78625
|
+
}
|
|
78626
|
+
get dirty() {
|
|
78627
|
+
return this.isDirty;
|
|
78628
|
+
}
|
|
78629
|
+
};
|
|
78630
|
+
var Container = class extends Widget {
|
|
78631
|
+
children;
|
|
78632
|
+
constructor(...children) {
|
|
78633
|
+
super();
|
|
78634
|
+
this.children = children;
|
|
78635
|
+
}
|
|
78636
|
+
add(child) {
|
|
78637
|
+
this.children.push(child);
|
|
78638
|
+
this.invalidate();
|
|
78639
|
+
return child;
|
|
78640
|
+
}
|
|
78641
|
+
extend(children) {
|
|
78642
|
+
this.children.push(...children);
|
|
78643
|
+
this.invalidate();
|
|
78644
|
+
}
|
|
78645
|
+
clear() {
|
|
78646
|
+
this.children = [];
|
|
78647
|
+
this.invalidate();
|
|
78648
|
+
}
|
|
78649
|
+
remove(child) {
|
|
78650
|
+
const index = this.children.indexOf(child);
|
|
78651
|
+
if (index >= 0) {
|
|
78652
|
+
this.children.splice(index, 1);
|
|
78653
|
+
this.invalidate();
|
|
78654
|
+
}
|
|
78655
|
+
}
|
|
78656
|
+
/** Concatenate the children, asking each: a clean child returns its cache,
|
|
78657
|
+
* so a frame costs what actually changed. */
|
|
78658
|
+
render(width) {
|
|
78659
|
+
const lines2 = [];
|
|
78660
|
+
for (const child of this.children) lines2.push(...child.render(width));
|
|
78661
|
+
this.isDirty = false;
|
|
78662
|
+
return lines2;
|
|
78663
|
+
}
|
|
78664
|
+
draw(width) {
|
|
78665
|
+
return this.render(width);
|
|
78666
|
+
}
|
|
78667
|
+
/** Offer the key to each child until one takes it. */
|
|
78668
|
+
handleInput(key2, data) {
|
|
78669
|
+
for (const child of this.children) {
|
|
78670
|
+
const handler = child.handleInput;
|
|
78671
|
+
if (handler?.call(child, key2, data)) return true;
|
|
78672
|
+
}
|
|
78673
|
+
return false;
|
|
78674
|
+
}
|
|
78675
|
+
};
|
|
78676
|
+
|
|
78677
|
+
// src/term/primitives.ts
|
|
78678
|
+
var Text = class extends Widget {
|
|
78679
|
+
constructor(content = "", paddingX = 1, paddingY = 0, tint2 = null) {
|
|
78680
|
+
super();
|
|
78681
|
+
this.content = content;
|
|
78682
|
+
this.paddingX = paddingX;
|
|
78683
|
+
this.paddingY = paddingY;
|
|
78684
|
+
this.tint = tint2;
|
|
78685
|
+
}
|
|
78686
|
+
content;
|
|
78687
|
+
paddingX;
|
|
78688
|
+
paddingY;
|
|
78689
|
+
tint;
|
|
78690
|
+
get text() {
|
|
78691
|
+
return this.content;
|
|
78692
|
+
}
|
|
78693
|
+
setText(text3) {
|
|
78694
|
+
if (text3 !== this.content) {
|
|
78695
|
+
this.content = text3;
|
|
78696
|
+
this.invalidate();
|
|
78697
|
+
}
|
|
78698
|
+
}
|
|
78699
|
+
draw(width) {
|
|
78700
|
+
const inner = Math.max(1, width - 2 * this.paddingX);
|
|
78701
|
+
const pad = " ".repeat(this.paddingX);
|
|
78702
|
+
const body = wrap2(this.content, inner).map((line) => `${pad}${line}`);
|
|
78703
|
+
const blank = Array(this.paddingY).fill("");
|
|
78704
|
+
return [...blank, ...body, ...blank].map(
|
|
78705
|
+
(line) => this.tint === null ? fillLine(line, width) : terminate(this.tint(padTo(line, width)))
|
|
78706
|
+
);
|
|
78707
|
+
}
|
|
78708
|
+
};
|
|
78709
|
+
var COLUMN = /^(\s*\S.*?[^\s.!?]\s{2,})\S/;
|
|
78710
|
+
function wrapAligned(paragraph2, width) {
|
|
78711
|
+
const lines2 = wrap2(paragraph2, width);
|
|
78712
|
+
if (lines2.length < 2) return lines2;
|
|
78713
|
+
const plain2 = stripAnsi(paragraph2);
|
|
78714
|
+
const column = COLUMN.exec(plain2);
|
|
78715
|
+
const lead = column ? cellWidth(column[1]) : plain2.length - plain2.replace(/^ +/, "").length;
|
|
78716
|
+
if (!(lead > 0 && lead <= Math.floor(width / 2))) return lines2;
|
|
78717
|
+
const [head4, body] = splitAtCell(paragraph2, lead);
|
|
78718
|
+
const rest = wrap2(body, width - lead);
|
|
78719
|
+
return [head4 + rest[0], ...rest.slice(1).map((line) => " ".repeat(lead) + line)];
|
|
78720
|
+
}
|
|
78721
|
+
function splitAtCell(text3, cells) {
|
|
78722
|
+
const tokens = tokenize(text3);
|
|
78723
|
+
let seen = 0;
|
|
78724
|
+
for (const [index, [isEscape, chunk]] of tokens.entries()) {
|
|
78725
|
+
if (isEscape) continue;
|
|
78726
|
+
if (seen >= cells) {
|
|
78727
|
+
const head4 = tokens.slice(0, index);
|
|
78728
|
+
const style = head4.filter(([isEscape2]) => isEscape2).map(([, c]) => c).join("");
|
|
78729
|
+
return [
|
|
78730
|
+
head4.map(([, c]) => c).join(""),
|
|
78731
|
+
style + tokens.slice(index).map(([, c]) => c).join("")
|
|
78732
|
+
];
|
|
78733
|
+
}
|
|
78734
|
+
seen += cellWidth(chunk);
|
|
78735
|
+
}
|
|
78736
|
+
return [text3, ""];
|
|
78737
|
+
}
|
|
78738
|
+
var HangingText = class extends Widget {
|
|
78739
|
+
constructor(prefix, body, paddingX = 1, tint2 = null) {
|
|
78740
|
+
super();
|
|
78741
|
+
this.prefix = prefix;
|
|
78742
|
+
this.body = body;
|
|
78743
|
+
this.paddingX = paddingX;
|
|
78744
|
+
this.tint = tint2;
|
|
78745
|
+
this.prefixWidth = cellWidth(prefix);
|
|
78746
|
+
}
|
|
78747
|
+
prefix;
|
|
78748
|
+
body;
|
|
78749
|
+
paddingX;
|
|
78750
|
+
tint;
|
|
78751
|
+
prefixWidth;
|
|
78752
|
+
setBody(body) {
|
|
78753
|
+
if (body !== this.body) {
|
|
78754
|
+
this.body = body;
|
|
78755
|
+
this.invalidate();
|
|
78756
|
+
}
|
|
78757
|
+
}
|
|
78758
|
+
draw(width) {
|
|
78759
|
+
const pad = " ".repeat(this.paddingX);
|
|
78760
|
+
const inner = Math.max(1, width - 2 * this.paddingX - this.prefixWidth);
|
|
78761
|
+
const hang = " ".repeat(this.prefixWidth);
|
|
78762
|
+
const out = [];
|
|
78763
|
+
for (const paragraph2 of this.body.split("\n")) {
|
|
78764
|
+
wrapAligned(paragraph2, inner).forEach((line, index) => {
|
|
78765
|
+
out.push(`${pad}${out.length === 0 && index === 0 ? this.prefix : hang}${line}`);
|
|
78766
|
+
});
|
|
78767
|
+
}
|
|
78768
|
+
return out.map(
|
|
78769
|
+
(line) => this.tint === null ? fillLine(line, width) : terminate(this.tint(padTo(line, width)))
|
|
78770
|
+
);
|
|
78771
|
+
}
|
|
78772
|
+
};
|
|
78773
|
+
var Box = class extends Widget {
|
|
78774
|
+
constructor(paddingX = 1, paddingY = 1, tint2 = null, ...children) {
|
|
78775
|
+
super();
|
|
78776
|
+
this.paddingX = paddingX;
|
|
78777
|
+
this.paddingY = paddingY;
|
|
78778
|
+
this.tint = tint2;
|
|
78779
|
+
this.children = children;
|
|
78780
|
+
}
|
|
78781
|
+
paddingX;
|
|
78782
|
+
paddingY;
|
|
78783
|
+
tint;
|
|
78784
|
+
children;
|
|
78785
|
+
add(child) {
|
|
78786
|
+
this.children.push(child);
|
|
78787
|
+
this.invalidate();
|
|
78788
|
+
return child;
|
|
78789
|
+
}
|
|
78790
|
+
clear() {
|
|
78791
|
+
this.children = [];
|
|
78792
|
+
this.invalidate();
|
|
78793
|
+
}
|
|
78794
|
+
render(width) {
|
|
78795
|
+
const inner = Math.max(1, width - 2 * this.paddingX);
|
|
78796
|
+
const pad = " ".repeat(this.paddingX);
|
|
78797
|
+
const body = this.children.flatMap(
|
|
78798
|
+
(child) => child.render(inner).map((line) => `${pad}${stripTerminator(line)}`)
|
|
78799
|
+
);
|
|
78800
|
+
const blank = Array(this.paddingY).fill("");
|
|
78801
|
+
return [...blank, ...body, ...blank].map(
|
|
78802
|
+
(line) => this.tint === null ? fillLine(line, width) : terminate(this.tint(padTo(line, width)))
|
|
78803
|
+
);
|
|
78804
|
+
}
|
|
78805
|
+
draw(width) {
|
|
78806
|
+
return this.render(width);
|
|
78807
|
+
}
|
|
78808
|
+
};
|
|
78809
|
+
var Spacer = class extends Widget {
|
|
78810
|
+
constructor(lines2 = 1) {
|
|
78811
|
+
super();
|
|
78812
|
+
this.lines = lines2;
|
|
78813
|
+
}
|
|
78814
|
+
lines;
|
|
78815
|
+
draw() {
|
|
78816
|
+
return Array(this.lines).fill("");
|
|
78817
|
+
}
|
|
78818
|
+
};
|
|
78819
|
+
var Rule = class extends Widget {
|
|
78820
|
+
constructor(color2 = null, char = "\u2500") {
|
|
78821
|
+
super();
|
|
78822
|
+
this.color = color2;
|
|
78823
|
+
this.char = char;
|
|
78824
|
+
}
|
|
78825
|
+
color;
|
|
78826
|
+
char;
|
|
78827
|
+
/** What the rule is painted with. */
|
|
78828
|
+
get paint() {
|
|
78829
|
+
return this.color;
|
|
78830
|
+
}
|
|
78831
|
+
setColor(color2) {
|
|
78832
|
+
this.color = color2;
|
|
78833
|
+
this.invalidate();
|
|
78834
|
+
}
|
|
78835
|
+
draw(width) {
|
|
78836
|
+
const line = this.char.repeat(Math.max(1, width));
|
|
78837
|
+
return [terminate(this.color ? this.color(line) : line)];
|
|
78838
|
+
}
|
|
78839
|
+
};
|
|
78840
|
+
var LabelledRule = class extends Rule {
|
|
78841
|
+
constructor(color2 = null, label = "", align = "left", char = "\u2500") {
|
|
78842
|
+
super(color2, char);
|
|
78843
|
+
this.label = label;
|
|
78844
|
+
this.align = align;
|
|
78845
|
+
}
|
|
78846
|
+
label;
|
|
78847
|
+
align;
|
|
78848
|
+
setLabel(label) {
|
|
78849
|
+
if (label !== this.label) {
|
|
78850
|
+
this.label = label;
|
|
78851
|
+
this.invalidate();
|
|
78852
|
+
}
|
|
78853
|
+
}
|
|
78854
|
+
draw(width) {
|
|
78855
|
+
const w = Math.max(1, width);
|
|
78856
|
+
if (!this.label) return super.draw(w);
|
|
78857
|
+
const label = ` ${this.label} `;
|
|
78858
|
+
const labelWidth = cellWidth(label);
|
|
78859
|
+
if (labelWidth + 4 > w) return super.draw(w);
|
|
78860
|
+
const left = this.align === "center" ? Math.floor((w - labelWidth) / 2) : 2;
|
|
78861
|
+
const right = w - labelWidth - left;
|
|
78862
|
+
const paint = this.color ?? ((text3) => text3);
|
|
78863
|
+
return [terminate(paint(this.char.repeat(left)) + label + paint(this.char.repeat(right)))];
|
|
78864
|
+
}
|
|
78865
|
+
};
|
|
78866
|
+
var Lines = class extends Widget {
|
|
78867
|
+
constructor(lines2, paddingX = 1, tint2 = null) {
|
|
78868
|
+
super();
|
|
78869
|
+
this.lines = lines2;
|
|
78870
|
+
this.paddingX = paddingX;
|
|
78871
|
+
this.tint = tint2;
|
|
78872
|
+
}
|
|
78873
|
+
lines;
|
|
78874
|
+
paddingX;
|
|
78875
|
+
tint;
|
|
78876
|
+
setLines(lines2) {
|
|
78877
|
+
this.lines = lines2;
|
|
78878
|
+
this.invalidate();
|
|
78879
|
+
}
|
|
78880
|
+
draw(width) {
|
|
78881
|
+
const pad = " ".repeat(this.paddingX);
|
|
78882
|
+
const inner = Math.max(1, width - 2 * this.paddingX);
|
|
78883
|
+
return this.lines.map((line) => `${pad}${truncateToWidth(stripTerminator(line), inner)}`).map(
|
|
78884
|
+
(line) => this.tint === null ? fillLine(line, width) : terminate(this.tint(padTo(line, width)))
|
|
78885
|
+
);
|
|
78886
|
+
}
|
|
78887
|
+
};
|
|
78888
|
+
var stripTerminator = (line) => line.endsWith(SEGMENT_RESET) ? line.slice(0, -SEGMENT_RESET.length) : line;
|
|
78889
|
+
function padTo(line, width) {
|
|
78890
|
+
let fitted = line;
|
|
78891
|
+
let used2 = cellWidth(fitted);
|
|
78892
|
+
if (used2 > width) {
|
|
78893
|
+
fitted = truncateToWidth(fitted, width);
|
|
78894
|
+
used2 = cellWidth(fitted);
|
|
78895
|
+
}
|
|
78896
|
+
return fitted + " ".repeat(Math.max(0, width - used2));
|
|
78897
|
+
}
|
|
78898
|
+
|
|
78295
78899
|
// src/tui/clipboard.ts
|
|
78296
78900
|
init_images();
|
|
78297
78901
|
init_pathlib();
|
|
78298
78902
|
init_py();
|
|
78299
78903
|
import { spawn as spawn9 } from "node:child_process";
|
|
78300
78904
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
78301
|
-
import { readFileSync as readFileSync9, rmSync as rmSync5, statSync as
|
|
78905
|
+
import { readFileSync as readFileSync9, rmSync as rmSync5, statSync as statSync15, writeFileSync as writeFileSync11 } from "node:fs";
|
|
78302
78906
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
78303
78907
|
var MAX_OSC52_ENCODED = 1e5;
|
|
78304
78908
|
var TIMEOUT_MS = 5e3;
|
|
@@ -78403,7 +79007,7 @@ async function readImage2(platform = process.platform, env = process.env) {
|
|
|
78403
79007
|
}
|
|
78404
79008
|
async function withScratch(fn) {
|
|
78405
79009
|
const target = join(tmpdir3(), `hx-clipboard-${randomBytes8(4).toString("hex")}.img`);
|
|
78406
|
-
|
|
79010
|
+
writeFileSync11(target, "");
|
|
78407
79011
|
try {
|
|
78408
79012
|
return await fn(target);
|
|
78409
79013
|
} finally {
|
|
@@ -78465,7 +79069,7 @@ async function readLinux(env) {
|
|
|
78465
79069
|
function copiedFile(path) {
|
|
78466
79070
|
if (!isImagePath(path)) return null;
|
|
78467
79071
|
try {
|
|
78468
|
-
if (
|
|
79072
|
+
if (statSync15(path).size > MAX_SOURCE_BYTES)
|
|
78469
79073
|
throw new ClipboardError(`${basename(path)} is too large to attach`);
|
|
78470
79074
|
return { data: readFileSync9(path), name: basename(path) };
|
|
78471
79075
|
} catch (error) {
|
|
@@ -78481,7 +79085,7 @@ init_resolve();
|
|
|
78481
79085
|
init_store();
|
|
78482
79086
|
init_config();
|
|
78483
79087
|
init_context();
|
|
78484
|
-
import { statSync as
|
|
79088
|
+
import { statSync as statSync16 } from "node:fs";
|
|
78485
79089
|
init_usage();
|
|
78486
79090
|
init_json();
|
|
78487
79091
|
init_net();
|
|
@@ -78944,295 +79548,6 @@ var THEME = new Theme();
|
|
|
78944
79548
|
// src/tui/views/login.ts
|
|
78945
79549
|
init_py();
|
|
78946
79550
|
|
|
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
79551
|
// src/tui/format.ts
|
|
79237
79552
|
init_py();
|
|
79238
79553
|
|
|
@@ -86255,7 +86570,7 @@ async function cmdAgents(ctx, _args) {
|
|
|
86255
86570
|
}
|
|
86256
86571
|
async function cmdMcp(ctx, args) {
|
|
86257
86572
|
const manager = ctx.app.mcp;
|
|
86258
|
-
if (manager === null || !manager.configs.length) {
|
|
86573
|
+
if (manager === null || !manager.configs.length && !manager.waiting.length) {
|
|
86259
86574
|
ctx.app.notice("No MCP servers configured. Add one with `hx mcp add`.", "warning");
|
|
86260
86575
|
return;
|
|
86261
86576
|
}
|
|
@@ -86297,7 +86612,10 @@ async function cmdMcp(ctx, args) {
|
|
|
86297
86612
|
lines2.push(` ${ljust(status3.name, 20)} ${status3.error} - /mcp login ${status3.name}`);
|
|
86298
86613
|
else lines2.push(` ${ljust(status3.name, 20)} unavailable - ${status3.error}`);
|
|
86299
86614
|
}
|
|
86300
|
-
|
|
86615
|
+
for (const { config: config2 } of manager.waiting)
|
|
86616
|
+
lines2.push(` ${ljust(config2.name, 20)} not approved - run \`hx mcp approve ${config2.name}\``);
|
|
86617
|
+
const healthy = manager.status().every((s) => s.connected) && !manager.waiting.length;
|
|
86618
|
+
ctx.app.notice(lines2.join("\n"), healthy ? "info" : "warning");
|
|
86301
86619
|
}
|
|
86302
86620
|
function onlyServerNeedingLogin(manager) {
|
|
86303
86621
|
const waiting = manager.status().filter((s) => s.needsLogin);
|
|
@@ -86663,18 +86981,18 @@ async function cmdCopy(ctx, _args) {
|
|
|
86663
86981
|
}
|
|
86664
86982
|
async function cmdTrace(ctx, args) {
|
|
86665
86983
|
const [target, openAfter] = traceArgs(args);
|
|
86666
|
-
let
|
|
86984
|
+
let written2;
|
|
86667
86985
|
try {
|
|
86668
|
-
|
|
86986
|
+
written2 = writeSessionTrace(ctx, target);
|
|
86669
86987
|
} catch (error) {
|
|
86670
86988
|
if (!error.code) throw error;
|
|
86671
86989
|
ctx.app.notice(`Could not write the trace: ${osErrorText(error)}`, "error");
|
|
86672
86990
|
return;
|
|
86673
86991
|
}
|
|
86674
|
-
const opened = openAfter && await openInBrowser(
|
|
86992
|
+
const opened = openAfter && await openInBrowser(written2);
|
|
86675
86993
|
const trailer = openAfter && !opened ? " No browser could be launched here; open it yourself." : "";
|
|
86676
86994
|
ctx.app.notice(
|
|
86677
|
-
`Trace written to ${
|
|
86995
|
+
`Trace written to ${written2} (${formatBytes(statSync16(written2).size)}).${trailer}`,
|
|
86678
86996
|
"success"
|
|
86679
86997
|
);
|
|
86680
86998
|
}
|
|
@@ -87771,7 +88089,7 @@ var PermissionPrompt = class extends Framed {
|
|
|
87771
88089
|
|
|
87772
88090
|
// src/tui/views/prompt.ts
|
|
87773
88091
|
init_images();
|
|
87774
|
-
import { statSync as
|
|
88092
|
+
import { statSync as statSync17 } from "node:fs";
|
|
87775
88093
|
init_pathlib();
|
|
87776
88094
|
init_py();
|
|
87777
88095
|
|
|
@@ -88249,7 +88567,7 @@ var FileCompleter = class _FileCompleter {
|
|
|
88249
88567
|
}
|
|
88250
88568
|
const mtime3 = (path) => {
|
|
88251
88569
|
try {
|
|
88252
|
-
return
|
|
88570
|
+
return statSync17(path).mtimeMs;
|
|
88253
88571
|
} catch {
|
|
88254
88572
|
return 0;
|
|
88255
88573
|
}
|
|
@@ -89042,6 +89360,13 @@ var HintsBar = class extends Widget {
|
|
|
89042
89360
|
return [fillLine(` ${parts2.join(fg2("dim", SEPARATOR2))}`, width)];
|
|
89043
89361
|
}
|
|
89044
89362
|
};
|
|
89363
|
+
var LOGO = [
|
|
89364
|
+
" __ __ _ __",
|
|
89365
|
+
" / / / / | |/ /",
|
|
89366
|
+
" / /_/ / | /",
|
|
89367
|
+
" / __ / / |",
|
|
89368
|
+
"/_/ /_/ /_/|_|"
|
|
89369
|
+
];
|
|
89045
89370
|
var Header = class extends Widget {
|
|
89046
89371
|
constructor(version = "", quiet = false) {
|
|
89047
89372
|
super();
|
|
@@ -89051,6 +89376,7 @@ var Header = class extends Widget {
|
|
|
89051
89376
|
quiet;
|
|
89052
89377
|
versionText;
|
|
89053
89378
|
isExpanded = false;
|
|
89379
|
+
showLogo = false;
|
|
89054
89380
|
get version() {
|
|
89055
89381
|
return this.versionText;
|
|
89056
89382
|
}
|
|
@@ -89068,10 +89394,19 @@ var Header = class extends Widget {
|
|
|
89068
89394
|
toggle() {
|
|
89069
89395
|
this.expanded = !this.isExpanded;
|
|
89070
89396
|
}
|
|
89397
|
+
/** The logo above the title: on the first screen of someone not signed in. */
|
|
89398
|
+
get logo() {
|
|
89399
|
+
return this.showLogo;
|
|
89400
|
+
}
|
|
89401
|
+
set logo(value) {
|
|
89402
|
+
this.showLogo = value;
|
|
89403
|
+
this.invalidate();
|
|
89404
|
+
}
|
|
89071
89405
|
draw(width) {
|
|
89072
89406
|
if (this.quiet) return [];
|
|
89073
89407
|
const keys = keymap();
|
|
89074
|
-
const lines2 = [fg2("accent",
|
|
89408
|
+
const lines2 = this.showLogo ? [...LOGO.map((row) => fg2("accent", row, { bold: true })), ""] : [];
|
|
89409
|
+
lines2.push(fg2("accent", "hx", { bold: true }) + fg2("dim", ` v${this.versionText}`));
|
|
89075
89410
|
if (!this.isExpanded) {
|
|
89076
89411
|
const inner = Math.max(1, width - 2);
|
|
89077
89412
|
lines2.push(
|
|
@@ -89342,6 +89677,7 @@ var HXSession = class {
|
|
|
89342
89677
|
this.view.dock.hints.setHints(HINTS);
|
|
89343
89678
|
this.refreshStatus();
|
|
89344
89679
|
if (this.loop.session.activeMessages().length) this.replayTranscript();
|
|
89680
|
+
if (this.extra.signIn) this.showSignIn(this.extra.signIn);
|
|
89345
89681
|
if (this.loop.permissions !== null)
|
|
89346
89682
|
this.loop.permissions.asker = (request2) => this.askPermission(request2);
|
|
89347
89683
|
const events = this.consumeEvents();
|
|
@@ -89385,6 +89721,13 @@ var HXSession = class {
|
|
|
89385
89721
|
this.runner.requestRender();
|
|
89386
89722
|
}
|
|
89387
89723
|
}
|
|
89724
|
+
/** Say how to sign in, a row below the header, under the logo - unless a
|
|
89725
|
+
* resumed conversation is already on screen. */
|
|
89726
|
+
showSignIn(hint2) {
|
|
89727
|
+
if (!this.view.transcript.blocks.length) this.view.header.logo = true;
|
|
89728
|
+
this.view.transcript.add(new Spacer(1));
|
|
89729
|
+
this.view.transcript.add(new Notice(hint2, "warning"));
|
|
89730
|
+
}
|
|
89388
89731
|
// -- the surface the slash commands expect ---------------------------------
|
|
89389
89732
|
get status() {
|
|
89390
89733
|
return this.view.dock.status;
|
|
@@ -90352,7 +90695,6 @@ Usage:
|
|
|
90352
90695
|
hx auth [set|clear] Show or change the OpenRouter API key
|
|
90353
90696
|
hx docs [SECTION|--all] Print the manual, or one section of it
|
|
90354
90697
|
hx changelog [VERSION] Print what shipped in each version
|
|
90355
|
-
hx upgrade Update hx to the latest version
|
|
90356
90698
|
hx --version Show version
|
|
90357
90699
|
hx --help Show this message
|
|
90358
90700
|
|
|
@@ -90416,25 +90758,10 @@ ${USAGE}`, process.stderr);
|
|
|
90416
90758
|
return runMcpCommand(parsed.rest);
|
|
90417
90759
|
case "trace":
|
|
90418
90760
|
return runTraceCommand(parsed);
|
|
90419
|
-
case "upgrade":
|
|
90420
|
-
return runUpgradeCommand();
|
|
90421
90761
|
default:
|
|
90422
90762
|
return runTuiCommand(parsed);
|
|
90423
90763
|
}
|
|
90424
90764
|
}
|
|
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
90765
|
function parseArgs(args) {
|
|
90439
90766
|
const parsed = {
|
|
90440
90767
|
command: "tui",
|
|
@@ -90489,7 +90816,7 @@ function parseArgs(args) {
|
|
|
90489
90816
|
}
|
|
90490
90817
|
if (positional.length) {
|
|
90491
90818
|
const [head4, ...tail3] = positional;
|
|
90492
|
-
if (["resume", "prompt", "trace", "mcp", "
|
|
90819
|
+
if (["resume", "prompt", "trace", "mcp", "auth"].includes(head4)) {
|
|
90493
90820
|
parsed.command = head4;
|
|
90494
90821
|
parsed.rest = tail3;
|
|
90495
90822
|
if (head4 === "resume" && tail3.length) parsed.sessionId = tail3[0];
|
|
@@ -90952,25 +91279,28 @@ function runTraceCommand(parsed) {
|
|
|
90952
91279
|
destination = purePath(expandUser(target));
|
|
90953
91280
|
if (isDir(destination)) destination = join(destination, TRACE_FILENAME);
|
|
90954
91281
|
}
|
|
90955
|
-
let
|
|
91282
|
+
let written2;
|
|
90956
91283
|
try {
|
|
90957
|
-
|
|
91284
|
+
written2 = writeTrace(buildTrace(session), destination);
|
|
90958
91285
|
} catch (error) {
|
|
90959
91286
|
if (!error.code) throw error;
|
|
90960
91287
|
print(`error: could not write the trace: ${osErrorText(error)}`, process.stderr);
|
|
90961
91288
|
return 1;
|
|
90962
91289
|
}
|
|
90963
|
-
print(
|
|
91290
|
+
print(written2);
|
|
90964
91291
|
return 0;
|
|
90965
91292
|
}
|
|
90966
91293
|
var MCP_USAGE = `hx mcp list Show configured servers
|
|
90967
91294
|
hx mcp add NAME COMMAND [ARGS...] Add a stdio server
|
|
90968
91295
|
hx mcp add NAME --url URL [--header K:V]... Add an HTTP server
|
|
91296
|
+
hx mcp approve NAME Let a server from the project's .hx/mcp.json start
|
|
90969
91297
|
hx mcp login NAME Sign in to an HTTP server in the browser
|
|
90970
91298
|
hx mcp logout NAME Forget a server's sign-in
|
|
90971
91299
|
hx mcp remove NAME Remove a server
|
|
90972
91300
|
|
|
90973
91301
|
Add --user to write to ~/.hx/mcp.json instead of the project's .hx/mcp.json.
|
|
91302
|
+
A server in the project's .hx/mcp.json came with the project, so it starts only
|
|
91303
|
+
once approved: \`hx mcp add\` approves what it adds, and \`hx mcp approve\` the rest.
|
|
90974
91304
|
An HTTP server that asks for sign-in gets it over OAuth unless a --header
|
|
90975
91305
|
already sends Authorization.
|
|
90976
91306
|
`;
|
|
@@ -90989,7 +91319,14 @@ async function runMcpCommand(given) {
|
|
|
90989
91319
|
print(MCP_USAGE);
|
|
90990
91320
|
return 0;
|
|
90991
91321
|
}
|
|
90992
|
-
|
|
91322
|
+
const [startable, waiting] = partition(configs, cwd);
|
|
91323
|
+
for (const { config: config2, changed } of waiting) {
|
|
91324
|
+
const why = changed ? "changed since approved" : "not approved";
|
|
91325
|
+
print(
|
|
91326
|
+
`${ljust2(config2.name, 20)} ${why} - runs ${describeServer(config2)}; run \`hx mcp approve ${config2.name}\``
|
|
91327
|
+
);
|
|
91328
|
+
}
|
|
91329
|
+
for (const status3 of await probeServers(startable)) {
|
|
90993
91330
|
if (status3.needsLogin) {
|
|
90994
91331
|
print(`${ljust2(status3.name, 20)} ${status3.error} - run \`hx mcp login ${status3.name}\``);
|
|
90995
91332
|
continue;
|
|
@@ -91029,9 +91366,10 @@ ${MCP_USAGE}`, process.stderr);
|
|
|
91029
91366
|
});
|
|
91030
91367
|
}
|
|
91031
91368
|
print(`Added ${name} to ${saveConfig(config2, cwd, userLevel)}`);
|
|
91369
|
+
if (!userLevel) approve({ ...config2, source: "project" }, cwd);
|
|
91032
91370
|
return 0;
|
|
91033
91371
|
}
|
|
91034
|
-
if (args[0] === "
|
|
91372
|
+
if (args[0] === "approve") {
|
|
91035
91373
|
if (args.length < 2) {
|
|
91036
91374
|
print(MCP_USAGE, process.stderr);
|
|
91037
91375
|
return 2;
|
|
@@ -91041,6 +91379,33 @@ ${MCP_USAGE}`, process.stderr);
|
|
|
91041
91379
|
print(`No server named ${args[1]}.`, process.stderr);
|
|
91042
91380
|
return 1;
|
|
91043
91381
|
}
|
|
91382
|
+
if (server.source === "user") {
|
|
91383
|
+
print(`${server.name} is in your own ~/.hx/mcp.json; it needs no approval.`);
|
|
91384
|
+
return 0;
|
|
91385
|
+
}
|
|
91386
|
+
approve(server, cwd);
|
|
91387
|
+
const does = server.url ? "connects to" : "runs";
|
|
91388
|
+
print(`Approved ${server.name}. It ${does}: ${describeServer(server)}`);
|
|
91389
|
+
return 0;
|
|
91390
|
+
}
|
|
91391
|
+
if (args[0] === "login" || args[0] === "logout") {
|
|
91392
|
+
if (args.length < 2) {
|
|
91393
|
+
print(MCP_USAGE, process.stderr);
|
|
91394
|
+
return 2;
|
|
91395
|
+
}
|
|
91396
|
+
const configs = loadConfigs(cwd);
|
|
91397
|
+
const server = configs.find((c) => c.name === args[1]);
|
|
91398
|
+
if (server === void 0) {
|
|
91399
|
+
print(`No server named ${args[1]}.`, process.stderr);
|
|
91400
|
+
return 1;
|
|
91401
|
+
}
|
|
91402
|
+
if (args[0] === "login" && partition([server], cwd)[1].length) {
|
|
91403
|
+
print(
|
|
91404
|
+
`${server.name} has not been approved. Run \`hx mcp approve ${server.name}\` first.`,
|
|
91405
|
+
process.stderr
|
|
91406
|
+
);
|
|
91407
|
+
return 1;
|
|
91408
|
+
}
|
|
91044
91409
|
return args[0] === "login" ? mcpLogin2(server) : mcpLogout(server);
|
|
91045
91410
|
}
|
|
91046
91411
|
if (args[0] === "remove") {
|
|
@@ -91049,6 +91414,7 @@ ${MCP_USAGE}`, process.stderr);
|
|
|
91049
91414
|
return 2;
|
|
91050
91415
|
}
|
|
91051
91416
|
if (removeConfig(args[1], cwd, userLevel)) {
|
|
91417
|
+
if (!userLevel) revoke(args[1], cwd);
|
|
91052
91418
|
print(`Removed ${args[1]}.`);
|
|
91053
91419
|
return 0;
|
|
91054
91420
|
}
|
|
@@ -91132,17 +91498,12 @@ async function runTuiCommand(parsed) {
|
|
|
91132
91498
|
try {
|
|
91133
91499
|
resume = resumeTarget(parsed);
|
|
91134
91500
|
moved = adoptSessionDirectory(parsed, resume);
|
|
91135
|
-
runtime2 = buildRuntime(parsed, resume);
|
|
91501
|
+
runtime2 = buildRuntime(parsed, resume, true);
|
|
91136
91502
|
} 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
|
-
}
|
|
91503
|
+
return report(error);
|
|
91144
91504
|
}
|
|
91145
91505
|
if (moved) runtime2.notices.push(moved);
|
|
91506
|
+
const signIn2 = runtime2.provider instanceof DeferredProvider ? signInHint(runtime2.loop.model, runtime2.auth) : null;
|
|
91146
91507
|
let relaunch = null;
|
|
91147
91508
|
try {
|
|
91148
91509
|
await runtime2.connectMcp();
|
|
@@ -91157,7 +91518,8 @@ async function runTuiCommand(parsed) {
|
|
|
91157
91518
|
mcp: runtime2.mcp,
|
|
91158
91519
|
notices: runtime2.notices,
|
|
91159
91520
|
checkpoints: runtime2.checkpoints,
|
|
91160
|
-
renameLeft
|
|
91521
|
+
renameLeft,
|
|
91522
|
+
signIn: signIn2
|
|
91161
91523
|
});
|
|
91162
91524
|
} finally {
|
|
91163
91525
|
runtime2.bus.close();
|