@townco/cli 0.1.108 → 0.1.110
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.js +69 -10
- package/dist/components/LogsPane.js +44 -70
- package/dist/components/TabbedOutput.js +42 -8
- package/package.json +9 -9
package/dist/commands/run.js
CHANGED
|
@@ -144,7 +144,7 @@ async function loadEnvVars(projectRoot, logger) {
|
|
|
144
144
|
}
|
|
145
145
|
// CLI mode runner - outputs directly to stdout without React UI
|
|
146
146
|
async function runCliMode(options) {
|
|
147
|
-
const { binPath, workingDir, prompt, configEnvVars, noSession, waitForDebugger = false, } = options;
|
|
147
|
+
const { binPath, workingDir, prompt, configEnvVars, noSession, waitForDebugger = false, logger, } = options;
|
|
148
148
|
// Wait for debugger to be ready BEFORE starting agent
|
|
149
149
|
if (waitForDebugger) {
|
|
150
150
|
let debuggerReady = false;
|
|
@@ -174,7 +174,14 @@ async function runCliMode(options) {
|
|
|
174
174
|
// Ensure port is available
|
|
175
175
|
const { ensurePortAvailable } = await import("../lib/port-utils.js");
|
|
176
176
|
const agentPort = 3100;
|
|
177
|
-
|
|
177
|
+
try {
|
|
178
|
+
await ensurePortAvailable(agentPort, "agent HTTP server");
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
182
|
+
logger.error("Port check failed", { error: errorMessage });
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
178
185
|
// Spawn agent process with HTTP mode
|
|
179
186
|
const agentProcess = spawn("bun", [binPath, "http"], {
|
|
180
187
|
cwd: workingDir,
|
|
@@ -377,6 +384,10 @@ export async function runCommand(options) {
|
|
|
377
384
|
"to create a project.");
|
|
378
385
|
process.exit(1);
|
|
379
386
|
}
|
|
387
|
+
// Configure logs directory and create logger early so all errors can be logged
|
|
388
|
+
const logsDir = join(projectRoot, "agents", name, ".logs");
|
|
389
|
+
configureLogsDir(logsDir);
|
|
390
|
+
const logger = createLogger("cli", "debug");
|
|
380
391
|
// Load environment variables from project .env
|
|
381
392
|
const configEnvVars = await loadEnvVars(projectRoot);
|
|
382
393
|
// Resolve agent path to load agent definition
|
|
@@ -409,17 +420,42 @@ export async function runCommand(options) {
|
|
|
409
420
|
// Check debugger ports are available before starting
|
|
410
421
|
const debuggerUiPort = 4000;
|
|
411
422
|
const debuggerOtlpPort = 4318;
|
|
412
|
-
|
|
413
|
-
|
|
423
|
+
try {
|
|
424
|
+
await ensurePortAvailable(debuggerUiPort, "debugger UI");
|
|
425
|
+
await ensurePortAvailable(debuggerOtlpPort, "OTLP collector");
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
429
|
+
logger.error("Port check failed", { error: errorMessage });
|
|
430
|
+
throw error;
|
|
431
|
+
}
|
|
414
432
|
debuggerProcess = spawn("bun", ["src/index.ts"], {
|
|
415
433
|
cwd: debuggerDir,
|
|
416
|
-
stdio: cli ? "ignore" : "inherit", //
|
|
434
|
+
stdio: cli ? ["ignore", "ignore", "pipe"] : "inherit", // Pipe stderr in CLI mode to capture errors
|
|
417
435
|
env: {
|
|
418
436
|
...process.env,
|
|
419
437
|
DB_PATH: join(projectRoot, "agents", name, ".traces.db"),
|
|
420
438
|
AGENT_NAME: agentDisplayName,
|
|
421
439
|
},
|
|
422
440
|
});
|
|
441
|
+
// Log debugger stderr to capture any startup errors (e.g., port binding failures)
|
|
442
|
+
if (cli && debuggerProcess.stderr) {
|
|
443
|
+
debuggerProcess.stderr.on("data", (data) => {
|
|
444
|
+
const text = data.toString().trim();
|
|
445
|
+
if (text) {
|
|
446
|
+
logger.error("Debugger error", { stderr: text });
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
// Handle debugger process errors
|
|
451
|
+
debuggerProcess.on("error", (error) => {
|
|
452
|
+
logger.error("Debugger process error", { error: error.message });
|
|
453
|
+
});
|
|
454
|
+
debuggerProcess.on("exit", (code, signal) => {
|
|
455
|
+
if (code !== null && code !== 0) {
|
|
456
|
+
logger.error("Debugger process exited with error", { code, signal });
|
|
457
|
+
}
|
|
458
|
+
});
|
|
423
459
|
if (!cli) {
|
|
424
460
|
console.log(`Debugger UI: http://localhost:${debuggerUiPort}`);
|
|
425
461
|
}
|
|
@@ -454,8 +490,6 @@ export async function runCommand(options) {
|
|
|
454
490
|
process.exit(1);
|
|
455
491
|
}
|
|
456
492
|
const binPath = join(agentPath, "bin.ts");
|
|
457
|
-
// Create logger with agent directory as logs location
|
|
458
|
-
const logger = createLogger("cli", "debug");
|
|
459
493
|
logger.info("Starting agent", {
|
|
460
494
|
name,
|
|
461
495
|
mode: gui ? "gui" : http ? "http" : cli ? "cli" : "tui",
|
|
@@ -478,8 +512,15 @@ export async function runCommand(options) {
|
|
|
478
512
|
}
|
|
479
513
|
// Ensure agent and GUI ports are available (debugger ports already checked above)
|
|
480
514
|
const guiPort = 5173;
|
|
481
|
-
|
|
482
|
-
|
|
515
|
+
try {
|
|
516
|
+
await ensurePortAvailable(port, "agent HTTP server");
|
|
517
|
+
await ensurePortAvailable(guiPort, "GUI dev server");
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
521
|
+
logger.error("Port check failed", { error: errorMessage });
|
|
522
|
+
throw error;
|
|
523
|
+
}
|
|
483
524
|
logger.info("Starting GUI mode", {
|
|
484
525
|
agentPort: port,
|
|
485
526
|
guiPort,
|
|
@@ -545,6 +586,16 @@ export async function runCommand(options) {
|
|
|
545
586
|
catch (_e) {
|
|
546
587
|
// Process may already be dead
|
|
547
588
|
}
|
|
589
|
+
// Kill any remaining process on port 5173 (Vite server)
|
|
590
|
+
try {
|
|
591
|
+
const { spawnSync } = require("node:child_process");
|
|
592
|
+
spawnSync("sh", ["-c", "lsof -ti:5173 | xargs kill -9 2>/dev/null || true"], {
|
|
593
|
+
stdio: "ignore",
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
catch (_e) {
|
|
597
|
+
// Ignore errors
|
|
598
|
+
}
|
|
548
599
|
// Also cleanup debugger
|
|
549
600
|
cleanupDebugger?.();
|
|
550
601
|
};
|
|
@@ -572,7 +623,14 @@ export async function runCommand(options) {
|
|
|
572
623
|
}
|
|
573
624
|
else if (http) {
|
|
574
625
|
// Ensure agent port is available (debugger ports already checked above)
|
|
575
|
-
|
|
626
|
+
try {
|
|
627
|
+
await ensurePortAvailable(port, "agent HTTP server");
|
|
628
|
+
}
|
|
629
|
+
catch (error) {
|
|
630
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
631
|
+
logger.error("Port check failed", { error: errorMessage });
|
|
632
|
+
throw error;
|
|
633
|
+
}
|
|
576
634
|
logger.info("Starting HTTP mode", { port });
|
|
577
635
|
console.log(`Starting agent "${name}" in HTTP mode on port ${port}...`);
|
|
578
636
|
console.log(`\nEndpoints:`);
|
|
@@ -648,6 +706,7 @@ export async function runCommand(options) {
|
|
|
648
706
|
configEnvVars,
|
|
649
707
|
noSession,
|
|
650
708
|
waitForDebugger: true, // Wait for debugger to be ready before starting agent
|
|
709
|
+
logger,
|
|
651
710
|
});
|
|
652
711
|
// Agent has exited cleanly, now cleanup debugger
|
|
653
712
|
if (cleanupDebugger) {
|
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { LOG_FILE_NAME } from "@townco/core";
|
|
6
|
-
import { Box, Text, useInput } from "ink";
|
|
7
|
-
import TextInput from "ink-text-input";
|
|
6
|
+
import { Box, Static, Text, useInput } from "ink";
|
|
8
7
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
9
|
-
const LOG_LEVELS = [
|
|
10
|
-
"all",
|
|
11
|
-
"debug",
|
|
12
|
-
"info",
|
|
13
|
-
"warn",
|
|
14
|
-
"error",
|
|
15
|
-
"fatal",
|
|
16
|
-
];
|
|
17
|
-
// Max lines to keep in buffer
|
|
18
|
-
const MAX_LINES = 1000;
|
|
8
|
+
const LOG_LEVELS = ["all", "info", "warn", "error"];
|
|
19
9
|
function buildCommand(logsDir, pretty, level, sessionStartTime) {
|
|
20
10
|
const logFile = join(logsDir, LOG_FILE_NAME);
|
|
21
11
|
const tailCmd = `tail -n +1 -f "${logFile}"`;
|
|
@@ -23,13 +13,8 @@ function buildCommand(logsDir, pretty, level, sessionStartTime) {
|
|
|
23
13
|
const conditions = [];
|
|
24
14
|
conditions.push(`.timestamp >= "${sessionStartTime}"`);
|
|
25
15
|
if (level !== "all") {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const allowedLevels = levelOrder.slice(levelIndex);
|
|
29
|
-
const levelCondition = allowedLevels
|
|
30
|
-
.map((l) => `.level == "${l}"`)
|
|
31
|
-
.join(" or ");
|
|
32
|
-
conditions.push(`(${levelCondition})`);
|
|
16
|
+
// Filter to exact level only
|
|
17
|
+
conditions.push(`.level == "${level}"`);
|
|
33
18
|
}
|
|
34
19
|
const jqFilter = `select(${conditions.join(" and ")})`;
|
|
35
20
|
const jqFlags = pretty ? "-C --unbuffered" : "-c --unbuffered";
|
|
@@ -37,41 +22,36 @@ function buildCommand(logsDir, pretty, level, sessionStartTime) {
|
|
|
37
22
|
}
|
|
38
23
|
export function LogsPane({ logsDir: customLogsDir } = {}) {
|
|
39
24
|
const logsDir = useMemo(() => customLogsDir ?? join(process.cwd(), ".logs"), [customLogsDir]);
|
|
40
|
-
const sessionStartTime =
|
|
25
|
+
const [sessionStartTime, setSessionStartTime] = useState(() => new Date().toISOString());
|
|
41
26
|
const [pretty, setPretty] = useState(true);
|
|
42
27
|
const [level, setLevel] = useState("all");
|
|
43
|
-
const [editMode, setEditMode] = useState(false);
|
|
44
|
-
const [customCommand, setCustomCommand] = useState("");
|
|
45
28
|
const [outputLines, setOutputLines] = useState([]);
|
|
46
29
|
const [error, setError] = useState(null);
|
|
47
|
-
const [terminalHeight, setTerminalHeight] = useState(process.stdout.rows ?? 30);
|
|
48
30
|
const processRef = useRef(null);
|
|
49
31
|
const commandRunIdRef = useRef(0);
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
: buildCommand(logsDir, pretty, level, sessionStartTime), [editMode, customCommand, logsDir, pretty, level, sessionStartTime]);
|
|
53
|
-
// Track terminal resize
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
const handleResize = () => setTerminalHeight(process.stdout.rows ?? 30);
|
|
56
|
-
process.stdout.on("resize", handleResize);
|
|
57
|
-
return () => {
|
|
58
|
-
process.stdout.off("resize", handleResize);
|
|
59
|
-
};
|
|
60
|
-
}, []);
|
|
32
|
+
const lineIdRef = useRef(0);
|
|
33
|
+
const activeCommand = useMemo(() => buildCommand(logsDir, pretty, level, sessionStartTime), [logsDir, pretty, level, sessionStartTime]);
|
|
61
34
|
// Spawn tail process
|
|
62
35
|
useEffect(() => {
|
|
36
|
+
// Create logs directory if it doesn't exist
|
|
63
37
|
if (!existsSync(logsDir)) {
|
|
64
|
-
|
|
65
|
-
|
|
38
|
+
try {
|
|
39
|
+
mkdirSync(logsDir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
catch (_error) {
|
|
42
|
+
setError(`Failed to create logs directory: ${logsDir}`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
66
45
|
}
|
|
67
46
|
if (processRef.current) {
|
|
68
47
|
processRef.current.kill();
|
|
69
48
|
processRef.current = null;
|
|
70
49
|
}
|
|
71
|
-
// Clear output when command changes
|
|
50
|
+
// Clear output and reset line counter when command changes
|
|
72
51
|
setOutputLines([]);
|
|
52
|
+
lineIdRef.current = 0;
|
|
73
53
|
const runId = ++commandRunIdRef.current;
|
|
74
|
-
const
|
|
54
|
+
const commandTimeoutId = setTimeout(() => {
|
|
75
55
|
if (runId !== commandRunIdRef.current)
|
|
76
56
|
return;
|
|
77
57
|
const proc = spawn("sh", ["-c", activeCommand], {
|
|
@@ -83,14 +63,21 @@ export function LogsPane({ logsDir: customLogsDir } = {}) {
|
|
|
83
63
|
if (runId !== commandRunIdRef.current)
|
|
84
64
|
return;
|
|
85
65
|
const lines = data.toString().split("\n").filter(Boolean);
|
|
86
|
-
|
|
66
|
+
const newLines = lines.map((text) => ({
|
|
67
|
+
id: ++lineIdRef.current,
|
|
68
|
+
text,
|
|
69
|
+
}));
|
|
70
|
+
setOutputLines((prev) => [...prev, ...newLines]);
|
|
87
71
|
});
|
|
88
72
|
proc.stderr?.on("data", (data) => {
|
|
89
73
|
if (runId !== commandRunIdRef.current)
|
|
90
74
|
return;
|
|
91
75
|
const text = data.toString().trim();
|
|
92
76
|
if (text && !text.includes("parse error")) {
|
|
93
|
-
setOutputLines((prev) => [
|
|
77
|
+
setOutputLines((prev) => [
|
|
78
|
+
...prev,
|
|
79
|
+
{ id: ++lineIdRef.current, text: `[stderr] ${text}` },
|
|
80
|
+
]);
|
|
94
81
|
}
|
|
95
82
|
});
|
|
96
83
|
proc.on("error", (err) => {
|
|
@@ -100,7 +87,7 @@ export function LogsPane({ logsDir: customLogsDir } = {}) {
|
|
|
100
87
|
});
|
|
101
88
|
}, 50);
|
|
102
89
|
return () => {
|
|
103
|
-
clearTimeout(
|
|
90
|
+
clearTimeout(commandTimeoutId);
|
|
104
91
|
if (processRef.current) {
|
|
105
92
|
processRef.current.kill();
|
|
106
93
|
processRef.current = null;
|
|
@@ -110,27 +97,17 @@ export function LogsPane({ logsDir: customLogsDir } = {}) {
|
|
|
110
97
|
const handleInput = useCallback((input, key) => {
|
|
111
98
|
if (key.ctrl && input === "c")
|
|
112
99
|
return;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
if (key.return) {
|
|
119
|
-
setEditMode(false);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
if (input === "e") {
|
|
125
|
-
setCustomCommand(activeCommand);
|
|
126
|
-
setEditMode(true);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
100
|
+
// Helper to clear terminal before state changes
|
|
101
|
+
const clearTerminal = () => {
|
|
102
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
103
|
+
};
|
|
129
104
|
if (input === "f") {
|
|
105
|
+
clearTerminal();
|
|
130
106
|
setPretty((prev) => !prev);
|
|
131
107
|
return;
|
|
132
108
|
}
|
|
133
109
|
if (input === "l") {
|
|
110
|
+
clearTerminal();
|
|
134
111
|
setLevel((prev) => {
|
|
135
112
|
const currentIndex = LOG_LEVELS.indexOf(prev);
|
|
136
113
|
const nextIndex = (currentIndex + 1) % LOG_LEVELS.length;
|
|
@@ -139,27 +116,24 @@ export function LogsPane({ logsDir: customLogsDir } = {}) {
|
|
|
139
116
|
return;
|
|
140
117
|
}
|
|
141
118
|
if (input === "c" && !key.ctrl) {
|
|
142
|
-
|
|
119
|
+
// Clear terminal and update timestamp to only show new logs
|
|
120
|
+
clearTerminal();
|
|
121
|
+
setSessionStartTime(new Date().toISOString());
|
|
143
122
|
return;
|
|
144
123
|
}
|
|
145
|
-
}, [
|
|
124
|
+
}, []);
|
|
146
125
|
useInput(handleInput);
|
|
147
126
|
if (error) {
|
|
148
127
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(Text, { color: "red", children: error }) }));
|
|
149
128
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const displayLines = outputLines.slice(-visibleLines);
|
|
154
|
-
return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [
|
|
155
|
-
_jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: displayLines.length === 0 ? (_jsx(Text, { dimColor: true, children: "Waiting for logs..." })) : (displayLines.map((line, idx) => (_jsx(Text, { children: line }, `${idx}-${line.slice(0, 20)}`)))) }), _jsxs(Box, { borderStyle: "single", borderTop: true, borderColor: "gray", paddingX: 1, flexDirection: "column", flexShrink: 0, children: [editMode ? (_jsxs(Box, { children: [
|
|
156
|
-
_jsx(Text, { color: "cyan", children: "$ " }), _jsx(TextInput, { value: customCommand, onChange: setCustomCommand, placeholder: "Enter command..." }), _jsx(Text, { dimColor: true, children: " (Enter to apply, Esc to cancel)" })
|
|
157
|
-
] })) : (_jsxs(Box, { children: [
|
|
129
|
+
return (_jsxs(Box, { flexDirection: "column", children: [
|
|
130
|
+
_jsx(Static, { items: outputLines, children: (line) => _jsx(Text, { children: line.text }, line.id) }), outputLines.length === 0 && _jsx(Text, { dimColor: true, children: "Waiting for logs..." }), _jsxs(Box, { borderStyle: "single", borderTop: true, borderColor: "gray", paddingX: 1, flexDirection: "column", flexShrink: 0, children: [
|
|
131
|
+
_jsxs(Box, { children: [
|
|
158
132
|
_jsx(Text, { dimColor: true, children: "$ " }), _jsx(Text, { children: activeCommand })
|
|
159
|
-
] })
|
|
133
|
+
] }), _jsxs(Box, { justifyContent: "space-between", children: [
|
|
160
134
|
_jsxs(Box, { children: [
|
|
161
135
|
_jsxs(Text, { ...(pretty && { color: "green" }), children: ["[", pretty ? "formatted" : "raw", "]"] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: "cyan", children: ["[level: ", level, "]"] }), _jsx(Text, { children: " " }), _jsxs(Text, { dimColor: true, children: [outputLines.length, " lines"] })
|
|
162
|
-
] }), _jsx(Text, { dimColor: true, children: "(
|
|
136
|
+
] }), _jsx(Text, { dimColor: true, children: "(f)ormat | (l)evel | (c)lear" })
|
|
163
137
|
] })
|
|
164
138
|
] })
|
|
165
139
|
] }));
|
|
@@ -74,14 +74,31 @@ export function TabbedOutput({ processes, customTabs = [], logsDir, onExit, onPo
|
|
|
74
74
|
if (logsDir) {
|
|
75
75
|
const logFile = join(logsDir, LOG_FILE_NAME);
|
|
76
76
|
try {
|
|
77
|
+
const trimmedOutput = output.trim();
|
|
78
|
+
// Check if stdout output is already a valid JSON log entry
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(trimmedOutput);
|
|
81
|
+
if (parsed &&
|
|
82
|
+
typeof parsed === "object" &&
|
|
83
|
+
"timestamp" in parsed &&
|
|
84
|
+
"level" in parsed &&
|
|
85
|
+
"message" in parsed) {
|
|
86
|
+
// Already a valid log entry, write as-is
|
|
87
|
+
appendFileSync(logFile, `${trimmedOutput}\n`, "utf-8");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Not valid JSON, continue to wrap it
|
|
93
|
+
}
|
|
94
|
+
// Wrap non-JSON stdout output as a log entry
|
|
77
95
|
const timestamp = new Date().toISOString();
|
|
78
|
-
|
|
96
|
+
appendFileSync(logFile, `${JSON.stringify({
|
|
79
97
|
timestamp,
|
|
80
98
|
level: "info",
|
|
81
99
|
service: processInfo.name.toLowerCase(),
|
|
82
|
-
message:
|
|
83
|
-
});
|
|
84
|
-
appendFileSync(logFile, `${logEntry}\n`, "utf-8");
|
|
100
|
+
message: trimmedOutput,
|
|
101
|
+
})}\n`, "utf-8");
|
|
85
102
|
}
|
|
86
103
|
catch (error) {
|
|
87
104
|
console.error("Failed to write to log file:", error);
|
|
@@ -118,14 +135,31 @@ export function TabbedOutput({ processes, customTabs = [], logsDir, onExit, onPo
|
|
|
118
135
|
if (logsDir) {
|
|
119
136
|
const logFile = join(logsDir, LOG_FILE_NAME);
|
|
120
137
|
try {
|
|
138
|
+
const trimmedText = text.trim();
|
|
139
|
+
// Check if stderr output is already a valid JSON log entry
|
|
140
|
+
try {
|
|
141
|
+
const parsed = JSON.parse(trimmedText);
|
|
142
|
+
if (parsed &&
|
|
143
|
+
typeof parsed === "object" &&
|
|
144
|
+
"timestamp" in parsed &&
|
|
145
|
+
"level" in parsed &&
|
|
146
|
+
"message" in parsed) {
|
|
147
|
+
// Already a valid log entry, write as-is
|
|
148
|
+
appendFileSync(logFile, `${trimmedText}\n`, "utf-8");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Not valid JSON, continue to wrap it
|
|
154
|
+
}
|
|
155
|
+
// Wrap non-JSON stderr output as a log entry
|
|
121
156
|
const timestamp = new Date().toISOString();
|
|
122
|
-
|
|
157
|
+
appendFileSync(logFile, `${JSON.stringify({
|
|
123
158
|
timestamp,
|
|
124
159
|
level: "error",
|
|
125
160
|
service: processInfo.name.toLowerCase(),
|
|
126
|
-
message:
|
|
127
|
-
});
|
|
128
|
-
appendFileSync(logFile, `${logEntry}\n`, "utf-8");
|
|
161
|
+
message: trimmedText,
|
|
162
|
+
})}\n`, "utf-8");
|
|
129
163
|
}
|
|
130
164
|
catch (error) {
|
|
131
165
|
console.error("Failed to write to log file:", error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@townco/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.110",
|
|
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.
|
|
18
|
+
"@townco/tsconfig": "0.1.102",
|
|
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.
|
|
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.
|
|
27
|
+
"@townco/agent": "0.1.113",
|
|
28
|
+
"@townco/apiclient": "0.0.25",
|
|
29
|
+
"@townco/core": "0.0.83",
|
|
30
|
+
"@townco/debugger": "0.1.61",
|
|
31
|
+
"@townco/env": "0.1.55",
|
|
32
|
+
"@townco/secret": "0.1.105",
|
|
33
|
+
"@townco/ui": "0.1.105",
|
|
34
34
|
"@trpc/client": "^11.7.2",
|
|
35
35
|
"archiver": "^7.0.1",
|
|
36
36
|
"eventsource": "^4.1.0",
|