@townco/cli 0.1.118 → 0.1.119

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.
@@ -8,7 +8,6 @@ declare const _default: {
8
8
  readonly prompt: string | undefined;
9
9
  readonly port: number | undefined;
10
10
  readonly noSession: true | undefined;
11
- readonly proxy: true | undefined;
12
11
  }, ["matched", string] | ["parsing", {
13
12
  readonly command: "run";
14
13
  readonly name: import("@optique/core").ValueParserResult<string> | undefined;
@@ -18,7 +17,6 @@ declare const _default: {
18
17
  readonly prompt: [import("@optique/core").ValueParserResult<string> | undefined] | undefined;
19
18
  readonly port: [import("@optique/core").ValueParserResult<number> | undefined] | undefined;
20
19
  readonly noSession: [import("@optique/core").ValueParserResult<true> | undefined] | undefined;
21
- readonly proxy: [import("@optique/core").ValueParserResult<true> | undefined] | undefined;
22
20
  }] | undefined>;
23
21
  impl: (def: {
24
22
  readonly command: "run";
@@ -29,7 +27,6 @@ declare const _default: {
29
27
  readonly prompt: string | undefined;
30
28
  readonly port: number | undefined;
31
29
  readonly noSession: true | undefined;
32
- readonly proxy: true | undefined;
33
30
  }) => unknown;
34
31
  };
35
32
  export default _default;
@@ -12,16 +12,14 @@ export default createCommand({
12
12
  prompt: optional(argument(string({ metavar: "PROMPT" }))),
13
13
  port: optional(option("-p", "--port", integer())),
14
14
  noSession: optional(flag("--no-session")),
15
- proxy: optional(flag("--proxy")),
16
15
  }), { brief: message `Run an agent.` }),
