@hraness/xcb 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/MANAGED-CODEX.md +213 -0
- package/README.md +555 -0
- package/dist/accounts.d.ts +46 -0
- package/dist/broker-descriptors.d.ts +5 -0
- package/dist/broker.d.ts +62 -0
- package/dist/browser-session.d.ts +161 -0
- package/dist/canonical-json.d.ts +2 -0
- package/dist/capabilities.d.ts +72 -0
- package/dist/claude-api-models.d.ts +24 -0
- package/dist/claude-api-transport.d.ts +5 -0
- package/dist/claude-api.d.ts +23 -0
- package/dist/claude-credentials.d.ts +13 -0
- package/dist/claude-options.d.ts +10 -0
- package/dist/claude-sdk.d.ts +48 -0
- package/dist/claude-task-adapter.d.ts +65 -0
- package/dist/cli.js +4190 -0
- package/dist/codex-account-process.d.ts +149 -0
- package/dist/codex-account-transport.d.ts +39 -0
- package/dist/codex-account.d.ts +126 -0
- package/dist/codex-config.d.ts +53 -0
- package/dist/codex-host.d.ts +40 -0
- package/dist/codex-managed-baseline.d.ts +5 -0
- package/dist/codex-managed-catalog.d.ts +32 -0
- package/dist/codex-managed-config.d.ts +93 -0
- package/dist/codex-managed-ledger.d.ts +37 -0
- package/dist/codex-managed-session.d.ts +62 -0
- package/dist/codex-managed-task-adapter.d.ts +21 -0
- package/dist/codex-process.d.ts +67 -0
- package/dist/codex-protocol-manifest.d.ts +27 -0
- package/dist/codex-relay.d.ts +80 -0
- package/dist/codex-scratch.d.ts +36 -0
- package/dist/codex-session.d.ts +44 -0
- package/dist/codex-task-adapter.d.ts +24 -0
- package/dist/codex-task-process.d.ts +21 -0
- package/dist/devin-acp.d.ts +105 -0
- package/dist/devin-adapter.d.ts +44 -0
- package/dist/devin-client.d.ts +36 -0
- package/dist/devin-mcp.d.ts +28 -0
- package/dist/egress-bridge.d.ts +35 -0
- package/dist/egress-client.d.ts +66 -0
- package/dist/index-kg2gx694.js +7217 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +3455 -0
- package/dist/judge.d.ts +121 -0
- package/dist/loopback-server.d.ts +17 -0
- package/dist/managed-account.d.ts +176 -0
- package/dist/models.d.ts +23 -0
- package/dist/os-sandbox.d.ts +168 -0
- package/dist/private-file.d.ts +215 -0
- package/dist/process-port.d.ts +44 -0
- package/dist/process-write.d.ts +13 -0
- package/dist/provider-process.d.ts +26 -0
- package/dist/public-web.d.ts +21 -0
- package/dist/router.d.ts +43 -0
- package/dist/runtime.d.ts +90 -0
- package/dist/sqlite-port.d.ts +29 -0
- package/dist/task-runtime.d.ts +150 -0
- package/dist/validation.d.ts +6 -0
- package/package.json +70 -0
- package/sandbox/loopback-forwarder.cjs +172 -0
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hraness/xcb",
|
|
3
|
+
"version": "0.9.1",
|
|
4
|
+
"description": "TypeScript SDK and compatibility CLI for xcb, which routes coding tasks across the Claude, Codex, and Devin subscriptions you already pay for.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"packageManager": "bun@1.3.14",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/hraness/xcb.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://xcb.sh",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/hraness/xcb/issues"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"provenance": true,
|
|
19
|
+
"registry": "https://registry.npmjs.org"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"bun": ">=1.3.14",
|
|
23
|
+
"node": ">=22.13"
|
|
24
|
+
},
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"bin": {
|
|
33
|
+
"xcb-compat": "dist/cli.js"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"MANAGED-CODEX.md",
|
|
38
|
+
"sandbox"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build:dist": "bun scripts/build-dist.ts",
|
|
42
|
+
"check:package": "bun scripts/package-smoke.ts",
|
|
43
|
+
"test": "bun test ./src ./test ./scripts",
|
|
44
|
+
"gobstopper-editor": "bun src/gobstopper-editor.ts",
|
|
45
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
46
|
+
"check:versions": "bun scripts/release-version-contract.ts",
|
|
47
|
+
"check": "bun run check:versions && bun run typecheck && bun run check:convex && bun run test && bun run build:dist && bun run check:package",
|
|
48
|
+
"prepack": "bun run check",
|
|
49
|
+
"convex:init": "bun scripts/convex-local.ts init",
|
|
50
|
+
"convex:dev": "bun scripts/convex-local.ts dev --tail-logs disable",
|
|
51
|
+
"convex:codegen": "bun scripts/convex-local.ts dev --once --tail-logs disable",
|
|
52
|
+
"check:convex": "tsc -p convex/tsconfig.json && bun test ./convex"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.278",
|
|
56
|
+
"@anthropic-ai/sdk": "0.127.0",
|
|
57
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
58
|
+
"zod": "4.6.5"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@convex-dev/auth": "0.0.95",
|
|
62
|
+
"@hraness/relay": "github:hraness/relay#d8251ff4b2f972c7b5694f7c583fc1a08049395a",
|
|
63
|
+
"@types/bun": "1.4.2",
|
|
64
|
+
"@types/node": "26.6.2",
|
|
65
|
+
"convex": "1.45.0",
|
|
66
|
+
"convex-test": "0.0.59",
|
|
67
|
+
"sigstore": "5.0.0",
|
|
68
|
+
"typescript": "7.0.2"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** In-namespace loopback CONNECT forwarder — the stock-binary consumption
|
|
3
|
+
* half of `provider-tcp443-dns`. Runs inside the bwrap network namespace on a
|
|
4
|
+
* mounted JS runtime (node or bun); it is not a launcher and holds no
|
|
5
|
+
* credentials.
|
|
6
|
+
*
|
|
7
|
+
* <runtime> loopback-forwarder.cjs <bridge-socket> <port> <lo-up-cmd|-> <env-file|-> [<service|->] -- <child argv...>
|
|
8
|
+
*
|
|
9
|
+
* The optional `service` positional is `<host-unix-socket>:<port>`: the
|
|
10
|
+
* forwarder also binds 127.0.0.1:<port> inside the namespace and pipes each
|
|
11
|
+
* accepted connection verbatim to that mounted host socket — the in-namespace
|
|
12
|
+
* consumption path for host services (the tool relay) that are not CONNECT
|
|
13
|
+
* egress. `-` (or omitting the argument) disables it.
|
|
14
|
+
*
|
|
15
|
+
* It listens on 127.0.0.1:<port>, accepts only `CONNECT host:443`, forwards
|
|
16
|
+
* each request verbatim onto the mounted host bridge unix socket, relays the
|
|
17
|
+
* bridge's response head, then pipes both directions. The child is spawned
|
|
18
|
+
* with standard proxy variables pointing at the forwarder, so a stock binary
|
|
19
|
+
* (curl, a provider CLI) reaches providers through the bridge with no DNS or
|
|
20
|
+
* direct TCP inside the namespace. `lo-up-cmd` is a diagnostic escape hatch:
|
|
21
|
+
* an absolute path to `ip` runs `ip link set lo up` first; `-` skips (bwrap
|
|
22
|
+
* raises loopback itself on --unshare-net).
|
|
23
|
+
*
|
|
24
|
+
* `env-file` is the secret channel: bwrap `--setenv` values are visible in
|
|
25
|
+
* the wrapper's own argv, so the host writes `KEY=VALUE` pairs into a
|
|
26
|
+
* private file inside the writable scratch instead. The forwarder reads,
|
|
27
|
+
* validates, and deletes it before spawning, injecting the pairs only into
|
|
28
|
+
* the child's environment; proxy variables are forwarder-owned and always
|
|
29
|
+
* win over file entries.
|
|
30
|
+
*
|
|
31
|
+
* Diagnostics go to stderr as `FWD key=json` lines; the child's stdio is
|
|
32
|
+
* inherited verbatim. Exit status is the child's. */
|
|
33
|
+
const net = require("node:net");
|
|
34
|
+
const fs = require("node:fs");
|
|
35
|
+
const { spawn, spawnSync } = require("node:child_process");
|
|
36
|
+
|
|
37
|
+
// Positional layout: <socket> <port> <lo-up|-> <env-file|-> [<service|->] -- <child...>
|
|
38
|
+
// The service slot is optional — when the argument before `--` is absent the
|
|
39
|
+
// arity is the legacy five, so both forms parse.
|
|
40
|
+
const raw = process.argv.slice(2);
|
|
41
|
+
const separatorIndex = raw.indexOf("--");
|
|
42
|
+
const positionals = separatorIndex === -1 ? raw : raw.slice(0, separatorIndex);
|
|
43
|
+
const [socketPath, portText, loUpPath, envFilePath, serviceText] = positionals;
|
|
44
|
+
const childArgv = separatorIndex === -1 ? [] : raw.slice(separatorIndex + 1);
|
|
45
|
+
if (typeof socketPath !== "string" || !socketPath.startsWith("/")
|
|
46
|
+
|| !/^[0-9]+$/.test(portText ?? "") || positionals.length < 4 || positionals.length > 5
|
|
47
|
+
|| separatorIndex === -1 || childArgv.length === 0) {
|
|
48
|
+
fs.writeSync(2, "FWD usage=<bridge-socket> <port> <lo-up-cmd|-> <env-file|-> [<service|->] -- <child argv...>\n");
|
|
49
|
+
process.exit(2);
|
|
50
|
+
}
|
|
51
|
+
const report = (key, value) => { try { fs.writeSync(2, "FWD " + key + "=" + JSON.stringify(value) + "\n"); } catch {} };
|
|
52
|
+
const status = (() => { try { return fs.readFileSync("/proc/self/status", "utf8"); } catch { return ""; } })();
|
|
53
|
+
report("capEff", (/CapEff:\s*([0-9a-f]+)/i.exec(status) || [])[1] ?? null);
|
|
54
|
+
report("loOperstate", (() => { try { return fs.readFileSync("/sys/class/net/lo/operstate", "utf8").trim(); } catch { return null; } })());
|
|
55
|
+
report("loFlags", (() => { try { return fs.readFileSync("/sys/class/net/lo/flags", "utf8").trim(); } catch { return null; } })());
|
|
56
|
+
if (loUpPath !== "-") {
|
|
57
|
+
const raised = spawnSync(loUpPath, ["link", "set", "lo", "up"], { stdio: ["ignore", "ignore", "pipe"], timeout: 5000 });
|
|
58
|
+
report("loUpStatus", raised.status);
|
|
59
|
+
report("loUpError", raised.error ? String(raised.error.code || raised.error) : null);
|
|
60
|
+
report("loUpStderr", (raised.stderr || "").toString().slice(0, 200));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Secret channel: bounded KEY=VALUE lines the host left inside the writable
|
|
64
|
+
// scratch. The file is deleted before the child spawns whether parsing
|
|
65
|
+
// succeeds or not — never log its contents.
|
|
66
|
+
const PROXY_KEYS = new Set(["http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "all_proxy", "ALL_PROXY", "no_proxy", "NO_PROXY"]);
|
|
67
|
+
const fileEnv = Object.create(null);
|
|
68
|
+
if (envFilePath !== "-") {
|
|
69
|
+
let text;
|
|
70
|
+
try { text = fs.readFileSync(envFilePath, "utf8"); }
|
|
71
|
+
catch (error) { report("envFileError", String(error && error.code || error)); process.exit(1); }
|
|
72
|
+
try { fs.unlinkSync(envFilePath); } catch {}
|
|
73
|
+
if (Buffer.byteLength(text) > 64 * 1024) { report("envFileError", "limit"); process.exit(1); }
|
|
74
|
+
for (const line of text.split("\n")) {
|
|
75
|
+
if (line === "") continue;
|
|
76
|
+
const eq = line.indexOf("=");
|
|
77
|
+
const key = eq === -1 ? "" : line.slice(0, eq);
|
|
78
|
+
const value = eq === -1 ? "" : line.slice(eq + 1);
|
|
79
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || PROXY_KEYS.has(key) || value.includes("\0")) {
|
|
80
|
+
report("envFileError", "invalid"); process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
fileEnv[key] = value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Optional host-service forward: "<socket>:<port>" pipes 127.0.0.1:<port> in
|
|
87
|
+
// this namespace straight to the mounted unix socket — no CONNECT protocol,
|
|
88
|
+
// the host service owns its own admission.
|
|
89
|
+
let service = null;
|
|
90
|
+
if (serviceText !== undefined && serviceText !== "-") {
|
|
91
|
+
const split = serviceText.lastIndexOf(":");
|
|
92
|
+
const svcSocket = split === -1 ? "" : serviceText.slice(0, split);
|
|
93
|
+
const svcPort = split === -1 ? NaN : Number(serviceText.slice(split + 1));
|
|
94
|
+
if (!svcSocket.startsWith("/") || !Number.isInteger(svcPort) || svcPort < 1 || svcPort > 65535) {
|
|
95
|
+
report("serviceError", "invalid"); process.exit(2);
|
|
96
|
+
}
|
|
97
|
+
service = { socket: svcSocket, port: svcPort };
|
|
98
|
+
}
|
|
99
|
+
if (service !== null) {
|
|
100
|
+
const serviceServer = net.createServer((inbound) => {
|
|
101
|
+
inbound.on("error", () => {});
|
|
102
|
+
const upstream = net.createConnection(service.socket);
|
|
103
|
+
upstream.on("error", () => {});
|
|
104
|
+
// Pipe immediately: inbound data must not be dropped while the upstream
|
|
105
|
+
// unix connection is still establishing, and upstream cannot produce data
|
|
106
|
+
// before it connects.
|
|
107
|
+
inbound.pipe(upstream);
|
|
108
|
+
upstream.pipe(inbound);
|
|
109
|
+
upstream.once("error", () => inbound.destroy());
|
|
110
|
+
inbound.once("close", () => upstream.destroy());
|
|
111
|
+
});
|
|
112
|
+
serviceServer.on("error", (error) => { report("serviceListenError", String(error && error.code || error)); process.exit(1); });
|
|
113
|
+
serviceServer.listen(service.port, "127.0.0.1", () => report("serviceListening", serviceServer.address()));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const port = Number(portText);
|
|
117
|
+
const server = net.createServer((inbound) => {
|
|
118
|
+
// A raced write into a closing peer must never take the supervisor down —
|
|
119
|
+
// both sockets carry their own error path.
|
|
120
|
+
inbound.on("error", () => {});
|
|
121
|
+
let head = Buffer.alloc(0);
|
|
122
|
+
const onData = (chunk) => {
|
|
123
|
+
head = Buffer.concat([head, chunk]);
|
|
124
|
+
const end = head.indexOf("\r\n\r\n");
|
|
125
|
+
if (end === -1) { if (head.length > 4096) inbound.destroy(); return; }
|
|
126
|
+
// Consume exactly one head: leaving this listener attached would re-run
|
|
127
|
+
// CONNECT handling for every tunneled byte (a ClientHello would spawn a
|
|
128
|
+
// second bridge connection and inject a second "200" into the TLS stream).
|
|
129
|
+
inbound.removeListener("data", onData);
|
|
130
|
+
const request = head.subarray(0, end).toString("latin1").split("\r\n")[0];
|
|
131
|
+
const match = /^CONNECT ([A-Za-z0-9._-]+):443 HTTP\/1\.[01]$/.exec(request);
|
|
132
|
+
inbound.pause();
|
|
133
|
+
if (match === null) { report("refused", request.slice(0, 120)); inbound.destroy(); return; }
|
|
134
|
+
const upstream = net.createConnection(socketPath, () => {
|
|
135
|
+
upstream.on("error", () => {});
|
|
136
|
+
upstream.write(head.subarray(0, end + 4));
|
|
137
|
+
const clientExtra = head.subarray(end + 4);
|
|
138
|
+
let replyHead = Buffer.alloc(0);
|
|
139
|
+
const onReply = (replyChunk) => {
|
|
140
|
+
replyHead = Buffer.concat([replyHead, replyChunk]);
|
|
141
|
+
const replyEnd = replyHead.indexOf("\r\n\r\n");
|
|
142
|
+
if (replyEnd === -1) { if (replyHead.length > 4096) { upstream.destroy(); inbound.destroy(); } return; }
|
|
143
|
+
upstream.removeListener("data", onReply);
|
|
144
|
+
inbound.write(replyHead.subarray(0, replyEnd + 4));
|
|
145
|
+
// Reply-head tail belongs to the client; the client's own post-head
|
|
146
|
+
// bytes belong upstream. Keep the two directions separate.
|
|
147
|
+
const bridgeExtra = replyHead.subarray(replyEnd + 4);
|
|
148
|
+
if (bridgeExtra.length) inbound.write(bridgeExtra);
|
|
149
|
+
if (clientExtra.length) upstream.write(clientExtra);
|
|
150
|
+
upstream.pipe(inbound);
|
|
151
|
+
inbound.pipe(upstream);
|
|
152
|
+
inbound.resume();
|
|
153
|
+
};
|
|
154
|
+
upstream.on("data", onReply);
|
|
155
|
+
});
|
|
156
|
+
upstream.once("error", () => inbound.destroy());
|
|
157
|
+
inbound.once("close", () => upstream.destroy());
|
|
158
|
+
};
|
|
159
|
+
inbound.on("data", onData);
|
|
160
|
+
});
|
|
161
|
+
server.listen(port, "127.0.0.1", () => {
|
|
162
|
+
report("listening", server.address());
|
|
163
|
+
const proxy = "http://127.0.0.1:" + port;
|
|
164
|
+
const env = { ...process.env, ...fileEnv,
|
|
165
|
+
http_proxy: proxy, HTTP_PROXY: proxy, https_proxy: proxy, HTTPS_PROXY: proxy,
|
|
166
|
+
all_proxy: proxy, ALL_PROXY: proxy, no_proxy: "", NO_PROXY: "" };
|
|
167
|
+
const child = spawn(childArgv[0], childArgv.slice(1), { stdio: "inherit", env });
|
|
168
|
+
for (const signal of ["SIGTERM", "SIGINT"]) process.on(signal, () => child.kill(signal));
|
|
169
|
+
child.on("exit", (code, signal) => { report("childExit", { code, signal }); process.exit(code ?? 1); });
|
|
170
|
+
child.on("error", (error) => { report("childError", String(error)); process.exit(1); });
|
|
171
|
+
});
|
|
172
|
+
server.on("error", (error) => { report("listenError", String(error && error.code || error)); process.exit(1); });
|