@hydra-acp/browser 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +258 -0
- package/dist/config.js +101 -0
- package/dist/config.js.map +1 -0
- package/dist/hydra/client.js +61 -0
- package/dist/hydra/client.js.map +1 -0
- package/dist/hydra/ws.js +178 -0
- package/dist/hydra/ws.js.map +1 -0
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -0
- package/dist/server/auth.js +104 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/http.js +128 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/routes-agents.js +14 -0
- package/dist/server/routes-agents.js.map +1 -0
- package/dist/server/routes-files.js +170 -0
- package/dist/server/routes-files.js.map +1 -0
- package/dist/server/routes-root.js +67 -0
- package/dist/server/routes-root.js.map +1 -0
- package/dist/server/routes-sessions.js +104 -0
- package/dist/server/routes-sessions.js.map +1 -0
- package/dist/server/title-cache.js +59 -0
- package/dist/server/title-cache.js.map +1 -0
- package/dist/server/ws-bridge.js +261 -0
- package/dist/server/ws-bridge.js.map +1 -0
- package/dist/ui/index.html +2151 -0
- package/dist/util/csrf.js +57 -0
- package/dist/util/csrf.js.map +1 -0
- package/dist/util/log.js +46 -0
- package/dist/util/log.js.map +1 -0
- package/dist/util/paths.js +24 -0
- package/dist/util/paths.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export function buildSecurityContext(host, port, scheme, extraHosts) {
|
|
2
|
+
const hostsAndPorts = new Set();
|
|
3
|
+
const origins = new Set();
|
|
4
|
+
const portSuffix = `:${port}`;
|
|
5
|
+
const baseHosts = new Set([host, "127.0.0.1", "localhost", "[::1]"]);
|
|
6
|
+
for (const h of baseHosts) {
|
|
7
|
+
hostsAndPorts.add(`${h}${portSuffix}`.toLowerCase());
|
|
8
|
+
origins.add(`${scheme}://${h}${portSuffix}`.toLowerCase());
|
|
9
|
+
}
|
|
10
|
+
for (const h of extraHosts) {
|
|
11
|
+
const trimmed = h.trim();
|
|
12
|
+
if (!trimmed) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const withPort = trimmed.includes(":") ? trimmed : `${trimmed}${portSuffix}`;
|
|
16
|
+
hostsAndPorts.add(withPort.toLowerCase());
|
|
17
|
+
origins.add(`${scheme}://${withPort}`.toLowerCase());
|
|
18
|
+
}
|
|
19
|
+
return { allowedHosts: hostsAndPorts, allowedOrigins: origins };
|
|
20
|
+
}
|
|
21
|
+
export function checkHost(ctx, headers) {
|
|
22
|
+
const host = headers.host;
|
|
23
|
+
if (typeof host !== "string") {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return ctx.allowedHosts.has(host.toLowerCase());
|
|
27
|
+
}
|
|
28
|
+
export function checkOrigin(ctx, headers) {
|
|
29
|
+
const origin = headers.origin;
|
|
30
|
+
if (typeof origin !== "string") {
|
|
31
|
+
// Same-origin browser navigations sometimes omit Origin; allow when
|
|
32
|
+
// Sec-Fetch-Site is present and same-origin/none.
|
|
33
|
+
return checkSecFetchSite(headers);
|
|
34
|
+
}
|
|
35
|
+
return ctx.allowedOrigins.has(origin.toLowerCase());
|
|
36
|
+
}
|
|
37
|
+
export function checkSecFetchSite(headers) {
|
|
38
|
+
const v = headers["sec-fetch-site"];
|
|
39
|
+
if (typeof v !== "string") {
|
|
40
|
+
// Older browsers / non-fetch clients don't send this; fall through.
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return v === "same-origin" || v === "none";
|
|
44
|
+
}
|
|
45
|
+
export function checkStateChanging(ctx, headers) {
|
|
46
|
+
if (!checkHost(ctx, headers)) {
|
|
47
|
+
return { ok: false, reason: "host not allowed", status: 421 };
|
|
48
|
+
}
|
|
49
|
+
if (!checkOrigin(ctx, headers)) {
|
|
50
|
+
return { ok: false, reason: "origin not allowed", status: 403 };
|
|
51
|
+
}
|
|
52
|
+
if (!checkSecFetchSite(headers)) {
|
|
53
|
+
return { ok: false, reason: "cross-site request blocked", status: 403 };
|
|
54
|
+
}
|
|
55
|
+
return { ok: true };
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=csrf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csrf.js","sourceRoot":"","sources":["../../src/util/csrf.ts"],"names":[],"mappings":"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"}
|
package/dist/util/log.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
let debugEnabled = false;
|
|
2
|
+
export function setDebug(on) {
|
|
3
|
+
debugEnabled = on;
|
|
4
|
+
}
|
|
5
|
+
function emit(level, scope, args) {
|
|
6
|
+
const ts = new Date().toISOString();
|
|
7
|
+
const stream = level === "error" || level === "warn" ? process.stderr : process.stdout;
|
|
8
|
+
stream.write(`[${ts}] ${level} [${scope}] ${formatArgs(args)}\n`);
|
|
9
|
+
}
|
|
10
|
+
function formatArgs(args) {
|
|
11
|
+
return args
|
|
12
|
+
.map((a) => {
|
|
13
|
+
if (typeof a === "string") {
|
|
14
|
+
return a;
|
|
15
|
+
}
|
|
16
|
+
if (a instanceof Error) {
|
|
17
|
+
return a.stack ?? a.message;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
return JSON.stringify(a);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return String(a);
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
.join(" ");
|
|
27
|
+
}
|
|
28
|
+
export function logger(scope) {
|
|
29
|
+
return {
|
|
30
|
+
debug(...args) {
|
|
31
|
+
if (debugEnabled) {
|
|
32
|
+
emit("debug", scope, args);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
info(...args) {
|
|
36
|
+
emit("info", scope, args);
|
|
37
|
+
},
|
|
38
|
+
warn(...args) {
|
|
39
|
+
emit("warn", scope, args);
|
|
40
|
+
},
|
|
41
|
+
error(...args) {
|
|
42
|
+
emit("error", scope, args);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/util/log.ts"],"names":[],"mappings":"AAEA,IAAI,YAAY,GAAG,KAAK,CAAC;AAEzB,MAAM,UAAU,QAAQ,CAAC,EAAW;IAClC,YAAY,GAAG,EAAE,CAAC;AACpB,CAAC;AAED,SAAS,IAAI,CAAC,KAAY,EAAE,KAAa,EAAE,IAAe;IACxD,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACpC,MAAM,MAAM,GACV,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IAC1E,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,KAAK,KAAK,KAAK,KAAK,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,UAAU,CAAC,IAAe;IACjC,OAAO,IAAI;SACR,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC1B,OAAO,CAAC,CAAC;QACX,CAAC;QACD,IAAI,CAAC,YAAY,KAAK,EAAE,CAAC;YACvB,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,OAAO,CAAC;QAC9B,CAAC;QACD,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;SACD,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO;QACL,KAAK,CAAC,GAAG,IAAe;YACtB,IAAI,YAAY,EAAE,CAAC;gBACjB,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,IAAI,CAAC,GAAG,IAAe;YACrB,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QAC5B,CAAC;QACD,IAAI,CAAC,GAAG,IAAe;YACrB,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QAC5B,CAAC;QACD,KAAK,CAAC,GAAG,IAAe;YACtB,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QAC7B,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
const HOME_DIR = `${homedir()}/.hydra-acp-browser`;
|
|
4
|
+
export function expandHome(p) {
|
|
5
|
+
if (p.startsWith("~/")) {
|
|
6
|
+
return resolve(homedir(), p.slice(2));
|
|
7
|
+
}
|
|
8
|
+
return p;
|
|
9
|
+
}
|
|
10
|
+
export const paths = {
|
|
11
|
+
home() {
|
|
12
|
+
return HOME_DIR;
|
|
13
|
+
},
|
|
14
|
+
authkeyFile() {
|
|
15
|
+
return `${HOME_DIR}/authkey`;
|
|
16
|
+
},
|
|
17
|
+
linkFile() {
|
|
18
|
+
return `${HOME_DIR}/link`;
|
|
19
|
+
},
|
|
20
|
+
configFile() {
|
|
21
|
+
return `${homedir()}/.hydra-acp-browser.conf`;
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/util/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,QAAQ,GAAG,GAAG,OAAO,EAAE,qBAAqB,CAAC;AAEnD,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,OAAO,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,IAAI;QACF,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,WAAW;QACT,OAAO,GAAG,QAAQ,UAAU,CAAC;IAC/B,CAAC;IACD,QAAQ;QACN,OAAO,GAAG,QAAQ,OAAO,CAAC;IAC5B,CAAC;IACD,UAAU;QACR,OAAO,GAAG,OAAO,EAAE,0BAA0B,CAAC;IAChD,CAAC;CACF,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hydra-acp/browser",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Browser-based UI for hydra-acp sessions.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"homepage": "https://github.com/smagnuso/hydra-acp-browser#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/smagnuso/hydra-acp-browser.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/smagnuso/hydra-acp-browser/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"acp",
|
|
17
|
+
"hydra-acp",
|
|
18
|
+
"browser",
|
|
19
|
+
"spa",
|
|
20
|
+
"agent-client-protocol",
|
|
21
|
+
"claude",
|
|
22
|
+
"codex"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public",
|
|
26
|
+
"registry": "https://registry.npmjs.org"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"main": "dist/index.js",
|
|
34
|
+
"bin": {
|
|
35
|
+
"hydra-acp-browser": "dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc -p . && tsc -p tsconfig.ui.json && node scripts/build-ui.mjs",
|
|
39
|
+
"build:server": "tsc -p .",
|
|
40
|
+
"build:ui": "tsc -p tsconfig.ui.json && node scripts/build-ui.mjs",
|
|
41
|
+
"watch": "tsc -p . --watch",
|
|
42
|
+
"start": "node dist/index.js",
|
|
43
|
+
"dev": "npm run build && node dist/index.js",
|
|
44
|
+
"test": "node --test --import tsx 'test/**/*.test.ts'",
|
|
45
|
+
"lint": "tsc -p . --noEmit && tsc -p tsconfig.ui.json --noEmit",
|
|
46
|
+
"prepublishOnly": "npm run build && npm test"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=20"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@fastify/cookie": "^11.0.2",
|
|
53
|
+
"fastify": "^5.0.0",
|
|
54
|
+
"ws": "^8.20.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^22.10.0",
|
|
58
|
+
"@types/ws": "^8.18.1",
|
|
59
|
+
"esbuild": "^0.28.0",
|
|
60
|
+
"tsx": "^4.20.0",
|
|
61
|
+
"typescript": "^5.6.3"
|
|
62
|
+
}
|
|
63
|
+
}
|