@townco/cli 0.1.127 → 0.1.129
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/commands/run-wrapper.d.ts +3 -0
- package/dist/commands/run-wrapper.js +3 -1
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +52 -11
- package/dist/index.js +0 -0
- package/package.json +11 -9
|
@@ -8,6 +8,7 @@ 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;
|
|
11
12
|
}, ["matched", string] | ["parsing", {
|
|
12
13
|
readonly command: "run";
|
|
13
14
|
readonly name: import("@optique/core").ValueParserResult<string> | undefined;
|
|
@@ -17,6 +18,7 @@ declare const _default: {
|
|
|
17
18
|
readonly prompt: [import("@optique/core").ValueParserResult<string> | undefined] | undefined;
|
|
18
19
|
readonly port: [import("@optique/core").ValueParserResult<number> | undefined] | undefined;
|
|
19
20
|
readonly noSession: [import("@optique/core").ValueParserResult<true> | undefined] | undefined;
|
|
21
|
+
readonly proxy: [import("@optique/core").ValueParserResult<true> | undefined] | undefined;
|
|
20
22
|
}] | undefined>;
|
|
21
23
|
impl: (def: {
|
|
22
24
|
readonly command: "run";
|
|
@@ -27,6 +29,7 @@ declare const _default: {
|
|
|
27
29
|
readonly prompt: string | undefined;
|
|
28
30
|
readonly port: number | undefined;
|
|
29
31
|
readonly noSession: true | undefined;
|
|
32
|
+
readonly proxy: true | undefined;
|
|
30
33
|
}) => unknown;
|
|
31
34
|
};
|
|
32
35
|
export default _default;
|
|
@@ -12,14 +12,16 @@ 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")),
|
|
15
16
|
}), { brief: message `Run an agent.` }),
|
|
16
|
-
impl: async ({ name, http, gui, cli, prompt, port, noSession }) => {
|
|
17
|
+
impl: async ({ name, http, gui, cli, prompt, port, noSession, proxy }) => {
|
|
17
18
|
const options = {
|
|
18
19
|
name,
|
|
19
20
|
http: http === true,
|
|
20
21
|
gui: gui === true,
|
|
21
22
|
cli: cli === true,
|
|
22
23
|
noSession: noSession === true,
|
|
24
|
+
proxy: proxy === true || process.env.TOWN_RUN_PROXY === "true",
|
|
23
25
|
};
|
|
24
26
|
if (prompt !== null && prompt !== undefined) {
|
|
25
27
|
options.prompt = prompt;
|
package/dist/commands/run.d.ts
CHANGED
package/dist/commands/run.js
CHANGED
|
@@ -13,6 +13,7 @@ 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";
|
|
16
17
|
function TuiRunner({ agentPath, workingDir, agentName, noSession, onExit, }) {
|
|
17
18
|
const [client, setClient] = useState(null);
|
|
18
19
|
const [error, setError] = useState(null);
|
|
@@ -82,15 +83,15 @@ function TuiRunner({ agentPath, workingDir, agentName, noSession, onExit, }) {
|
|
|
82
83
|
], [client, error, workingDir]);
|
|
83
84
|
return (_jsx(TabbedOutput, { processes: [], customTabs: customTabs, onExit: onExit }));
|
|
84
85
|
}
|
|
85
|
-
function GuiRunner({ agentProcess, guiProcess, agentPort, agentPath, logger, onExit, }) {
|
|
86
|
+
function GuiRunner({ agentProcess, guiProcess, agentPort, browserPort, agentPath, logger, onExit, }) {
|
|
86
87
|
const browserOpenedRef = useRef(false);
|
|
87
|
-
const handlePortDetected = useCallback((processIndex,
|
|
88
|
+
const handlePortDetected = useCallback((processIndex, _port) => {
|
|
88
89
|
// Process index 1 is the GUI process
|
|
89
90
|
if (processIndex === 1) {
|
|
90
|
-
// Open browser once we know the
|
|
91
|
+
// Open browser once we know the GUI is ready
|
|
91
92
|
if (!browserOpenedRef.current) {
|
|
92
93
|
browserOpenedRef.current = true;
|
|
93
|
-
const guiUrl = `http://localhost:${
|
|
94
|
+
const guiUrl = `http://localhost:${browserPort}`;
|
|
94
95
|
logger.info("Opening browser", { url: guiUrl });
|
|
95
96
|
open(guiUrl).catch((error) => {
|
|
96
97
|
logger.warn("Could not automatically open browser", {
|
|
@@ -100,7 +101,7 @@ function GuiRunner({ agentProcess, guiProcess, agentPort, agentPath, logger, onE
|
|
|
100
101
|
});
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
|
-
}, [logger]);
|
|
104
|
+
}, [logger, browserPort]);
|
|
104
105
|
// Memoize processes array based only on actual process objects and initial ports
|
|
105
106
|
// Don't include guiPort as dependency to prevent re-creating array when port is detected
|
|
106
107
|
// TabbedOutput will update the displayed port internally via onPortDetected callback
|
|
@@ -374,7 +375,7 @@ async function runCliMode(options) {
|
|
|
374
375
|
}
|
|
375
376
|
}
|
|
376
377
|
export async function runCommand(options) {
|
|
377
|
-
const { name, http = false, gui = false, cli = false, prompt, port = 3100, noSession = false, } = options;
|
|
378
|
+
const { name, http = false, gui = false, cli = false, prompt, port = 3100, noSession = false, proxy = false, } = options;
|
|
378
379
|
// Check if we're inside a Town project
|
|
379
380
|
const projectRoot = await isInsideTownProject();
|
|
380
381
|
if (projectRoot === null) {
|
|
@@ -512,9 +513,15 @@ export async function runCommand(options) {
|
|
|
512
513
|
}
|
|
513
514
|
// Ensure agent and GUI ports are available (debugger ports already checked above)
|
|
514
515
|
const guiPort = 5173;
|
|
516
|
+
const proxyPort = process.env.TOWN_AGENT_PROXY_PORT
|
|
517
|
+
? Number.parseInt(process.env.TOWN_AGENT_PROXY_PORT, 10)
|
|
518
|
+
: 3500;
|
|
515
519
|
try {
|
|
516
520
|
await ensurePortAvailable(port, "agent HTTP server");
|
|
517
521
|
await ensurePortAvailable(guiPort, "GUI dev server");
|
|
522
|
+
if (proxy) {
|
|
523
|
+
await ensurePortAvailable(proxyPort, "reverse proxy");
|
|
524
|
+
}
|
|
518
525
|
}
|
|
519
526
|
catch (error) {
|
|
520
527
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -524,10 +531,18 @@ export async function runCommand(options) {
|
|
|
524
531
|
logger.info("Starting GUI mode", {
|
|
525
532
|
agentPort: port,
|
|
526
533
|
guiPort,
|
|
534
|
+
...(proxy ? { proxyPort } : {}),
|
|
527
535
|
});
|
|
528
536
|
console.log(`Starting agent "${name}" with GUI...`);
|
|
529
|
-
|
|
530
|
-
|
|
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
|
+
}
|
|
531
546
|
// Set stdin to raw mode for Ink
|
|
532
547
|
if (process.stdin.isTTY) {
|
|
533
548
|
process.stdin.setRawMode(true);
|
|
@@ -551,13 +566,17 @@ export async function runCommand(options) {
|
|
|
551
566
|
if (process.env.BIND_HOST) {
|
|
552
567
|
viteArgs.push("--host", process.env.BIND_HOST);
|
|
553
568
|
}
|
|
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}`;
|
|
554
573
|
const guiProcess = spawn("bunx", viteArgs, {
|
|
555
574
|
cwd: guiPath,
|
|
556
575
|
stdio: ["ignore", "pipe", "pipe"], // Pipe stdout/stderr for capture
|
|
557
576
|
env: {
|
|
558
577
|
...process.env,
|
|
559
578
|
...configEnvVars,
|
|
560
|
-
VITE_AGENT_URL:
|
|
579
|
+
VITE_AGENT_URL: process.env.EXT_HOST ?? agentUrl,
|
|
561
580
|
VITE_DEBUGGER_URL: `http://localhost:${debuggerUiPort}`,
|
|
562
581
|
// If agent uses library MCP, pass LIBRARY_API_URL to GUI for auth
|
|
563
582
|
...(usesLibraryMcp &&
|
|
@@ -567,12 +586,32 @@ export async function runCommand(options) {
|
|
|
567
586
|
}),
|
|
568
587
|
},
|
|
569
588
|
});
|
|
570
|
-
//
|
|
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
|
|
571
601
|
let isCleaningUp = false;
|
|
572
602
|
const cleanupProcesses = () => {
|
|
573
603
|
if (isCleaningUp)
|
|
574
604
|
return;
|
|
575
605
|
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
|
+
}
|
|
576
615
|
// Kill child processes - since they're not detached, they're in our process group
|
|
577
616
|
try {
|
|
578
617
|
agentProcess.kill("SIGTERM");
|
|
@@ -600,7 +639,9 @@ export async function runCommand(options) {
|
|
|
600
639
|
cleanupDebugger?.();
|
|
601
640
|
};
|
|
602
641
|
// Render the tabbed UI with dynamic port detection
|
|
603
|
-
|
|
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: () => {
|
|
604
645
|
cleanupProcesses();
|
|
605
646
|
} }));
|
|
606
647
|
// 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.
|
|
3
|
+
"version": "0.1.129",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"town": "./dist/index.js"
|
|
@@ -15,22 +15,23 @@
|
|
|
15
15
|
"build": "tsgo"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@townco/tsconfig": "0.1.
|
|
18
|
+
"@townco/tsconfig": "0.1.121",
|
|
19
19
|
"@types/archiver": "^7.0.0",
|
|
20
20
|
"@types/bun": "^1.3.1",
|
|
21
|
+
"@types/http-proxy": "^1.17.14",
|
|
21
22
|
"@types/ignore-walk": "^4.0.3",
|
|
22
23
|
"@types/react": "^19.2.2"
|
|
23
24
|
},
|
|
24
25
|
"dependencies": {
|
|
25
26
|
"@optique/core": "^0.6.2",
|
|
26
27
|
"@optique/run": "^0.6.2",
|
|
27
|
-
"@townco/agent": "0.1.
|
|
28
|
-
"@townco/apiclient": "0.0.
|
|
29
|
-
"@townco/core": "0.0.
|
|
30
|
-
"@townco/debugger": "0.1.
|
|
31
|
-
"@townco/env": "0.1.
|
|
32
|
-
"@townco/secret": "0.1.
|
|
33
|
-
"@townco/ui": "0.1.
|
|
28
|
+
"@townco/agent": "0.1.132",
|
|
29
|
+
"@townco/apiclient": "0.0.44",
|
|
30
|
+
"@townco/core": "0.0.102",
|
|
31
|
+
"@townco/debugger": "0.1.80",
|
|
32
|
+
"@townco/env": "0.1.74",
|
|
33
|
+
"@townco/secret": "0.1.124",
|
|
34
|
+
"@townco/ui": "0.1.124",
|
|
34
35
|
"@trpc/client": "^11.7.2",
|
|
35
36
|
"archiver": "^7.0.1",
|
|
36
37
|
"eventsource": "^4.1.0",
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"open": "^10.2.0",
|
|
42
43
|
"superjson": "^2.2.5",
|
|
43
44
|
"ts-pattern": "^5.9.0",
|
|
45
|
+
"http-proxy": "^1.18.1",
|
|
44
46
|
"zod": "^4.1.12"
|
|
45
47
|
},
|
|
46
48
|
"peerDependencies": {
|