@townco/cli 0.1.125 → 0.1.126

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/index.js CHANGED
File without changes
@@ -0,0 +1,15 @@
1
+ import http from "node:http";
2
+ import type { createLogger } from "@townco/core";
3
+ export interface ProxyServerOptions {
4
+ port: number;
5
+ vitePort: number;
6
+ agentPort: number;
7
+ basicAuthUser: string | undefined;
8
+ basicAuthPass: string | undefined;
9
+ logger: ReturnType<typeof createLogger>;
10
+ }
11
+ export interface ProxyServer {
12
+ server: http.Server;
13
+ stop: () => Promise<void>;
14
+ }
15
+ export declare function createProxyServer(options: ProxyServerOptions): ProxyServer;
@@ -0,0 +1,96 @@
1
+ import crypto from "node:crypto";
2
+ import http from "node:http";
3
+ import httpProxy from "http-proxy";
4
+ // Agent routes that should be proxied to the agent server
5
+ const AGENT_ROUTES = [
6
+ "/rpc",
7
+ "/events",
8
+ "/health",
9
+ "/sessions",
10
+ "/sandbox",
11
+ "/static",
12
+ "/logs",
13
+ ];
14
+ function timingSafeEqual(a, b) {
15
+ const aBuf = Buffer.from(a);
16
+ const bBuf = Buffer.from(b);
17
+ // timingSafeEqual requires equal lengths, so pad the shorter buffer
18
+ const maxLen = Math.max(aBuf.length, bBuf.length);
19
+ const paddedA = Buffer.alloc(maxLen);
20
+ const paddedB = Buffer.alloc(maxLen);
21
+ aBuf.copy(paddedA);
22
+ bBuf.copy(paddedB);
23
+ // Always do both comparisons to avoid leaking length information via timing
24
+ const lengthsMatch = aBuf.length === bBuf.length;
25
+ const contentsMatch = crypto.timingSafeEqual(paddedA, paddedB);
26
+ return lengthsMatch && contentsMatch;
27
+ }
28
+ function getTarget(pathname, options) {
29
+ // Check for agent routes
30
+ for (const route of AGENT_ROUTES) {
31
+ if (pathname === route || pathname.startsWith(`${route}/`)) {
32
+ return `http://localhost:${options.agentPort}`;
33
+ }
34
+ }
35
+ // Default: Vite server
36
+ return `http://localhost:${options.vitePort}`;
37
+ }
38
+ export function createProxyServer(options) {
39
+ const { port, basicAuthUser, basicAuthPass, logger } = options;
40
+ const proxy = httpProxy.createProxyServer({
41
+ ws: true,
42
+ changeOrigin: true,
43
+ // Don't add X-Forwarded headers
44
+ xfwd: false,
45
+ });
46
+ // Handle proxy errors
47
+ proxy.on("error", (err, req, res) => {
48
+ logger.error("Proxy error", { error: err.message, url: req.url });
49
+ if (res && "writeHead" in res) {
50
+ res.writeHead(502, {
51
+ "Content-Type": "text/plain",
52
+ });
53
+ res.end("Bad Gateway");
54
+ }
55
+ });
56
+ // Build expected auth header if auth is configured
57
+ const expectedAuth = basicAuthUser && basicAuthPass
58
+ ? `Basic ${Buffer.from(`${basicAuthUser}:${basicAuthPass}`).toString("base64")}`
59
+ : null;
60
+ const server = http.createServer((req, res) => {
61
+ // Basic auth check
62
+ if (expectedAuth) {
63
+ const authHeader = req.headers.authorization ?? "";
64
+ if (!timingSafeEqual(authHeader, expectedAuth)) {
65
+ res.statusCode = 401;
66
+ res.setHeader("WWW-Authenticate", "Basic");
67
+ res.end("Unauthorized");
68
+ return;
69
+ }
70
+ }
71
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
72
+ const target = getTarget(url.pathname, options);
73
+ // Proxy the request
74
+ proxy.web(req, res, { target });
75
+ });
76
+ // Handle WebSocket upgrades (for Vite HMR)
77
+ server.on("upgrade", (req, socket, head) => {
78
+ // WebSocket upgrades go to Vite for HMR
79
+ // Note: We don't do auth on WebSocket upgrades since Vite HMR is internal
80
+ const viteTarget = `http://localhost:${options.vitePort}`;
81
+ proxy.ws(req, socket, head, { target: viteTarget });
82
+ });
83
+ server.on("error", (err) => {
84
+ logger.error("Proxy server error", { error: err.message, port });
85
+ });
86
+ server.listen(port, () => {
87
+ logger.info("Proxy server started", { port });
88
+ });
89
+ const stop = () => {
90
+ return new Promise((resolve) => {
91
+ proxy.close();
92
+ server.close(() => resolve());
93
+ });
94
+ };
95
+ return { server, stop };
96
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@townco/cli",
3
- "version": "0.1.125",
3
+ "version": "0.1.126",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "town": "./dist/index.js"
@@ -15,7 +15,7 @@
15
15
  "build": "tsgo"
16
16
  },
17
17
  "devDependencies": {
18
- "@townco/tsconfig": "0.1.117",
18
+ "@townco/tsconfig": "0.1.118",
19
19
  "@types/archiver": "^7.0.0",
20
20
  "@types/bun": "^1.3.1",
21
21
  "@types/ignore-walk": "^4.0.3",
@@ -24,13 +24,13 @@
24
24
  "dependencies": {
25
25
  "@optique/core": "^0.6.2",
26
26
  "@optique/run": "^0.6.2",
27
- "@townco/agent": "0.1.128",
28
- "@townco/apiclient": "0.0.40",
29
- "@townco/core": "0.0.98",
30
- "@townco/debugger": "0.1.76",
31
- "@townco/env": "0.1.70",
32
- "@townco/secret": "0.1.120",
33
- "@townco/ui": "0.1.120",
27
+ "@townco/agent": "0.1.129",
28
+ "@townco/apiclient": "0.0.41",
29
+ "@townco/core": "0.0.99",
30
+ "@townco/debugger": "0.1.77",
31
+ "@townco/env": "0.1.71",
32
+ "@townco/secret": "0.1.121",
33
+ "@townco/ui": "0.1.121",
34
34
  "@trpc/client": "^11.7.2",
35
35
  "archiver": "^7.0.1",
36
36
  "eventsource": "^4.1.0",