@snowyroad/braid 0.74.0 → 0.76.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
InviteExpiredError,
|
|
23
23
|
KeystorePermsError,
|
|
24
24
|
RebootstrapError,
|
|
25
|
+
STRICT_IPC_ENV_STRIP,
|
|
25
26
|
acquireAgentLock,
|
|
26
27
|
assertKeystorePerms,
|
|
27
28
|
boundedJsonFetch,
|
|
@@ -46,16 +47,18 @@ import {
|
|
|
46
47
|
pickRejectOptionId,
|
|
47
48
|
pinEndpoint,
|
|
48
49
|
readPathWithFallback,
|
|
49
|
-
redactConfig,
|
|
50
50
|
redeemInvite,
|
|
51
51
|
relayHostSegment,
|
|
52
52
|
resolveAgentSelection,
|
|
53
53
|
resolveConfig,
|
|
54
|
+
resolveIpcProfile,
|
|
54
55
|
resolveKnobs,
|
|
56
|
+
resolveScope,
|
|
55
57
|
restoreTerminal,
|
|
56
58
|
sanitizeForTty,
|
|
57
59
|
saveAgent,
|
|
58
60
|
snapshotHttpMetrics,
|
|
61
|
+
startingLines,
|
|
59
62
|
takeDenialHint,
|
|
60
63
|
toolModeLabel,
|
|
61
64
|
toolStatusLine,
|
|
@@ -63,7 +66,7 @@ import {
|
|
|
63
66
|
validateReplacementToken,
|
|
64
67
|
verifyCardSignature,
|
|
65
68
|
wsToHttp
|
|
66
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-IBUDDALN.js";
|
|
67
70
|
|
|
68
71
|
// src/health.ts
|
|
69
72
|
var CEILING_STRIKES_TO_DEAD = 3;
|
|
@@ -2786,7 +2789,7 @@ var ActivityBeacon = class {
|
|
|
2786
2789
|
// src/adapter.ts
|
|
2787
2790
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
2788
2791
|
import { accessSync, constants, existsSync, statSync } from "fs";
|
|
2789
|
-
import { delimiter, dirname as dirname2, join as
|
|
2792
|
+
import { delimiter, dirname as dirname2, join as join2, resolve } from "path";
|
|
2790
2793
|
import { homedir } from "os";
|
|
2791
2794
|
|
|
2792
2795
|
// src/acp/client.ts
|
|
@@ -3529,323 +3532,9 @@ function modelLaunchFor(agent, model) {
|
|
|
3529
3532
|
// src/scope/sandbox.ts
|
|
3530
3533
|
import { SandboxManager, SandboxRuntimeConfigSchema, installWindowsSandbox, getSrtWinPath } from "@anthropic-ai/sandbox-runtime";
|
|
3531
3534
|
import path2 from "path";
|
|
3532
|
-
import { realpathSync
|
|
3535
|
+
import { realpathSync } from "fs";
|
|
3533
3536
|
import { spawnSync as nodeSpawnSync } from "child_process";
|
|
3534
3537
|
|
|
3535
|
-
// src/scope/scopeConfig.ts
|
|
3536
|
-
import { realpathSync, readdirSync, lstatSync } from "fs";
|
|
3537
|
-
import { execSync } from "child_process";
|
|
3538
|
-
import { join as join2 } from "path";
|
|
3539
|
-
function resolveIpcProfile(scope, flagValue) {
|
|
3540
|
-
if (flagValue !== void 0) return flagValue;
|
|
3541
|
-
if (scope?.ipcProfile !== void 0) return scope.ipcProfile;
|
|
3542
|
-
return "compat";
|
|
3543
|
-
}
|
|
3544
|
-
var STRICT_IPC_ENV_STRIP = [
|
|
3545
|
-
"SSH_AUTH_SOCK",
|
|
3546
|
-
"DOCKER_HOST",
|
|
3547
|
-
"DBUS_SESSION_BUS_ADDRESS",
|
|
3548
|
-
"DBUS_SYSTEM_BUS_ADDRESS",
|
|
3549
|
-
"GPG_AGENT_INFO",
|
|
3550
|
-
"DISPLAY",
|
|
3551
|
-
"WAYLAND_DISPLAY"
|
|
3552
|
-
];
|
|
3553
|
-
function strictIpcDenyRead(env, lstatFn = lstatSync) {
|
|
3554
|
-
const base = ["/run", "/var/run", "/tmp/.X11-unix"];
|
|
3555
|
-
const xdg = env["XDG_RUNTIME_DIR"];
|
|
3556
|
-
const candidates = xdg ? (() => {
|
|
3557
|
-
const segments = xdg.split("/").filter((s) => s.length > 0);
|
|
3558
|
-
if (!xdg.startsWith("/") || segments.length < 2) {
|
|
3559
|
-
console.warn(
|
|
3560
|
-
`[braid] warn: XDG_RUNTIME_DIR="${xdg}" failed the sanity gate (must be absolute, not "/", and have \u22652 path segments) \u2014 skipping from strict IPC denyRead shroud`
|
|
3561
|
-
);
|
|
3562
|
-
return base;
|
|
3563
|
-
}
|
|
3564
|
-
return [...base, xdg];
|
|
3565
|
-
})() : base;
|
|
3566
|
-
return candidates.filter((p) => {
|
|
3567
|
-
try {
|
|
3568
|
-
return !lstatFn(p).isSymbolicLink();
|
|
3569
|
-
} catch {
|
|
3570
|
-
return true;
|
|
3571
|
-
}
|
|
3572
|
-
});
|
|
3573
|
-
}
|
|
3574
|
-
var DEFAULT_ALLOWED_DOMAINS = [
|
|
3575
|
-
// Anthropic / Claude
|
|
3576
|
-
"api.anthropic.com",
|
|
3577
|
-
"*.anthropic.com",
|
|
3578
|
-
// OpenAI / Codex
|
|
3579
|
-
"api.openai.com",
|
|
3580
|
-
// Google / Gemini
|
|
3581
|
-
"generativelanguage.googleapis.com",
|
|
3582
|
-
"*.googleapis.com",
|
|
3583
|
-
// xAI / Grok
|
|
3584
|
-
"api.x.ai",
|
|
3585
|
-
// Cursor
|
|
3586
|
-
"api.cursor.sh",
|
|
3587
|
-
// opencode (BL-169): its serve process fetches the models.dev registry AT BOOT;
|
|
3588
|
-
// without it opencode dies inside the jail (`ServeError` / ACP connection closed
|
|
3589
|
-
// on every connect). `models.dev` is repro-CONFIRMED (blocked → 403, allowed →
|
|
3590
|
-
// 200 through the srt proxy). `opencode.ai` (opencode's own service) and the
|
|
3591
|
-
// ChatGPT-sign-in OAuth hosts are opencode's documented auth/boot endpoints
|
|
3592
|
-
// (from the 2026-07-02 dry-run that hard-failed opencode). claude-code was
|
|
3593
|
-
// unaffected because all its hosts were already listed above.
|
|
3594
|
-
"models.dev",
|
|
3595
|
-
"opencode.ai",
|
|
3596
|
-
"*.opencode.ai",
|
|
3597
|
-
"auth.openai.com",
|
|
3598
|
-
"chatgpt.com",
|
|
3599
|
-
// npm
|
|
3600
|
-
"registry.npmjs.org",
|
|
3601
|
-
"*.npmjs.org",
|
|
3602
|
-
// PyPI
|
|
3603
|
-
"pypi.org",
|
|
3604
|
-
"files.pythonhosted.org",
|
|
3605
|
-
// Go modules
|
|
3606
|
-
"proxy.golang.org",
|
|
3607
|
-
// Crates.io
|
|
3608
|
-
"crates.io",
|
|
3609
|
-
"static.crates.io",
|
|
3610
|
-
// Source hosts
|
|
3611
|
-
"github.com",
|
|
3612
|
-
"*.github.com",
|
|
3613
|
-
"*.githubusercontent.com",
|
|
3614
|
-
"gitlab.com",
|
|
3615
|
-
"bitbucket.org"
|
|
3616
|
-
];
|
|
3617
|
-
var PROVIDER_PATHS = {
|
|
3618
|
-
"claude-code": ["~/.claude", "~/.claude.json"],
|
|
3619
|
-
codex: ["~/.codex"],
|
|
3620
|
-
gemini: ["~/.gemini"],
|
|
3621
|
-
grok: ["~/.grok"],
|
|
3622
|
-
cursor: ["~/.cursor"],
|
|
3623
|
-
opencode: ["~/.local/share/opencode", "~/.config/opencode"],
|
|
3624
|
-
goose: ["~/.config/goose"],
|
|
3625
|
-
cline: ["~/.cline"],
|
|
3626
|
-
copilot: ["~/.copilot"],
|
|
3627
|
-
qwen: ["~/.qwen"]
|
|
3628
|
-
};
|
|
3629
|
-
function expandHome(p, home) {
|
|
3630
|
-
if (p.startsWith("~/")) return `${home}${p.slice(1)}`;
|
|
3631
|
-
if (p === "~") return home;
|
|
3632
|
-
return p;
|
|
3633
|
-
}
|
|
3634
|
-
function canonicalize(p) {
|
|
3635
|
-
try {
|
|
3636
|
-
return realpathSync(p);
|
|
3637
|
-
} catch {
|
|
3638
|
-
return p;
|
|
3639
|
-
}
|
|
3640
|
-
}
|
|
3641
|
-
function parseEnvList(val) {
|
|
3642
|
-
if (!val) return [];
|
|
3643
|
-
return val.split(/[,:]/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
3644
|
-
}
|
|
3645
|
-
function dedup(arr) {
|
|
3646
|
-
return [...new Set(arr)];
|
|
3647
|
-
}
|
|
3648
|
-
function darwinMdsCacheDir() {
|
|
3649
|
-
try {
|
|
3650
|
-
const cacheRoot = execSync("/usr/bin/getconf DARWIN_USER_CACHE_DIR", {
|
|
3651
|
-
encoding: "utf8"
|
|
3652
|
-
}).trim();
|
|
3653
|
-
if (!cacheRoot) return [];
|
|
3654
|
-
return [`${cacheRoot.replace(/\/+$/, "")}/mds`];
|
|
3655
|
-
} catch {
|
|
3656
|
-
return [];
|
|
3657
|
-
}
|
|
3658
|
-
}
|
|
3659
|
-
function resolvePaths(paths, home) {
|
|
3660
|
-
return dedup(paths.map((p) => canonicalize(expandHome(p, home))));
|
|
3661
|
-
}
|
|
3662
|
-
function foldCase(p, platform) {
|
|
3663
|
-
return platform === "darwin" || platform === "win32" ? p.toLowerCase() : p;
|
|
3664
|
-
}
|
|
3665
|
-
function checkGrantsAgainstReservations(grants, reservations, platform) {
|
|
3666
|
-
const sep2 = platform === "win32" ? "\\" : "/";
|
|
3667
|
-
const out = [];
|
|
3668
|
-
for (const g of grants) {
|
|
3669
|
-
const gf = foldCase(g.path, platform);
|
|
3670
|
-
for (const r of reservations) {
|
|
3671
|
-
const rf = foldCase(r, platform);
|
|
3672
|
-
if (gf === rf) {
|
|
3673
|
-
out.push({ grant: g.path, source: g.source, reservation: r, relation: "equal" });
|
|
3674
|
-
} else if (gf.startsWith(rf + sep2)) {
|
|
3675
|
-
out.push({ grant: g.path, source: g.source, reservation: r, relation: "descendant" });
|
|
3676
|
-
}
|
|
3677
|
-
}
|
|
3678
|
-
}
|
|
3679
|
-
return out;
|
|
3680
|
-
}
|
|
3681
|
-
function resolveScope(cfg, provider, env, platform = process.platform) {
|
|
3682
|
-
const scope = cfg.scope ?? {};
|
|
3683
|
-
const home = env["HOME"] ?? "";
|
|
3684
|
-
const cwd = env["PWD"] ?? process.cwd();
|
|
3685
|
-
const tmpDir = env["TMPDIR"] ?? "/tmp";
|
|
3686
|
-
const braidConfigDir = env["BRAID_CONFIG_DIR"]?.trim() || expandHome("~/.braid", home);
|
|
3687
|
-
const legacyArpConfigDir = expandHome("~/.arp", home);
|
|
3688
|
-
const protectedConfigDirs = braidConfigDir === legacyArpConfigDir ? [braidConfigDir] : [braidConfigDir, legacyArpConfigDir];
|
|
3689
|
-
const flagOverride = cfg.scopeFlags?.scopeOverride;
|
|
3690
|
-
const disabled = flagOverride !== void 0 ? flagOverride === "off" : scope.disabled === true || env["BRAID_SCOPE"] === "off";
|
|
3691
|
-
const secretDirs = [
|
|
3692
|
-
"~/.ssh",
|
|
3693
|
-
"~/.aws",
|
|
3694
|
-
"~/.gnupg",
|
|
3695
|
-
"~/.kube/config"
|
|
3696
|
-
].map((p) => expandHome(p, home));
|
|
3697
|
-
const protectedConfigDirEntries = protectedConfigDirs.flatMap((configDirPath) => {
|
|
3698
|
-
if (platform !== "win32") return [configDirPath];
|
|
3699
|
-
try {
|
|
3700
|
-
const children = readdirSync(configDirPath, { withFileTypes: true });
|
|
3701
|
-
const denied = children.filter((entry) => entry.name !== "runtime").map((entry) => canonicalize(join2(configDirPath, entry.name)));
|
|
3702
|
-
if (denied.length === 0) return [configDirPath];
|
|
3703
|
-
return denied;
|
|
3704
|
-
} catch {
|
|
3705
|
-
return [configDirPath];
|
|
3706
|
-
}
|
|
3707
|
-
});
|
|
3708
|
-
const providerPaths = PROVIDER_PATHS[provider].map((p) => expandHome(p, home));
|
|
3709
|
-
const uid = process.getuid?.();
|
|
3710
|
-
const claudeTmpRoot = provider === "claude-code" && uid !== void 0 ? [`${canonicalize("/tmp")}/claude-${uid}`, `${canonicalize("/tmp")}/claude-*-cwd`] : [];
|
|
3711
|
-
const mdsCacheDir = platform === "darwin" ? darwinMdsCacheDir() : [];
|
|
3712
|
-
const opAllowWrite = (scope.allowWrite ?? []).map((p) => expandHome(p, home));
|
|
3713
|
-
const envAllowWrite = parseEnvList(env["BRAID_SCOPE_ALLOW_WRITE"]).map((p) => expandHome(p, home));
|
|
3714
|
-
const flagAllowWrite = (cfg.scopeFlags?.allowWrite ?? []).map((p) => expandHome(p, home));
|
|
3715
|
-
const rawAllowWrite = [
|
|
3716
|
-
cwd,
|
|
3717
|
-
...providerPaths,
|
|
3718
|
-
...claudeTmpRoot,
|
|
3719
|
-
...mdsCacheDir,
|
|
3720
|
-
expandHome("~/.npm", home),
|
|
3721
|
-
tmpDir,
|
|
3722
|
-
...opAllowWrite,
|
|
3723
|
-
...envAllowWrite,
|
|
3724
|
-
...flagAllowWrite
|
|
3725
|
-
];
|
|
3726
|
-
const allowWrite = resolvePaths(rawAllowWrite, home);
|
|
3727
|
-
const rawDenyWrite = [
|
|
3728
|
-
...secretDirs,
|
|
3729
|
-
...protectedConfigDirEntries,
|
|
3730
|
-
...(scope.denyWrite ?? []).map((p) => expandHome(p, home)),
|
|
3731
|
-
...parseEnvList(env["BRAID_SCOPE_DENY_WRITE"]).map((p) => expandHome(p, home))
|
|
3732
|
-
];
|
|
3733
|
-
const denyWrite = resolvePaths(rawDenyWrite, home);
|
|
3734
|
-
const ipcProfile = resolveIpcProfile(scope, cfg.scopeFlags?.ipcProfile);
|
|
3735
|
-
const rawDenyRead = [
|
|
3736
|
-
...secretDirs,
|
|
3737
|
-
...protectedConfigDirEntries,
|
|
3738
|
-
...(scope.denyRead ?? []).map((p) => expandHome(p, home)),
|
|
3739
|
-
...parseEnvList(env["BRAID_SCOPE_DENY_READ"]).map((p) => expandHome(p, home))
|
|
3740
|
-
];
|
|
3741
|
-
const denyRead = dedup([
|
|
3742
|
-
...resolvePaths(rawDenyRead, home),
|
|
3743
|
-
...ipcProfile === "strict" && platform === "linux" ? strictIpcDenyRead(env) : []
|
|
3744
|
-
]);
|
|
3745
|
-
const opAllowRead = (scope.allowRead ?? []).map((p) => expandHome(p, home));
|
|
3746
|
-
const envAllowRead = parseEnvList(env["BRAID_SCOPE_ALLOW_READ"]).map((p) => expandHome(p, home));
|
|
3747
|
-
const flagAllowRead = (cfg.scopeFlags?.allowRead ?? []).map((p) => expandHome(p, home));
|
|
3748
|
-
const rawAllowRead = [
|
|
3749
|
-
...opAllowRead,
|
|
3750
|
-
...envAllowRead,
|
|
3751
|
-
...flagAllowRead
|
|
3752
|
-
];
|
|
3753
|
-
const allowRead = resolvePaths(rawAllowRead, home);
|
|
3754
|
-
if (!disabled) {
|
|
3755
|
-
const reservations = resolvePaths([...secretDirs, ...protectedConfigDirs], home);
|
|
3756
|
-
const labelled = (paths, source) => resolvePaths(paths, home).map((p) => ({ path: p, source }));
|
|
3757
|
-
const grants = [
|
|
3758
|
-
...labelled([cwd], "launch directory (cwd)"),
|
|
3759
|
-
...labelled(providerPaths, "provider default paths"),
|
|
3760
|
-
...labelled(opAllowWrite, "scope.allowWrite"),
|
|
3761
|
-
...labelled(envAllowWrite, "BRAID_SCOPE_ALLOW_WRITE"),
|
|
3762
|
-
...labelled(flagAllowWrite, "--allow-write"),
|
|
3763
|
-
...labelled(opAllowRead, "scope.allowRead"),
|
|
3764
|
-
...labelled(envAllowRead, "BRAID_SCOPE_ALLOW_READ"),
|
|
3765
|
-
...labelled(flagAllowRead, "--allow-read")
|
|
3766
|
-
];
|
|
3767
|
-
const conflicts = checkGrantsAgainstReservations(grants, reservations, platform);
|
|
3768
|
-
if (conflicts.length > 0) {
|
|
3769
|
-
const detail = conflicts.map((c) => ` - ${c.source}: "${c.grant}" is ${c.relation === "equal" ? "" : "inside "}the protected path "${c.reservation}"`).join("\n");
|
|
3770
|
-
throw new Error(
|
|
3771
|
-
`[braid] SCOPE ERROR: a grant covers a protected credential/secret path and was refused:
|
|
3772
|
-
${detail}
|
|
3773
|
-
Protected paths (never grantable): ${reservations.join(", ")}
|
|
3774
|
-
Narrow the grant to a path outside the protected set. (Ancestor grants like "~" are fine \u2014 the protected dirs are carved back out automatically.)`
|
|
3775
|
-
);
|
|
3776
|
-
}
|
|
3777
|
-
const writableDefaults = resolvePaths(
|
|
3778
|
-
[...providerPaths, expandHome("~/.npm", home), tmpDir],
|
|
3779
|
-
home
|
|
3780
|
-
);
|
|
3781
|
-
const resolvedConfigDir = canonicalize(braidConfigDir);
|
|
3782
|
-
const placement = checkGrantsAgainstReservations(
|
|
3783
|
-
[{ path: resolvedConfigDir, source: "BRAID_CONFIG_DIR" }],
|
|
3784
|
-
writableDefaults,
|
|
3785
|
-
platform
|
|
3786
|
-
);
|
|
3787
|
-
if (placement.length > 0) {
|
|
3788
|
-
const c = placement[0];
|
|
3789
|
-
throw new Error(
|
|
3790
|
-
`[braid] SCOPE ERROR: BRAID_CONFIG_DIR ("${c.grant}") is ${c.relation === "equal" ? "" : "inside "}a default provider-writable location ("${c.reservation}"). The bridge credential store must live outside provider/tmp/npm paths. Move it (default: ~/.braid) or unset BRAID_CONFIG_DIR.`
|
|
3791
|
-
);
|
|
3792
|
-
}
|
|
3793
|
-
}
|
|
3794
|
-
const allowedDomains = dedup([
|
|
3795
|
-
...DEFAULT_ALLOWED_DOMAINS,
|
|
3796
|
-
...scope.network?.allowedDomains ?? [],
|
|
3797
|
-
...cfg.derivedAllowedDomains ?? [],
|
|
3798
|
-
...parseEnvList(env["BRAID_SCOPE_ALLOW_DOMAINS"]),
|
|
3799
|
-
...cfg.scopeFlags?.allowDomains ?? []
|
|
3800
|
-
]);
|
|
3801
|
-
const deniedDomains = dedup([
|
|
3802
|
-
...scope.network?.deniedDomains ?? [],
|
|
3803
|
-
...parseEnvList(env["BRAID_SCOPE_DENY_DOMAINS"])
|
|
3804
|
-
]);
|
|
3805
|
-
function describe() {
|
|
3806
|
-
const lines = [];
|
|
3807
|
-
if (disabled) {
|
|
3808
|
-
lines.push("[scope] SANDBOX DISABLED (BRAID_SCOPE=off or scope.disabled:true) \u2014 agent runs unconfined");
|
|
3809
|
-
return lines;
|
|
3810
|
-
}
|
|
3811
|
-
lines.push("[scope] Agent sandbox is ACTIVE");
|
|
3812
|
-
lines.push(
|
|
3813
|
-
`[scope] protected (never readable/writable by the agent): ${resolvePaths([...secretDirs, ...protectedConfigDirs], home).join(", ")}`
|
|
3814
|
-
);
|
|
3815
|
-
lines.push(`[scope] cwd (writable): ${cwd}`);
|
|
3816
|
-
if (providerPaths.length > 0) {
|
|
3817
|
-
lines.push(`[scope] provider paths (writable): ${providerPaths.join(", ")}`);
|
|
3818
|
-
}
|
|
3819
|
-
lines.push(`[scope] allowed domains (${allowedDomains.length}): ${allowedDomains.slice(0, 5).join(", ")}${allowedDomains.length > 5 ? " ..." : ""}`);
|
|
3820
|
-
if ((cfg.derivedAllowedDomains ?? []).length > 0) {
|
|
3821
|
-
lines.push(`[scope] model endpoints (derived): ${(cfg.derivedAllowedDomains ?? []).join(", ")}`);
|
|
3822
|
-
}
|
|
3823
|
-
lines.push(`[scope] denied read: ${denyRead.join(", ")}`);
|
|
3824
|
-
if (denyWrite.length > 0) {
|
|
3825
|
-
lines.push(`[scope] denied write: ${denyWrite.join(", ")}`);
|
|
3826
|
-
}
|
|
3827
|
-
if (deniedDomains.length > 0) {
|
|
3828
|
-
lines.push(`[scope] denied domains: ${deniedDomains.join(", ")}`);
|
|
3829
|
-
}
|
|
3830
|
-
if (ipcProfile === "compat") {
|
|
3831
|
-
lines.push("[scope] ipc profile: compat \u2014 all unix sockets reachable inside the jail (Linux seccomp constraint)");
|
|
3832
|
-
} else {
|
|
3833
|
-
lines.push("[scope] ipc profile: strict \u2014 /run, /var/run, XDG_RUNTIME_DIR, /tmp/.X11-unix hidden; socket env vars stripped");
|
|
3834
|
-
}
|
|
3835
|
-
lines.push("[scope] To widen: add paths to scope.allowWrite/allowRead in agent config, or BRAID_SCOPE_ALLOW_WRITE/BRAID_SCOPE=off env.");
|
|
3836
|
-
return lines;
|
|
3837
|
-
}
|
|
3838
|
-
return {
|
|
3839
|
-
runtimeConfig: {
|
|
3840
|
-
filesystem: { allowWrite, denyWrite, allowRead, denyRead },
|
|
3841
|
-
network: { allowedDomains, deniedDomains }
|
|
3842
|
-
},
|
|
3843
|
-
disabled,
|
|
3844
|
-
ipcProfile,
|
|
3845
|
-
describe
|
|
3846
|
-
};
|
|
3847
|
-
}
|
|
3848
|
-
|
|
3849
3538
|
// src/mcp/broker.ts
|
|
3850
3539
|
import http from "http";
|
|
3851
3540
|
import os from "os";
|
|
@@ -4201,7 +3890,7 @@ function brokerSocketNetworkExtras() {
|
|
|
4201
3890
|
const dir = brokerSocketDir();
|
|
4202
3891
|
const subpaths = [dir];
|
|
4203
3892
|
try {
|
|
4204
|
-
const resolved = path2.join(
|
|
3893
|
+
const resolved = path2.join(realpathSync(path2.dirname(dir)), path2.basename(dir));
|
|
4205
3894
|
if (resolved !== dir) subpaths.push(resolved);
|
|
4206
3895
|
} catch {
|
|
4207
3896
|
}
|
|
@@ -4397,13 +4086,13 @@ function pinned(pkg) {
|
|
|
4397
4086
|
function computeNpxLaunch(platform, execPath, exists) {
|
|
4398
4087
|
const nodeDir = dirname2(execPath);
|
|
4399
4088
|
if (platform === "win32") {
|
|
4400
|
-
const npxCliJs =
|
|
4089
|
+
const npxCliJs = join2(nodeDir, "node_modules", "npm", "bin", "npx-cli.js");
|
|
4401
4090
|
if (exists(npxCliJs)) {
|
|
4402
4091
|
return { command: execPath, argsPrefix: [npxCliJs] };
|
|
4403
4092
|
}
|
|
4404
4093
|
}
|
|
4405
4094
|
for (const name of ["npx", "npx.cmd"]) {
|
|
4406
|
-
const candidate =
|
|
4095
|
+
const candidate = join2(nodeDir, name);
|
|
4407
4096
|
if (exists(candidate)) {
|
|
4408
4097
|
return { command: candidate, argsPrefix: [] };
|
|
4409
4098
|
}
|
|
@@ -4426,7 +4115,7 @@ function which(cmd, pathEnv = process.env.PATH ?? "", platform = process.platfor
|
|
|
4426
4115
|
const name = platform === "win32" && !cmd.toLowerCase().endsWith(".exe") ? `${cmd}.exe` : cmd;
|
|
4427
4116
|
for (const dir of pathEnv.split(delimiter)) {
|
|
4428
4117
|
if (!dir) continue;
|
|
4429
|
-
const candidate =
|
|
4118
|
+
const candidate = join2(dir, name);
|
|
4430
4119
|
try {
|
|
4431
4120
|
accessSync(candidate, constants.X_OK);
|
|
4432
4121
|
if (statSync(candidate).isFile()) return resolve(candidate);
|
|
@@ -4554,8 +4243,8 @@ function launchSpecFor(agent) {
|
|
|
4554
4243
|
}
|
|
4555
4244
|
}
|
|
4556
4245
|
function handoffFallbackDirs(name, home) {
|
|
4557
|
-
const dirs = [
|
|
4558
|
-
if (name === "claude") dirs.push(
|
|
4246
|
+
const dirs = [join2(home, ".local", "bin")];
|
|
4247
|
+
if (name === "claude") dirs.push(join2(home, ".claude", "local"));
|
|
4559
4248
|
return dirs;
|
|
4560
4249
|
}
|
|
4561
4250
|
function findHandoffBinary(name, pathEnv = process.env.PATH ?? "", home = homedir(), platform = process.platform) {
|
|
@@ -5171,8 +4860,8 @@ function withTimeout(p, ms) {
|
|
|
5171
4860
|
}
|
|
5172
4861
|
|
|
5173
4862
|
// src/cardFromDisk.ts
|
|
5174
|
-
import { readdirSync
|
|
5175
|
-
import { join as
|
|
4863
|
+
import { readdirSync, readFileSync as readFileSync2, lstatSync } from "fs";
|
|
4864
|
+
import { join as join3, relative, sep } from "path";
|
|
5176
4865
|
import { homedir as homedir2 } from "os";
|
|
5177
4866
|
|
|
5178
4867
|
// src/skillMd.ts
|
|
@@ -5226,22 +4915,22 @@ function parseSkillFrontmatter(raw) {
|
|
|
5226
4915
|
var MAX_DESC = 500;
|
|
5227
4916
|
var MAX_NAME = 100;
|
|
5228
4917
|
var MAX_SKILLS = 200;
|
|
5229
|
-
var skillsRoot = () => process.env.BRAID_SKILLS_DIR ??
|
|
5230
|
-
var pluginsRoot = () => process.env.BRAID_PLUGINS_DIR ??
|
|
4918
|
+
var skillsRoot = () => process.env.BRAID_SKILLS_DIR ?? join3(homedir2(), ".claude", "skills");
|
|
4919
|
+
var pluginsRoot = () => process.env.BRAID_PLUGINS_DIR ?? join3(homedir2(), ".claude", "plugins");
|
|
5231
4920
|
function collectSkillMd(root) {
|
|
5232
4921
|
const out = [];
|
|
5233
4922
|
const walk = (dir) => {
|
|
5234
4923
|
let entries;
|
|
5235
4924
|
try {
|
|
5236
|
-
entries =
|
|
4925
|
+
entries = readdirSync(dir);
|
|
5237
4926
|
} catch {
|
|
5238
4927
|
return;
|
|
5239
4928
|
}
|
|
5240
4929
|
for (const name of entries) {
|
|
5241
|
-
const full =
|
|
4930
|
+
const full = join3(dir, name);
|
|
5242
4931
|
let st;
|
|
5243
4932
|
try {
|
|
5244
|
-
st =
|
|
4933
|
+
st = lstatSync(full);
|
|
5245
4934
|
} catch {
|
|
5246
4935
|
continue;
|
|
5247
4936
|
}
|
|
@@ -5329,20 +5018,20 @@ import {
|
|
|
5329
5018
|
writeFileSync as writeFileSync2,
|
|
5330
5019
|
renameSync as renameSync2,
|
|
5331
5020
|
chmodSync as chmodSync2,
|
|
5332
|
-
readdirSync as
|
|
5021
|
+
readdirSync as readdirSync2,
|
|
5333
5022
|
statSync as statSync2,
|
|
5334
5023
|
existsSync as existsSync2
|
|
5335
5024
|
} from "fs";
|
|
5336
5025
|
import { createHash } from "crypto";
|
|
5337
5026
|
import { homedir as homedir3 } from "os";
|
|
5338
|
-
import { join as
|
|
5027
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
5339
5028
|
function safe2(s) {
|
|
5340
5029
|
return s.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
5341
5030
|
}
|
|
5342
5031
|
function cardCacheFilePath(cfg) {
|
|
5343
5032
|
const dir = configDir(process.env);
|
|
5344
5033
|
const safeName = safe2(cfg.agentName);
|
|
5345
|
-
return
|
|
5034
|
+
return join4(dir, "card-cache", relayHostSegment(cfg.relayWsUrl), `${safeName}.json`);
|
|
5346
5035
|
}
|
|
5347
5036
|
function cardCacheReadPath(cfg) {
|
|
5348
5037
|
return readPathWithFallback(
|
|
@@ -5356,12 +5045,12 @@ function collectSkillMdPaths(root) {
|
|
|
5356
5045
|
function walk(dir) {
|
|
5357
5046
|
let entries;
|
|
5358
5047
|
try {
|
|
5359
|
-
entries =
|
|
5048
|
+
entries = readdirSync2(dir);
|
|
5360
5049
|
} catch {
|
|
5361
5050
|
return;
|
|
5362
5051
|
}
|
|
5363
5052
|
for (const entry of entries) {
|
|
5364
|
-
const full =
|
|
5053
|
+
const full = join4(dir, entry);
|
|
5365
5054
|
let st;
|
|
5366
5055
|
try {
|
|
5367
5056
|
st = statSync2(full);
|
|
@@ -5378,7 +5067,7 @@ function collectSkillMdPaths(root) {
|
|
|
5378
5067
|
walk(root);
|
|
5379
5068
|
return out;
|
|
5380
5069
|
}
|
|
5381
|
-
function computeCapabilityFingerprint(cfg, skillsDir = process.env.BRAID_SKILLS_DIR ??
|
|
5070
|
+
function computeCapabilityFingerprint(cfg, skillsDir = process.env.BRAID_SKILLS_DIR ?? join4(homedir3(), ".claude", "skills"), pluginsDir = process.env.BRAID_PLUGINS_DIR ?? join4(homedir3(), ".claude", "plugins")) {
|
|
5382
5071
|
const paths1 = collectSkillMdPaths(skillsDir);
|
|
5383
5072
|
const paths2 = collectSkillMdPaths(pluginsDir);
|
|
5384
5073
|
const allPaths = [...paths1, ...paths2].sort();
|
|
@@ -5517,11 +5206,11 @@ function installCrashGuard() {
|
|
|
5517
5206
|
|
|
5518
5207
|
// src/service/needsHumanMarker.ts
|
|
5519
5208
|
import { mkdirSync as mkdirSync5, chmodSync as chmodSync4, writeFileSync as writeFileSync5, readFileSync as readFileSync6, existsSync as existsSync6, unlinkSync as unlinkSync2 } from "fs";
|
|
5520
|
-
import { join as
|
|
5209
|
+
import { join as join7 } from "path";
|
|
5521
5210
|
|
|
5522
5211
|
// src/service/install.ts
|
|
5523
|
-
import { existsSync as existsSync5, readdirSync as
|
|
5524
|
-
import { join as
|
|
5212
|
+
import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "fs";
|
|
5213
|
+
import { join as join6 } from "path";
|
|
5525
5214
|
|
|
5526
5215
|
// src/service/runtime.ts
|
|
5527
5216
|
import {
|
|
@@ -5529,30 +5218,30 @@ import {
|
|
|
5529
5218
|
mkdirSync as mkdirSync3,
|
|
5530
5219
|
readFileSync as readFileSync4,
|
|
5531
5220
|
writeFileSync as writeFileSync3,
|
|
5532
|
-
readdirSync as
|
|
5221
|
+
readdirSync as readdirSync3,
|
|
5533
5222
|
renameSync as renameSync3,
|
|
5534
5223
|
rmSync,
|
|
5535
5224
|
symlinkSync,
|
|
5536
5225
|
readlinkSync,
|
|
5537
|
-
lstatSync as
|
|
5226
|
+
lstatSync as lstatSync2,
|
|
5538
5227
|
existsSync as existsSync3
|
|
5539
5228
|
} from "fs";
|
|
5540
5229
|
import { execFileSync } from "child_process";
|
|
5541
5230
|
import { createHash as createHash2 } from "crypto";
|
|
5542
|
-
import { dirname as dirname4, join as
|
|
5231
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
5543
5232
|
import { homedir as homedir4 } from "os";
|
|
5544
5233
|
import { fileURLToPath } from "url";
|
|
5545
5234
|
function defaultBraidDir() {
|
|
5546
5235
|
const override = process.env.BRAID_CONFIG_DIR?.trim();
|
|
5547
|
-
return override && override !== "" ? override :
|
|
5236
|
+
return override && override !== "" ? override : join5(homedir4(), ".braid");
|
|
5548
5237
|
}
|
|
5549
5238
|
function runtimeRootForRead(braidDir, platform) {
|
|
5550
|
-
const primary =
|
|
5551
|
-
const markerOf = (root) => platform === "win32" ? currentTxtPath(root) :
|
|
5239
|
+
const primary = join5(braidDir, "runtime");
|
|
5240
|
+
const markerOf = (root) => platform === "win32" ? currentTxtPath(root) : join5(root, "current");
|
|
5552
5241
|
if (existsSync3(markerOf(primary))) return primary;
|
|
5553
5242
|
const override = process.env.BRAID_CONFIG_DIR?.trim();
|
|
5554
|
-
if (!override && braidDir ===
|
|
5555
|
-
const legacy =
|
|
5243
|
+
if (!override && braidDir === join5(homedir4(), ".braid")) {
|
|
5244
|
+
const legacy = join5(homedir4(), ".arp", "runtime");
|
|
5556
5245
|
if (existsSync3(markerOf(legacy))) return legacy;
|
|
5557
5246
|
}
|
|
5558
5247
|
return primary;
|
|
@@ -5560,7 +5249,7 @@ function runtimeRootForRead(braidDir, platform) {
|
|
|
5560
5249
|
function runningPackageRoot() {
|
|
5561
5250
|
let dir = dirname4(fileURLToPath(import.meta.url));
|
|
5562
5251
|
for (let i = 0; i < 6; i++) {
|
|
5563
|
-
if (existsSync3(
|
|
5252
|
+
if (existsSync3(join5(dir, "package.json")) && existsSync3(join5(dir, "dist"))) return dir;
|
|
5564
5253
|
const parent = dirname4(dir);
|
|
5565
5254
|
if (parent === dir) break;
|
|
5566
5255
|
dir = parent;
|
|
@@ -5568,13 +5257,13 @@ function runningPackageRoot() {
|
|
|
5568
5257
|
return dirname4(dirname4(fileURLToPath(import.meta.url)));
|
|
5569
5258
|
}
|
|
5570
5259
|
function currentTxtPath(runtimeRoot) {
|
|
5571
|
-
return
|
|
5260
|
+
return join5(runtimeRoot, "current.txt");
|
|
5572
5261
|
}
|
|
5573
5262
|
function pinCurrentRuntime(opts = {}) {
|
|
5574
5263
|
const sourceDir = opts.sourceDir ?? runningPackageRoot();
|
|
5575
5264
|
const braidDir = opts.braidDir ?? defaultBraidDir();
|
|
5576
5265
|
const platform = opts.platform ?? process.platform;
|
|
5577
|
-
const pkg = JSON.parse(readFileSync4(
|
|
5266
|
+
const pkg = JSON.parse(readFileSync4(join5(sourceDir, "package.json"), "utf8"));
|
|
5578
5267
|
const version = String(pkg.version ?? "").trim();
|
|
5579
5268
|
if (version === "") throw new Error(`cannot pin runtime: package.json at ${sourceDir} has no version`);
|
|
5580
5269
|
if (!/^\d+\.\d+\.\d+$/.test(version)) {
|
|
@@ -5583,20 +5272,20 @@ function pinCurrentRuntime(opts = {}) {
|
|
|
5583
5272
|
);
|
|
5584
5273
|
}
|
|
5585
5274
|
const prodDeps = Object.keys(pkg.dependencies ?? {});
|
|
5586
|
-
const runtimeRoot =
|
|
5587
|
-
const versionDir =
|
|
5275
|
+
const runtimeRoot = join5(braidDir, "runtime");
|
|
5276
|
+
const versionDir = join5(runtimeRoot, version);
|
|
5588
5277
|
mkdirSync3(versionDir, { recursive: true });
|
|
5589
|
-
cpSync(
|
|
5590
|
-
cpSync(
|
|
5591
|
-
const shrinkwrapSrc =
|
|
5278
|
+
cpSync(join5(sourceDir, "dist"), join5(versionDir, "dist"), { recursive: true, force: true });
|
|
5279
|
+
cpSync(join5(sourceDir, "package.json"), join5(versionDir, "package.json"), { force: true });
|
|
5280
|
+
const shrinkwrapSrc = join5(sourceDir, "npm-shrinkwrap.json");
|
|
5592
5281
|
const hasShrinkwrap = existsSync3(shrinkwrapSrc);
|
|
5593
5282
|
if (hasShrinkwrap) {
|
|
5594
|
-
cpSync(shrinkwrapSrc,
|
|
5283
|
+
cpSync(shrinkwrapSrc, join5(versionDir, "npm-shrinkwrap.json"), { force: true });
|
|
5595
5284
|
}
|
|
5596
5285
|
const method = populateNodeModules(sourceDir, versionDir, prodDeps, hasShrinkwrap, opts.exec);
|
|
5597
|
-
const shrinkwrapSha256 = hasShrinkwrap ? createHash2("sha256").update(readFileSync4(
|
|
5286
|
+
const shrinkwrapSha256 = hasShrinkwrap ? createHash2("sha256").update(readFileSync4(join5(versionDir, "npm-shrinkwrap.json"))).digest("hex") : null;
|
|
5598
5287
|
writeFileSync3(
|
|
5599
|
-
|
|
5288
|
+
join5(versionDir, "closure.json"),
|
|
5600
5289
|
JSON.stringify({ method, shrinkwrapSha256 }, null, 2) + "\n"
|
|
5601
5290
|
);
|
|
5602
5291
|
const previousVersion = installedRuntimeVersion({ braidDir, platform });
|
|
@@ -5612,12 +5301,12 @@ function pinCurrentRuntime(opts = {}) {
|
|
|
5612
5301
|
function populateNodeModules(sourceDir, versionDir, prodDeps, hasShrinkwrap, exec) {
|
|
5613
5302
|
if (prodDeps.length === 0) return "none";
|
|
5614
5303
|
const marker = prodDeps[0];
|
|
5615
|
-
const dest =
|
|
5304
|
+
const dest = join5(versionDir, "node_modules");
|
|
5616
5305
|
const plainTree = findCopyableNodeModules(sourceDir, marker);
|
|
5617
5306
|
if (plainTree) {
|
|
5618
5307
|
rmSync(dest, { recursive: true, force: true });
|
|
5619
5308
|
cpSync(plainTree, dest, { recursive: true, force: true });
|
|
5620
|
-
if (existsSync3(
|
|
5309
|
+
if (existsSync3(join5(dest, marker))) return "copy";
|
|
5621
5310
|
rmSync(dest, { recursive: true, force: true });
|
|
5622
5311
|
}
|
|
5623
5312
|
return npmInstallProdDeps(versionDir, marker, hasShrinkwrap, exec);
|
|
@@ -5625,10 +5314,10 @@ function populateNodeModules(sourceDir, versionDir, prodDeps, hasShrinkwrap, exe
|
|
|
5625
5314
|
function findCopyableNodeModules(sourceDir, marker) {
|
|
5626
5315
|
let dir = sourceDir;
|
|
5627
5316
|
for (let i = 0; i < 8; i++) {
|
|
5628
|
-
const nm =
|
|
5629
|
-
const markerPath =
|
|
5317
|
+
const nm = join5(dir, "node_modules");
|
|
5318
|
+
const markerPath = join5(nm, marker);
|
|
5630
5319
|
if (existsSync3(markerPath)) {
|
|
5631
|
-
if (!
|
|
5320
|
+
if (!lstatSync2(markerPath).isSymbolicLink()) return nm;
|
|
5632
5321
|
return null;
|
|
5633
5322
|
}
|
|
5634
5323
|
const parent = dirname4(dir);
|
|
@@ -5655,7 +5344,7 @@ function npmInstallProdDeps(versionDir, marker, hasShrinkwrap, exec) {
|
|
|
5655
5344
|
`pinned runtime at ${versionDir} could not obtain its production dependencies (\`npm ${args[0]}\` failed \u2014 npm missing, no network, or shrinkwrap/platform mismatch?). Fix and re-run, or the service will crash-loop with ERR_MODULE_NOT_FOUND. Underlying error: ${detail}`
|
|
5656
5345
|
);
|
|
5657
5346
|
}
|
|
5658
|
-
if (!existsSync3(
|
|
5347
|
+
if (!existsSync3(join5(versionDir, "node_modules", marker))) {
|
|
5659
5348
|
throw new Error(
|
|
5660
5349
|
`pinned runtime at ${versionDir} is missing dependency "${marker}" after \`npm ${args[0]}\`. Refusing to leave a runtime that will crash-loop with ERR_MODULE_NOT_FOUND.`
|
|
5661
5350
|
);
|
|
@@ -5667,7 +5356,7 @@ function pruneOldVersions(runtimeRoot, currentVersion, previousVersion) {
|
|
|
5667
5356
|
if (previousVersion && previousVersion !== currentVersion) keep.add(previousVersion);
|
|
5668
5357
|
let entries;
|
|
5669
5358
|
try {
|
|
5670
|
-
entries =
|
|
5359
|
+
entries = readdirSync3(runtimeRoot);
|
|
5671
5360
|
} catch {
|
|
5672
5361
|
return;
|
|
5673
5362
|
}
|
|
@@ -5676,9 +5365,9 @@ function pruneOldVersions(runtimeRoot, currentVersion, previousVersion) {
|
|
|
5676
5365
|
if (name === "current.txt") continue;
|
|
5677
5366
|
if (name.startsWith("current.tmp.")) continue;
|
|
5678
5367
|
if (keep.has(name)) continue;
|
|
5679
|
-
const dir =
|
|
5368
|
+
const dir = join5(runtimeRoot, name);
|
|
5680
5369
|
try {
|
|
5681
|
-
if (!
|
|
5370
|
+
if (!lstatSync2(dir).isDirectory()) continue;
|
|
5682
5371
|
} catch {
|
|
5683
5372
|
continue;
|
|
5684
5373
|
}
|
|
@@ -5687,8 +5376,8 @@ function pruneOldVersions(runtimeRoot, currentVersion, previousVersion) {
|
|
|
5687
5376
|
}
|
|
5688
5377
|
function flipCurrentSymlink(runtimeRoot, version) {
|
|
5689
5378
|
mkdirSync3(runtimeRoot, { recursive: true });
|
|
5690
|
-
const current =
|
|
5691
|
-
const tmp =
|
|
5379
|
+
const current = join5(runtimeRoot, "current");
|
|
5380
|
+
const tmp = join5(runtimeRoot, `current.tmp.${process.pid}.${Date.now()}`);
|
|
5692
5381
|
try {
|
|
5693
5382
|
rmSync(tmp, { force: true });
|
|
5694
5383
|
} catch {
|
|
@@ -5708,7 +5397,7 @@ function installedRuntimeVersion(q = {}) {
|
|
|
5708
5397
|
return null;
|
|
5709
5398
|
}
|
|
5710
5399
|
}
|
|
5711
|
-
const current =
|
|
5400
|
+
const current = join5(runtimeRoot, "current");
|
|
5712
5401
|
try {
|
|
5713
5402
|
const target = readlinkSync(current);
|
|
5714
5403
|
return target.split("/").pop() ?? null;
|
|
@@ -5721,9 +5410,9 @@ function pinnedEntryPath(q = {}) {
|
|
|
5721
5410
|
const platform = q.platform ?? process.platform;
|
|
5722
5411
|
if (platform === "win32") {
|
|
5723
5412
|
const version = installedRuntimeVersion({ braidDir, platform });
|
|
5724
|
-
if (version) return
|
|
5413
|
+
if (version) return join5(braidDir, "runtime", version, "dist", "cli.js");
|
|
5725
5414
|
}
|
|
5726
|
-
return
|
|
5415
|
+
return join5(braidDir, "runtime", "current", "dist", "cli.js");
|
|
5727
5416
|
}
|
|
5728
5417
|
function installedClosureRecord(q = {}) {
|
|
5729
5418
|
const braidDir = q.braidDir ?? defaultBraidDir();
|
|
@@ -5731,7 +5420,7 @@ function installedClosureRecord(q = {}) {
|
|
|
5731
5420
|
const version = installedRuntimeVersion(q);
|
|
5732
5421
|
if (!version) return null;
|
|
5733
5422
|
try {
|
|
5734
|
-
const raw = JSON.parse(readFileSync4(
|
|
5423
|
+
const raw = JSON.parse(readFileSync4(join5(runtimeRootForRead(braidDir, platform), version, "closure.json"), "utf8"));
|
|
5735
5424
|
if (typeof raw.method !== "string") return null;
|
|
5736
5425
|
return {
|
|
5737
5426
|
method: raw.method,
|
|
@@ -6017,13 +5706,13 @@ function validateServiceKey(name) {
|
|
|
6017
5706
|
return trimmed;
|
|
6018
5707
|
}
|
|
6019
5708
|
function runDir(braidDir) {
|
|
6020
|
-
return
|
|
5709
|
+
return join6(braidDir, "run");
|
|
6021
5710
|
}
|
|
6022
5711
|
function controlSocketPath(name, braidDir, platform = process.platform) {
|
|
6023
5712
|
if (platform === "win32") {
|
|
6024
5713
|
return `\\\\.\\pipe\\braid-${name}`;
|
|
6025
5714
|
}
|
|
6026
|
-
return
|
|
5715
|
+
return join6(runDir(braidDir), `${name}.sock`);
|
|
6027
5716
|
}
|
|
6028
5717
|
function legacyControlSocketPath(name, platform = process.platform) {
|
|
6029
5718
|
if (platform !== "win32") return null;
|
|
@@ -6047,7 +5736,7 @@ function controlSocketReadPath(name, braidDir, platform = process.platform) {
|
|
|
6047
5736
|
}
|
|
6048
5737
|
function controlTokenPath(name, braidDir, platform = process.platform) {
|
|
6049
5738
|
if (platform !== "win32") return null;
|
|
6050
|
-
return
|
|
5739
|
+
return join6(runDir(braidDir), `${name}.token`);
|
|
6051
5740
|
}
|
|
6052
5741
|
function readControlToken(name, braidDir, platform = process.platform) {
|
|
6053
5742
|
let p = controlTokenPath(name, braidDir, platform);
|
|
@@ -6093,7 +5782,7 @@ function sleep(ms) {
|
|
|
6093
5782
|
}
|
|
6094
5783
|
function enumerateInstalledServices(platform, deps) {
|
|
6095
5784
|
if (platform === "darwin") {
|
|
6096
|
-
const dir =
|
|
5785
|
+
const dir = join6(deps.homeDir, "Library", "LaunchAgents");
|
|
6097
5786
|
return dedupe([
|
|
6098
5787
|
...globNames(dir, /^com\.snowyroad\.braid\.(.+)\.plist$/),
|
|
6099
5788
|
...globNames(dir, /^dev\.arp\.bridge\.(.+)\.plist$/)
|
|
@@ -6101,7 +5790,7 @@ function enumerateInstalledServices(platform, deps) {
|
|
|
6101
5790
|
]);
|
|
6102
5791
|
}
|
|
6103
5792
|
if (platform === "linux") {
|
|
6104
|
-
const dir =
|
|
5793
|
+
const dir = join6(deps.homeDir, ".config", "systemd", "user");
|
|
6105
5794
|
return dedupe([
|
|
6106
5795
|
...globNames(dir, /^braid-(.+)\.service$/),
|
|
6107
5796
|
...globNames(dir, /^arp-bridge-(.+)\.service$/)
|
|
@@ -6115,19 +5804,19 @@ function dedupe(names) {
|
|
|
6115
5804
|
}
|
|
6116
5805
|
function isLegacyFamilyService(platform, name, homeDir) {
|
|
6117
5806
|
if (platform === "darwin") {
|
|
6118
|
-
const dir =
|
|
6119
|
-
return !existsSync5(
|
|
5807
|
+
const dir = join6(homeDir, "Library", "LaunchAgents");
|
|
5808
|
+
return !existsSync5(join6(dir, `com.snowyroad.braid.${name}.plist`)) && existsSync5(join6(dir, `dev.arp.bridge.${name}.plist`));
|
|
6120
5809
|
}
|
|
6121
5810
|
if (platform === "linux") {
|
|
6122
|
-
const dir =
|
|
6123
|
-
return !existsSync5(
|
|
5811
|
+
const dir = join6(homeDir, ".config", "systemd", "user");
|
|
5812
|
+
return !existsSync5(join6(dir, `braid-${name}.service`)) && existsSync5(join6(dir, `arp-bridge-${name}.service`));
|
|
6124
5813
|
}
|
|
6125
5814
|
return false;
|
|
6126
5815
|
}
|
|
6127
5816
|
function globNames(dir, pattern) {
|
|
6128
5817
|
let entries = [];
|
|
6129
5818
|
try {
|
|
6130
|
-
entries =
|
|
5819
|
+
entries = readdirSync4(dir);
|
|
6131
5820
|
} catch {
|
|
6132
5821
|
return [];
|
|
6133
5822
|
}
|
|
@@ -6171,7 +5860,7 @@ async function updateInstalled(opts) {
|
|
|
6171
5860
|
|
|
6172
5861
|
// src/service/needsHumanMarker.ts
|
|
6173
5862
|
function needsHumanMarkerPath(name, braidDir) {
|
|
6174
|
-
return
|
|
5863
|
+
return join7(runDir(braidDir), `${name}.needs-human`);
|
|
6175
5864
|
}
|
|
6176
5865
|
function writeNeedsHumanMarker(name, reason, braidDir) {
|
|
6177
5866
|
try {
|
|
@@ -7913,7 +7602,7 @@ import { homedir as homedir6 } from "os";
|
|
|
7913
7602
|
|
|
7914
7603
|
// src/service/manager.ts
|
|
7915
7604
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7, rmSync as rmSync2, existsSync as existsSync7 } from "fs";
|
|
7916
|
-
import { join as
|
|
7605
|
+
import { join as join9 } from "path";
|
|
7917
7606
|
|
|
7918
7607
|
// src/service/templates.ts
|
|
7919
7608
|
function escapeXml(s) {
|
|
@@ -8084,18 +7773,18 @@ function schtasksXml(p) {
|
|
|
8084
7773
|
}
|
|
8085
7774
|
|
|
8086
7775
|
// src/service/win32.ts
|
|
8087
|
-
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, readdirSync as
|
|
8088
|
-
import { join as
|
|
7776
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, readdirSync as readdirSync5, readFileSync as readFileSync7, unlinkSync as unlinkSync3 } from "fs";
|
|
7777
|
+
import { join as join8 } from "path";
|
|
8089
7778
|
function win32LogDir(homeDir) {
|
|
8090
|
-
return
|
|
7779
|
+
return join8(homeDir, "AppData", "Local", "braid", "logs");
|
|
8091
7780
|
}
|
|
8092
7781
|
function win32Manager(ctx) {
|
|
8093
|
-
const serviceDir =
|
|
7782
|
+
const serviceDir = join8(ctx.braidDir, "service");
|
|
8094
7783
|
const task = (name) => schtasksLabel(name);
|
|
8095
7784
|
const legacyTask = (name) => legacySchtasksLabel(name);
|
|
8096
|
-
const xmlPath = (name) =>
|
|
8097
|
-
const wrapperPath = (name) =>
|
|
8098
|
-
const launcherPath = (name) =>
|
|
7785
|
+
const xmlPath = (name) => join8(serviceDir, `${task(name)}.xml`);
|
|
7786
|
+
const wrapperPath = (name) => join8(serviceDir, `${task(name)}.cmd`);
|
|
7787
|
+
const launcherPath = (name) => join8(serviceDir, `${task(name)}.vbs`);
|
|
8099
7788
|
const st = (...args) => ctx.exec("schtasks", args);
|
|
8100
7789
|
const stx = async (...args) => {
|
|
8101
7790
|
const res = await st(...args);
|
|
@@ -8106,18 +7795,18 @@ function win32Manager(ctx) {
|
|
|
8106
7795
|
};
|
|
8107
7796
|
const killBridgeProcess = async (name) => {
|
|
8108
7797
|
const legacyAgents = legacyTwinPath(ctx.braidDir, ["agents"]);
|
|
8109
|
-
const agentDirs = [
|
|
7798
|
+
const agentDirs = [join8(ctx.braidDir, "agents"), ...legacyAgents ? [legacyAgents] : []];
|
|
8110
7799
|
const hostDirs = [];
|
|
8111
7800
|
for (const agentsDir of agentDirs) {
|
|
8112
7801
|
try {
|
|
8113
|
-
for (const host of
|
|
7802
|
+
for (const host of readdirSync5(agentsDir)) hostDirs.push(join8(agentsDir, host));
|
|
8114
7803
|
} catch {
|
|
8115
7804
|
}
|
|
8116
7805
|
}
|
|
8117
7806
|
for (const hostDir of hostDirs) {
|
|
8118
7807
|
let pid = NaN;
|
|
8119
7808
|
try {
|
|
8120
|
-
pid = Number(readFileSync7(
|
|
7809
|
+
pid = Number(readFileSync7(join8(hostDir, `${name}.json.lock`), "utf8").trim());
|
|
8121
7810
|
} catch {
|
|
8122
7811
|
continue;
|
|
8123
7812
|
}
|
|
@@ -8161,7 +7850,7 @@ function win32Manager(ctx) {
|
|
|
8161
7850
|
await st("/delete", "/tn", task(name), "/f");
|
|
8162
7851
|
await st("/delete", "/tn", legacyTask(name), "/f");
|
|
8163
7852
|
try {
|
|
8164
|
-
unlinkSync3(
|
|
7853
|
+
unlinkSync3(join8(runDir(ctx.braidDir), `${name}.token`));
|
|
8165
7854
|
} catch {
|
|
8166
7855
|
}
|
|
8167
7856
|
const legacyToken = legacyTwinPath(ctx.braidDir, ["run", `${name}.token`]);
|
|
@@ -8199,7 +7888,7 @@ function win32Manager(ctx) {
|
|
|
8199
7888
|
return { installed, loaded, running };
|
|
8200
7889
|
},
|
|
8201
7890
|
logsPath(name) {
|
|
8202
|
-
return
|
|
7891
|
+
return join8(win32LogDir(ctx.homeDir), `${name}.err.log`);
|
|
8203
7892
|
}
|
|
8204
7893
|
};
|
|
8205
7894
|
}
|
|
@@ -8213,7 +7902,7 @@ var NotSupportedError = class extends Error {
|
|
|
8213
7902
|
};
|
|
8214
7903
|
function makeServiceManager(platform, exec, deps = {}) {
|
|
8215
7904
|
const homeDir = deps.homeDir ?? process.env.HOME ?? "";
|
|
8216
|
-
const braidDir = deps.braidDir ??
|
|
7905
|
+
const braidDir = deps.braidDir ?? join9(homeDir, ".braid");
|
|
8217
7906
|
const uid = deps.uid ?? (typeof process.getuid === "function" ? process.getuid() : 0);
|
|
8218
7907
|
const nodePath = deps.nodePath ?? process.execPath;
|
|
8219
7908
|
const entryPath = pinnedEntryPath({ braidDir });
|
|
@@ -8238,11 +7927,11 @@ function notSupportedManager(platform) {
|
|
|
8238
7927
|
};
|
|
8239
7928
|
}
|
|
8240
7929
|
function darwinManager(ctx) {
|
|
8241
|
-
const launchAgentsDir =
|
|
8242
|
-
const logDir =
|
|
8243
|
-
const legacyLogDir =
|
|
8244
|
-
const plistPath = (name) =>
|
|
8245
|
-
const legacyPlistPath = (name) =>
|
|
7930
|
+
const launchAgentsDir = join9(ctx.homeDir, "Library", "LaunchAgents");
|
|
7931
|
+
const logDir = join9(ctx.homeDir, "Library", "Logs", "braid");
|
|
7932
|
+
const legacyLogDir = join9(ctx.homeDir, "Library", "Logs", "arp-bridge");
|
|
7933
|
+
const plistPath = (name) => join9(launchAgentsDir, `${launchdLabel(name)}.plist`);
|
|
7934
|
+
const legacyPlistPath = (name) => join9(launchAgentsDir, `${legacyLaunchdLabel(name)}.plist`);
|
|
8246
7935
|
const legacyInstalled = (name) => !existsSync7(plistPath(name)) && existsSync7(legacyPlistPath(name));
|
|
8247
7936
|
const domainTarget = `gui/${ctx.uid}`;
|
|
8248
7937
|
const serviceTarget = (name) => `${domainTarget}/${legacyInstalled(name) ? legacyLaunchdLabel(name) : launchdLabel(name)}`;
|
|
@@ -8307,16 +7996,16 @@ function darwinManager(ctx) {
|
|
|
8307
7996
|
};
|
|
8308
7997
|
},
|
|
8309
7998
|
logsPath(name) {
|
|
8310
|
-
return
|
|
7999
|
+
return join9(legacyInstalled(name) ? legacyLogDir : logDir, `${name}.err.log`);
|
|
8311
8000
|
}
|
|
8312
8001
|
};
|
|
8313
8002
|
}
|
|
8314
8003
|
function linuxManager(ctx) {
|
|
8315
|
-
const unitDir =
|
|
8004
|
+
const unitDir = join9(ctx.homeDir, ".config", "systemd", "user");
|
|
8316
8005
|
const newUnitName = (name) => `braid-${name}.service`;
|
|
8317
8006
|
const legacyUnitName = (name) => `arp-bridge-${name}.service`;
|
|
8318
|
-
const newUnitPath = (name) =>
|
|
8319
|
-
const legacyUnitPath = (name) =>
|
|
8007
|
+
const newUnitPath = (name) => join9(unitDir, newUnitName(name));
|
|
8008
|
+
const legacyUnitPath = (name) => join9(unitDir, legacyUnitName(name));
|
|
8320
8009
|
const legacyInstalled = (name) => !existsSync7(newUnitPath(name)) && existsSync7(legacyUnitPath(name));
|
|
8321
8010
|
const unitName = (name) => legacyInstalled(name) ? legacyUnitName(name) : newUnitName(name);
|
|
8322
8011
|
const unitPath = (name) => legacyInstalled(name) ? legacyUnitPath(name) : newUnitPath(name);
|
|
@@ -8598,7 +8287,7 @@ function printStatusHuman(name, st, logsPath) {
|
|
|
8598
8287
|
|
|
8599
8288
|
// src/service/serviceLog.ts
|
|
8600
8289
|
import { createWriteStream, mkdirSync as mkdirSync8 } from "fs";
|
|
8601
|
-
import { dirname as dirname6, join as
|
|
8290
|
+
import { dirname as dirname6, join as join10 } from "path";
|
|
8602
8291
|
|
|
8603
8292
|
// src/service/logRotation.ts
|
|
8604
8293
|
import {
|
|
@@ -8651,16 +8340,16 @@ function rotateLogFileIfNeeded(logPath, opts = {}) {
|
|
|
8651
8340
|
|
|
8652
8341
|
// src/service/serviceLog.ts
|
|
8653
8342
|
function darwinLogDir(homeDir) {
|
|
8654
|
-
return
|
|
8343
|
+
return join10(homeDir, "Library", "Logs", "braid");
|
|
8655
8344
|
}
|
|
8656
8345
|
function serviceLogTargets(name, platform, homeDir) {
|
|
8657
8346
|
if (platform === "darwin") {
|
|
8658
8347
|
const d = darwinLogDir(homeDir);
|
|
8659
|
-
return [
|
|
8348
|
+
return [join10(d, `${name}.out.log`), join10(d, `${name}.err.log`)];
|
|
8660
8349
|
}
|
|
8661
8350
|
if (platform === "win32") {
|
|
8662
8351
|
const d = win32LogDir(homeDir);
|
|
8663
|
-
return [
|
|
8352
|
+
return [join10(d, `${name}.out.log`), join10(d, `${name}.err.log`)];
|
|
8664
8353
|
}
|
|
8665
8354
|
return [];
|
|
8666
8355
|
}
|
|
@@ -9026,7 +8715,7 @@ async function main() {
|
|
|
9026
8715
|
}
|
|
9027
8716
|
assertKeystorePerms(dir, file);
|
|
9028
8717
|
}
|
|
9029
|
-
console.log("
|
|
8718
|
+
console.log(banner("braid", "STARTING", startingLines(cfg)));
|
|
9030
8719
|
if (cfg.toolMode === "full") {
|
|
9031
8720
|
console.error(
|
|
9032
8721
|
"[braid] WARNING full tool access: remote channel content can drive local tool use on this machine"
|