@townco/cli 0.1.50 → 0.1.52
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/create.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
interface MCPAddProps {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
name?: string;
|
|
3
|
+
url?: string;
|
|
4
|
+
command?: string;
|
|
5
|
+
args?: readonly string[];
|
|
6
6
|
}
|
|
7
|
-
declare function MCPAddApp({
|
|
8
|
-
name: initialName,
|
|
9
|
-
url: initialUrl,
|
|
10
|
-
command: initialCommand,
|
|
11
|
-
args: initialArgs,
|
|
12
|
-
}: MCPAddProps): import("react/jsx-runtime").JSX.Element | null;
|
|
7
|
+
declare function MCPAddApp({ name: initialName, url: initialUrl, command: initialCommand, args: initialArgs, }: MCPAddProps): import("react/jsx-runtime").JSX.Element | null;
|
|
13
8
|
export default MCPAddApp;
|
|
14
9
|
export declare function runMCPAdd(props?: MCPAddProps): Promise<void>;
|
package/dist/commands/run.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
|
-
import { join } from "node:path";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
6
|
import { isInsideTownProject } from "@townco/agent/storage";
|
|
7
7
|
import { configureLogsDir, createLogger } from "@townco/core";
|
|
8
8
|
import { AcpClient } from "@townco/ui";
|
|
@@ -38,7 +38,10 @@ function TuiRunner({ agentPath, workingDir, noSession, onExit, }) {
|
|
|
38
38
|
options: {
|
|
39
39
|
agentPath,
|
|
40
40
|
workingDirectory: workingDir,
|
|
41
|
-
environment:
|
|
41
|
+
environment: {
|
|
42
|
+
ENABLE_TELEMETRY: "true",
|
|
43
|
+
...(noSession ? { TOWN_NO_SESSION: "true" } : {}),
|
|
44
|
+
},
|
|
42
45
|
},
|
|
43
46
|
});
|
|
44
47
|
setClient(newClient);
|
|
@@ -156,6 +159,25 @@ export async function runCommand(options) {
|
|
|
156
159
|
}
|
|
157
160
|
// Load environment variables from project .env
|
|
158
161
|
const configEnvVars = await loadEnvVars(projectRoot);
|
|
162
|
+
// Start the debugger server as subprocess (OTLP collector + UI)
|
|
163
|
+
const debuggerPkgPath = require.resolve("@townco/debugger/package.json");
|
|
164
|
+
const debuggerDir = dirname(debuggerPkgPath);
|
|
165
|
+
const debuggerProcess = spawn("bun", ["src/index.ts"], {
|
|
166
|
+
cwd: debuggerDir,
|
|
167
|
+
stdio: "inherit",
|
|
168
|
+
env: {
|
|
169
|
+
...process.env,
|
|
170
|
+
DB_PATH: join(projectRoot, ".traces.db"),
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
console.log(`Debugger UI: http://localhost:4000`);
|
|
174
|
+
// Cleanup debugger process on exit
|
|
175
|
+
const cleanupDebugger = () => {
|
|
176
|
+
debuggerProcess.kill();
|
|
177
|
+
};
|
|
178
|
+
process.on("exit", cleanupDebugger);
|
|
179
|
+
process.on("SIGINT", cleanupDebugger);
|
|
180
|
+
process.on("SIGTERM", cleanupDebugger);
|
|
159
181
|
// Resolve agent path within the project
|
|
160
182
|
const agentPath = join(projectRoot, "agents", name);
|
|
161
183
|
// Check if agent exists
|
|
@@ -220,6 +242,7 @@ export async function runCommand(options) {
|
|
|
220
242
|
...configEnvVars,
|
|
221
243
|
NODE_ENV: process.env.NODE_ENV || "production",
|
|
222
244
|
PORT: availablePort.toString(),
|
|
245
|
+
ENABLE_TELEMETRY: "true",
|
|
223
246
|
...(noSession ? { TOWN_NO_SESSION: "true" } : {}),
|
|
224
247
|
},
|
|
225
248
|
});
|
|
@@ -266,6 +289,7 @@ export async function runCommand(options) {
|
|
|
266
289
|
...configEnvVars,
|
|
267
290
|
NODE_ENV: process.env.NODE_ENV || "production",
|
|
268
291
|
PORT: availablePort.toString(),
|
|
292
|
+
ENABLE_TELEMETRY: "true",
|
|
269
293
|
...(noSession ? { TOWN_NO_SESSION: "true" } : {}),
|
|
270
294
|
},
|
|
271
295
|
});
|
|
@@ -19,6 +19,19 @@ function parseLogLevel(line) {
|
|
|
19
19
|
}
|
|
20
20
|
return null;
|
|
21
21
|
}
|
|
22
|
+
// Parse timestamp from a JSON log line
|
|
23
|
+
function parseTimestamp(line) {
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(line);
|
|
26
|
+
if (parsed.timestamp && typeof parsed.timestamp === "string") {
|
|
27
|
+
return parsed.timestamp;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Not JSON, use current time
|
|
32
|
+
}
|
|
33
|
+
return new Date().toISOString();
|
|
34
|
+
}
|
|
22
35
|
// Fuzzy search: checks if query characters appear in order in the target string
|
|
23
36
|
function fuzzyMatch(query, target) {
|
|
24
37
|
if (!query)
|
|
@@ -48,6 +61,7 @@ export function MergedLogsPane({ services, onClear }) {
|
|
|
48
61
|
}
|
|
49
62
|
}, [availableServices, serviceFilter]);
|
|
50
63
|
// Merge all outputs into a single array with service tags and parsed levels
|
|
64
|
+
// Sort by timestamp to show chronological order
|
|
51
65
|
const mergedLines = useMemo(() => {
|
|
52
66
|
const lines = [];
|
|
53
67
|
for (const [serviceIndex, service] of services.entries()) {
|
|
@@ -58,10 +72,14 @@ export function MergedLogsPane({ services, onClear }) {
|
|
|
58
72
|
index,
|
|
59
73
|
serviceIndex,
|
|
60
74
|
level: parseLogLevel(line),
|
|
75
|
+
timestamp: parseTimestamp(line),
|
|
61
76
|
});
|
|
62
77
|
}
|
|
63
78
|
}
|
|
64
|
-
|
|
79
|
+
// Sort by timestamp chronologically
|
|
80
|
+
return lines.sort((a, b) => {
|
|
81
|
+
return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
|
|
82
|
+
});
|
|
65
83
|
}, [services]);
|
|
66
84
|
// Handle keyboard input
|
|
67
85
|
useInput((input, key) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { appendFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
import { Box, useApp, useInput } from "ink";
|
|
5
6
|
import { useEffect, useRef, useState } from "react";
|
|
@@ -20,6 +21,7 @@ export function TabbedOutput({ processes, customTabs = [], logsDir, onExit, onPo
|
|
|
20
21
|
const [statuses, setStatuses] = useState(processes.map(() => "starting"));
|
|
21
22
|
const [ports, setPorts] = useState(processes.map((p) => p.port));
|
|
22
23
|
const portDetectedRef = useRef(new Set());
|
|
24
|
+
const [logFileOutputs, setLogFileOutputs] = useState({});
|
|
23
25
|
// Ensure logs directory exists if provided
|
|
24
26
|
useEffect(() => {
|
|
25
27
|
if (logsDir && !existsSync(logsDir)) {
|
|
@@ -172,6 +174,59 @@ export function TabbedOutput({ processes, customTabs = [], logsDir, onExit, onPo
|
|
|
172
174
|
});
|
|
173
175
|
};
|
|
174
176
|
}, [processes, onPortDetected, logsDir]);
|
|
177
|
+
// GUI mode: only process tabs, no custom tabs - use merged logs view
|
|
178
|
+
const isGuiMode = customTabs.length === 0 && processes.length > 0;
|
|
179
|
+
// Tail log files in GUI mode to capture internal service logs
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (!logsDir || !isGuiMode || !existsSync(logsDir)) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const tailProcesses = [];
|
|
185
|
+
const tailedFiles = new Set();
|
|
186
|
+
// Function to start tailing a specific log file
|
|
187
|
+
const tailLogFile = (file) => {
|
|
188
|
+
if (tailedFiles.has(file))
|
|
189
|
+
return;
|
|
190
|
+
tailedFiles.add(file);
|
|
191
|
+
const serviceName = file.replace(".log", "");
|
|
192
|
+
const filePath = join(logsDir, file);
|
|
193
|
+
// Use tail -F (follow by name, retry if file doesn't exist)
|
|
194
|
+
const tail = spawn("tail", ["-F", "-n", "50", filePath]);
|
|
195
|
+
tail.stdout.on("data", (data) => {
|
|
196
|
+
const lines = data
|
|
197
|
+
.toString()
|
|
198
|
+
.split("\n")
|
|
199
|
+
.filter((line) => line.trim());
|
|
200
|
+
setLogFileOutputs((prev) => ({
|
|
201
|
+
...prev,
|
|
202
|
+
[serviceName]: [...(prev[serviceName] || []), ...lines],
|
|
203
|
+
}));
|
|
204
|
+
});
|
|
205
|
+
tail.stderr.on("data", (_data) => {
|
|
206
|
+
// Ignore tail errors (like file not found initially)
|
|
207
|
+
});
|
|
208
|
+
tailProcesses.push(tail);
|
|
209
|
+
};
|
|
210
|
+
// Scan for log files and start tailing them
|
|
211
|
+
const scanLogFiles = () => {
|
|
212
|
+
if (!existsSync(logsDir))
|
|
213
|
+
return;
|
|
214
|
+
const logFiles = readdirSync(logsDir).filter((f) => f.endsWith(".log"));
|
|
215
|
+
for (const file of logFiles) {
|
|
216
|
+
tailLogFile(file);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
// Initial scan
|
|
220
|
+
scanLogFiles();
|
|
221
|
+
// Re-scan every 2 seconds to catch new log files
|
|
222
|
+
const scanInterval = setInterval(scanLogFiles, 2000);
|
|
223
|
+
return () => {
|
|
224
|
+
clearInterval(scanInterval);
|
|
225
|
+
for (const tail of tailProcesses) {
|
|
226
|
+
tail.kill();
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}, [logsDir, isGuiMode]);
|
|
175
230
|
const currentTab = allTabs[activeTab];
|
|
176
231
|
if (!currentTab) {
|
|
177
232
|
return null;
|
|
@@ -190,8 +245,6 @@ export function TabbedOutput({ processes, customTabs = [], logsDir, onExit, onPo
|
|
|
190
245
|
return newOutputs;
|
|
191
246
|
});
|
|
192
247
|
};
|
|
193
|
-
// GUI mode: only process tabs, no custom tabs - use merged logs view
|
|
194
|
-
const isGuiMode = customTabs.length === 0 && processes.length > 0;
|
|
195
248
|
if (isGuiMode) {
|
|
196
249
|
const serviceOutputs = processes.map((proc, idx) => ({
|
|
197
250
|
service: proc.name,
|
|
@@ -199,7 +252,15 @@ export function TabbedOutput({ processes, customTabs = [], logsDir, onExit, onPo
|
|
|
199
252
|
port: ports[idx],
|
|
200
253
|
status: statuses[idx] || "starting",
|
|
201
254
|
}));
|
|
202
|
-
|
|
255
|
+
// Add log file services (internal agent services like adapter, agent-runner, etc.)
|
|
256
|
+
const logFileServices = Object.entries(logFileOutputs).map(([service, output]) => ({
|
|
257
|
+
service,
|
|
258
|
+
output,
|
|
259
|
+
port: undefined,
|
|
260
|
+
status: "running",
|
|
261
|
+
}));
|
|
262
|
+
const allServices = [...serviceOutputs, ...logFileServices];
|
|
263
|
+
return (_jsx(Box, { flexDirection: "column", height: "100%", children: _jsx(MergedLogsPane, { services: allServices, onClear: handleClearMergedOutput }) }));
|
|
203
264
|
}
|
|
204
265
|
// TUI mode: has custom tabs - use tabbed interface
|
|
205
266
|
// Get status content from current tab if available
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@townco/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.52",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"town": "./dist/index.js"
|
|
@@ -15,17 +15,18 @@
|
|
|
15
15
|
"build": "tsc"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@townco/tsconfig": "0.1.
|
|
18
|
+
"@townco/tsconfig": "0.1.44",
|
|
19
19
|
"@types/bun": "^1.3.1",
|
|
20
20
|
"@types/react": "^19.2.2"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@optique/core": "^0.6.2",
|
|
24
24
|
"@optique/run": "^0.6.2",
|
|
25
|
-
"@townco/agent": "0.1.
|
|
26
|
-
"@townco/
|
|
27
|
-
"@townco/
|
|
28
|
-
"@townco/
|
|
25
|
+
"@townco/agent": "0.1.52",
|
|
26
|
+
"@townco/debugger": "0.1.2",
|
|
27
|
+
"@townco/core": "0.0.25",
|
|
28
|
+
"@townco/secret": "0.1.47",
|
|
29
|
+
"@townco/ui": "0.1.47",
|
|
29
30
|
"@types/inquirer": "^9.0.9",
|
|
30
31
|
"ink": "^6.4.0",
|
|
31
32
|
"ink-text-input": "^6.0.0",
|