17
- impl: async ({ name, http, gui, cli, prompt, port, noSession, proxy }) => {
16
+ impl: async ({ name, http, gui, cli, prompt, port, noSession }) => {
18
17
  const options = {
19
18
  name,
20
19
  http: http === true,
21
20
  gui: gui === true,
22
21
  cli: cli === true,
23
22
  noSession: noSession === true,
24
- proxy: proxy === true,
25
23
  };
26
24
  if (prompt !== null && prompt !== undefined) {
27
25
  options.prompt = prompt;
@@ -6,6 +6,5 @@ export interface RunCommandOptions {
6
6
  prompt?: string;
7
7
  port?: number;
8
8
  noSession?: boolean;
9
- proxy?: boolean;
10
9
  }
11
10
  export declare function runCommand(options: RunCommandOptions): Promise<void>;
@@ -13,7 +13,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
13
13
  import { LogsPane } from "../components/LogsPane.js";
14
14
  import { TabbedOutput } from "../components/TabbedOutput.js";
15
15
  import { ensurePortAvailable } from "../lib/port-utils.js";
16
- import { createProxyServer } from "../lib/proxy-server.js";
17
16
  function TuiRunner({ agentPath, workingDir, agentName, noSession, onExit, }) {
18
17
  const [client, setClient] = useState(null);
19
18
  const [error, setError] = useState(null);
@@ -83,15 +82,15 @@ function TuiRunner({ agentPath, workingDir, agentName, noSession, onExit, }) {
83
82
  ], [client, error, workingDir]);
84
83
  return (_jsx(TabbedOutput, { processes: [], customTabs: customTabs, onExit: onExit }));
85
84
  }
86
- function GuiRunner({ agentProcess, guiProcess, agentPort, browserPort, agentPath, logger, onExit, }) {
85
+ function GuiRunner({ agentProcess, guiProcess, agentPort, agentPath, logger, onExit, }) {
87
86
  const browserOpenedRef = useRef(false);
88
- const handlePortDetected = useCallback((processIndex, _port) => {
87
+ const handlePortDetected = useCallback((processIndex, port) => {
89
88
  // Process index 1 is the GUI process
90
89
  if (processIndex === 1) {
91
- // Open browser once we know the GUI is ready
90
+ // Open browser once we know the actual port
92
91
  if (!browserOpenedRef.current) {
93
92
  browserOpenedRef.current = true;
94
- const guiUrl = `http://localhost:${browserPort}`;
93
+ const guiUrl = `http://localhost:${port}`;
95
94
  logger.info("Opening browser", { url: guiUrl });
96
95
  open(guiUrl).catch((error) => {
97
96
  logger.warn("Could not automatically open browser", {
@@ -101,7 +100,7 @@ function GuiRunner({ agentProcess, guiProcess, agentPort, browserPort, agentPath
101
100
  });
102
101
  }
103
102
  }
104
- }, [logger, browserPort]);
103
+ }, [logger]);
105
104
  // Memoize processes array based only on actual process objects and initial ports
106
105
  // Don't include guiPort as dependency to prevent re-creating array when port is detected
107
106
  // TabbedOutput will update the displayed port internally via onPortDetected callback
@@ -375,7 +374,7 @@ async function runCliMode(options) {
375
374
  }
376
375
  }
377
376
  export async function runCommand(options) {
378
- const { name, http = false, gui = false, cli = false, prompt, port = 3100, noSession = false, proxy = false, } = options;
377
+ const { name, http = false, gui = false, cli = false, prompt, port = 3100, noSession = false, } = options;
379
378
  // Check if we're inside a Town project
380
379
  const projectRoot = await isInsideTownProject();
381
380
  if (projectRoot === null) {
@@ -513,15 +512,9 @@ export async function runCommand(options) {
513
512
  }
514
513
  // Ensure agent and GUI ports are available (debugger ports already checked above)
515
514
  const guiPort = 5173;
516
- const proxyPort = process.env.TOWN_AGENT_PROXY_PORT
517
- ? Number.parseInt(process.env.TOWN_AGENT_PROXY_PORT, 10)
518
- : 3500;
519
515
  try {
520
516
  await ensurePortAvailable(port, "agent HTTP server");
521
517
  await ensurePortAvailable(guiPort, "GUI dev server");
522
- if (proxy) {
523
- await ensurePortAvailable(proxyPort, "reverse proxy");
524
- }
525
518
  }
526
519
  catch (error) {
527
520
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -531,18 +524,10 @@ export async function runCommand(options) {
531
524
  logger.info("Starting GUI mode", {
532
525
  agentPort: port,
533
526
  guiPort,
534
- ...(proxy ? { proxyPort } : {}),
535
527
  });
536
528
  console.log(`Starting agent "${name}" with GUI...`);
537
- if (proxy) {
538
- console.log(`Proxy server running on port ${proxyPort}`);
539
- console.log(` - GUI: http://localhost:${proxyPort}/`);
540
- console.log(` - Agent API: http://localhost:${proxyPort}/rpc\n`);
541
- }
542
- else {
543
- console.log(`GUI: http://localhost:${guiPort}/`);
544
- console.log(`Agent API: http://localhost:${port}/rpc\n`);
545
- }
529
+ console.log(`Agent HTTP server will run on port ${port}`);
530
+ console.log(`GUI dev server will run on port ${guiPort}\n`);
546
531
  // Set stdin to raw mode for Ink
547
532
  if (process.stdin.isTTY) {
548
533
  process.stdin.setRawMode(true);
@@ -566,17 +551,13 @@ export async function runCommand(options) {
566
551
  if (process.env.BIND_HOST) {
567
552
  viteArgs.push("--host", process.env.BIND_HOST);
568
553
  }
569
- // When proxy is enabled, GUI talks to proxy; otherwise directly to agent
570
- const agentUrl = proxy
571
- ? `http://localhost:${proxyPort}`
572
- : `http://localhost:${port}`;
573
554
  const guiProcess = spawn("bunx", viteArgs, {
574
555
  cwd: guiPath,
575
556
  stdio: ["ignore", "pipe", "pipe"], // Pipe stdout/stderr for capture
576
557
  env: {
577
558
  ...process.env,
578
559
  ...configEnvVars,
579
- VITE_AGENT_URL: process.env.EXT_HOST ?? agentUrl,
560
+ VITE_AGENT_URL: `${process.env.EXT_HOST || "http://localhost"}:${port}`,
580
561
  VITE_DEBUGGER_URL: `http://localhost:${debuggerUiPort}`,
581
562
  // If agent uses library MCP, pass LIBRARY_API_URL to GUI for auth
582
563
  ...(usesLibraryMcp &&
@@ -586,32 +567,12 @@ export async function runCommand(options) {
586
567
  }),
587
568
  },
588
569
  });
589
- // Start the reverse proxy server if enabled
590
- const proxyServer = proxy
591
- ? createProxyServer({
592
- port: proxyPort,
593
- vitePort: guiPort,
594
- agentPort: port,
595
- basicAuthUser: process.env.BASIC_AUTH_USER,
596
- basicAuthPass: process.env.BASIC_AUTH_PASS,
597
- logger,
598
- })
599
- : null;
600
- // Setup cleanup handlers for agent, GUI, and proxy processes
570
+ // Setup cleanup handlers for agent and GUI processes
601
571
  let isCleaningUp = false;
602
572
  const cleanupProcesses = () => {
603
573
  if (isCleaningUp)
604
574
  return;
605
575
  isCleaningUp = true;
606
- // Stop proxy server if running
607
- if (proxyServer) {
608
- try {
609
- proxyServer.stop();
610
- }
611
- catch (_e) {
612
- // Proxy may already be stopped
613
- }
614
- }
615
576
  // Kill child processes - since they're not detached, they're in our process group
616
577
  try {
617
578
  agentProcess.kill("SIGTERM");
@@ -639,9 +600,7 @@ export async function runCommand(options) {
639
600
  cleanupDebugger?.();
640
601
  };
641
602
  // Render the tabbed UI with dynamic port detection
642
- // Browser opens to proxy port if enabled, otherwise to GUI port
643
- const browserPort = proxy ? proxyPort : guiPort;
644
- const { waitUntilExit } = render(_jsx(GuiRunner, { agentProcess: agentProcess, guiProcess: guiProcess, agentPort: port, browserPort: browserPort, agentPath: agentPath, logger: logger, onExit: () => {
603
+ const { waitUntilExit } = render(_jsx(GuiRunner, { agentProcess: agentProcess, guiProcess: guiProcess, agentPort: port, agentPath: agentPath, logger: logger, onExit: () => {
645
604
  cleanupProcesses();
646
605
  } }));
647
606
  // Register signal handlers AFTER Ink render to ensure they take precedence
package/dist/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@townco/cli",
3
- "version": "0.1.118",
3
+ "version": "0.1.119",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "town": "./dist/index.js"
@@ -15,23 +15,22 @@
15
15
  "build": "tsgo"
16
16
  },
17
17
  "devDependencies": {
18
- "@townco/tsconfig": "0.1.110",
18
+ "@townco/tsconfig": "0.1.111",
19
19
  "@types/archiver": "^7.0.0",
20
20
  "@types/bun": "^1.3.1",
21
- "@types/http-proxy": "^1.17.14",
22
21
  "@types/ignore-walk": "^4.0.3",
23
22
  "@types/react": "^19.2.2"
24
23
  },
25
24
  "dependencies": {
26
25
  "@optique/core": "^0.6.2",
27
26
  "@optique/run": "^0.6.2",
28
- "@townco/agent": "0.1.121",
29
- "@townco/apiclient": "0.0.33",
30
- "@townco/core": "0.0.91",
31
- "@townco/debugger": "0.1.69",
32
- "@townco/env": "0.1.63",
33
- "@townco/secret": "0.1.113",
34
- "@townco/ui": "0.1.113",
27
+ "@townco/agent": "0.1.122",
28
+ "@townco/apiclient": "0.0.34",
29
+ "@townco/core": "0.0.92",
30
+ "@townco/debugger": "0.1.70",
31
+ "@townco/env": "0.1.64",
32
+ "@townco/secret": "0.1.114",
33
+ "@townco/ui": "0.1.114",
35
34
  "@trpc/client": "^11.7.2",
36
35
  "archiver": "^7.0.1",
37
36
  "eventsource": "^4.1.0",
@@ -42,7 +41,6 @@
42
41
  "open": "^10.2.0",
43
42
  "superjson": "^2.2.5",
44
43
  "ts-pattern": "^5.9.0",
45
- "http-proxy": "^1.18.1",
46
44
  "zod": "^4.1.12"
47
45
  },
48
46
  "peerDependencies": {
@@ -1,15 +0,0 @@
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;
@@ -1,96 +0,0 @@
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
- }