@townco/cli 0.1.104 → 0.1.105
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
|
@@ -48,14 +48,9 @@ const AVAILABLE_TOOLS = [
|
|
|
48
48
|
description: "Exa-powered web search (requires EXA_API_KEY)",
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
|
-
label: "
|
|
52
|
-
value: "
|
|
53
|
-
description: "
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
label: "Generate Image",
|
|
57
|
-
value: "generate_image",
|
|
58
|
-
description: "Generate images using Google's Gemini model (requires GEMINI_API_KEY)",
|
|
51
|
+
label: "Code Sandbox",
|
|
52
|
+
value: "code_sandbox",
|
|
53
|
+
description: "Execute code, run commands, generate images, and manage files in a secure cloud sandbox",
|
|
59
54
|
},
|
|
60
55
|
{
|
|
61
56
|
label: "Browser",
|
package/dist/commands/run.js
CHANGED
|
@@ -21,13 +21,6 @@ function TuiRunner({ agentPath, workingDir, agentName, noSession, onExit, }) {
|
|
|
21
21
|
useMemo(() => {
|
|
22
22
|
configureLogsDir(join(workingDir, ".logs"));
|
|
23
23
|
}, [workingDir]);
|
|
24
|
-
// Session start time for log filtering - set once when component mounts
|
|
25
|
-
// Set to 5 seconds in the past to capture logs generated during initialization
|
|
26
|
-
const sessionStartTime = useMemo(() => {
|
|
27
|
-
const now = new Date();
|
|
28
|
-
now.setSeconds(now.getSeconds() - 5);
|
|
29
|
-
return now.toISOString();
|
|
30
|
-
}, []);
|
|
31
24
|
// Create logger with agent directory as logs location
|
|
32
25
|
const logger = useMemo(() => createLogger("tui", "debug"), []);
|
|
33
26
|
useEffect(() => {
|
|
@@ -84,9 +77,9 @@ function TuiRunner({ agentPath, workingDir, agentName, noSession, onExit, }) {
|
|
|
84
77
|
{
|
|
85
78
|
name: "Logs",
|
|
86
79
|
type: "custom",
|
|
87
|
-
render: () =>
|
|
80
|
+
render: () => _jsx(LogsPane, { logsDir: join(workingDir, ".logs") }),
|
|
88
81
|
},
|
|
89
|
-
], [client, error, workingDir
|
|
82
|
+
], [client, error, workingDir]);
|
|
90
83
|
return (_jsx(TabbedOutput, { processes: [], customTabs: customTabs, onExit: onExit }));
|
|
91
84
|
}
|
|
92
85
|
function GuiRunner({ agentProcess, guiProcess, agentPort, agentPath, logger, onExit, }) {
|
|
@@ -390,7 +383,7 @@ export async function runCommand(options) {
|
|
|
390
383
|
const agentPath = join(projectRoot, "agents", name);
|
|
391
384
|
// Load agent definition to get displayName and detect MCPs by parsing the file
|
|
392
385
|
let agentDisplayName = name; // Fallback to agent directory name
|
|
393
|
-
let
|
|
386
|
+
let usesLibraryMcp = false;
|
|
394
387
|
try {
|
|
395
388
|
const agentIndexPath = join(agentPath, "index.ts");
|
|
396
389
|
const content = await readFile(agentIndexPath, "utf-8");
|
|
@@ -400,9 +393,9 @@ export async function runCommand(options) {
|
|
|
400
393
|
if (displayNameMatch?.[1]) {
|
|
401
394
|
agentDisplayName = displayNameMatch[1];
|
|
402
395
|
}
|
|
403
|
-
// Detect if agent uses
|
|
404
|
-
// Looking for patterns like: name: "
|
|
405
|
-
|
|
396
|
+
// Detect if agent uses library MCP
|
|
397
|
+
// Looking for patterns like: name: "library" or name: 'library'
|
|
398
|
+
usesLibraryMcp = /name:\s*["']library["']/.test(content);
|
|
406
399
|
}
|
|
407
400
|
catch (_error) {
|
|
408
401
|
// If we can't read the agent definition, just use the directory name
|
|
@@ -525,11 +518,11 @@ export async function runCommand(options) {
|
|
|
525
518
|
...configEnvVars,
|
|
526
519
|
VITE_AGENT_URL: `${process.env.EXT_HOST || "http://localhost"}:${port}`,
|
|
527
520
|
VITE_DEBUGGER_URL: `http://localhost:${debuggerUiPort}`,
|
|
528
|
-
// If agent uses
|
|
529
|
-
...(
|
|
530
|
-
process.env.
|
|
531
|
-
|
|
532
|
-
|
|
521
|
+
// If agent uses library MCP, pass LIBRARY_API_URL to GUI for auth
|
|
522
|
+
...(usesLibraryMcp &&
|
|
523
|
+
process.env.LIBRARY_API_URL && {
|
|
524
|
+
VITE_LIBRARY_API_URL: process.env.LIBRARY_API_URL,
|
|
525
|
+
VITE_LIBRARY_FRONTEND_URL: process.env.LIBRARY_FRONTEND_URL,
|
|
533
526
|
}),
|
|
534
527
|
},
|
|
535
528
|
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export interface LogsPaneProps {
|
|
2
2
|
logsDir?: string;
|
|
3
|
-
sessionStartTime?: string;
|
|
4
3
|
}
|
|
5
|
-
export declare function LogsPane({ logsDir: customLogsDir
|
|
4
|
+
export declare function LogsPane({ logsDir: customLogsDir }?: LogsPaneProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,262 +1,166 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { existsSync
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { LOG_FILE_NAME } from "@townco/core";
|
|
5
6
|
import { Box, Text, useInput } from "ink";
|
|
6
7
|
import TextInput from "ink-text-input";
|
|
7
|
-
import { useEffect, useMemo, useState } from "react";
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
debug
|
|
11
|
-
info
|
|
12
|
-
warn
|
|
13
|
-
error
|
|
14
|
-
fatal
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
8
|
+
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;
|
|
19
|
+
function buildCommand(logsDir, pretty, level, sessionStartTime) {
|
|
20
|
+
const logFile = join(logsDir, LOG_FILE_NAME);
|
|
21
|
+
const tailCmd = `tail -n +1 -f "${logFile}"`;
|
|
22
|
+
// Build jq filter conditions
|
|
23
|
+
const conditions = [];
|
|
24
|
+
conditions.push(`.timestamp >= "${sessionStartTime}"`);
|
|
25
|
+
if (level !== "all") {
|
|
26
|
+
const levelOrder = ["debug", "info", "warn", "error", "fatal"];
|
|
27
|
+
const levelIndex = levelOrder.indexOf(level);
|
|
28
|
+
const allowedLevels = levelOrder.slice(levelIndex);
|
|
29
|
+
const levelCondition = allowedLevels
|
|
30
|
+
.map((l) => `.level == "${l}"`)
|
|
31
|
+
.join(" or ");
|
|
32
|
+
conditions.push(`(${levelCondition})`);
|
|
28
33
|
}
|
|
29
|
-
|
|
34
|
+
const jqFilter = `select(${conditions.join(" and ")})`;
|
|
35
|
+
const jqFlags = pretty ? "-C --unbuffered" : "-c --unbuffered";
|
|
36
|
+
return `${tailCmd} | jq ${jqFlags} '${jqFilter}'`;
|
|
30
37
|
}
|
|
31
|
-
export function LogsPane({ logsDir: customLogsDir
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const [
|
|
35
|
-
const [
|
|
36
|
-
const [
|
|
37
|
-
const [
|
|
38
|
-
const [
|
|
39
|
-
const [
|
|
38
|
+
export function LogsPane({ logsDir: customLogsDir } = {}) {
|
|
39
|
+
const logsDir = useMemo(() => customLogsDir ?? join(process.cwd(), ".logs"), [customLogsDir]);
|
|
40
|
+
const sessionStartTime = useMemo(() => new Date().toISOString(), []);
|
|
41
|
+
const [pretty, setPretty] = useState(true);
|
|
42
|
+
const [level, setLevel] = useState("all");
|
|
43
|
+
const [editMode, setEditMode] = useState(false);
|
|
44
|
+
const [customCommand, setCustomCommand] = useState("");
|
|
45
|
+
const [outputLines, setOutputLines] = useState([]);
|
|
46
|
+
const [error, setError] = useState(null);
|
|
40
47
|
const [terminalHeight, setTerminalHeight] = useState(process.stdout.rows ?? 30);
|
|
48
|
+
const processRef = useRef(null);
|
|
49
|
+
const commandRunIdRef = useRef(0);
|
|
50
|
+
const activeCommand = useMemo(() => editMode
|
|
51
|
+
? customCommand
|
|
52
|
+
: buildCommand(logsDir, pretty, level, sessionStartTime), [editMode, customCommand, logsDir, pretty, level, sessionStartTime]);
|
|
41
53
|
// Track terminal resize
|
|
42
54
|
useEffect(() => {
|
|
43
|
-
const handleResize = () =>
|
|
44
|
-
setTerminalHeight(process.stdout.rows ?? 30);
|
|
45
|
-
};
|
|
55
|
+
const handleResize = () => setTerminalHeight(process.stdout.rows ?? 30);
|
|
46
56
|
process.stdout.on("resize", handleResize);
|
|
47
57
|
return () => {
|
|
48
58
|
process.stdout.off("resize", handleResize);
|
|
49
59
|
};
|
|
50
60
|
}, []);
|
|
51
|
-
//
|
|
52
|
-
const sessionStartTime = useMemo(() => {
|
|
53
|
-
if (providedSessionStartTime)
|
|
54
|
-
return providedSessionStartTime;
|
|
55
|
-
const now = new Date();
|
|
56
|
-
now.setSeconds(now.getSeconds() - 1);
|
|
57
|
-
return now.toISOString();
|
|
58
|
-
}, [providedSessionStartTime]);
|
|
59
|
-
const logsDir = customLogsDir ?? join(process.cwd(), ".logs");
|
|
60
|
-
// Update available services based on logs from current session
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
const servicesWithLogs = new Set(logLines.map((log) => log.service));
|
|
63
|
-
const newServices = Array.from(servicesWithLogs);
|
|
64
|
-
setAvailableServices(newServices);
|
|
65
|
-
// Clear service filter if the selected service is no longer available
|
|
66
|
-
if (serviceFilter && !newServices.includes(serviceFilter)) {
|
|
67
|
-
setServiceFilter(null);
|
|
68
|
-
}
|
|
69
|
-
}, [logLines, serviceFilter]);
|
|
70
|
-
// Tail all log files and merge output
|
|
61
|
+
// Spawn tail process
|
|
71
62
|
useEffect(() => {
|
|
72
63
|
if (!existsSync(logsDir)) {
|
|
73
|
-
|
|
74
|
-
{
|
|
75
|
-
timestamp: new Date().toISOString(),
|
|
76
|
-
level: "info",
|
|
77
|
-
service: "logs",
|
|
78
|
-
message: `No logs directory found at ${logsDir}`,
|
|
79
|
-
},
|
|
80
|
-
]);
|
|
64
|
+
setError(`Logs directory not found: ${logsDir}`);
|
|
81
65
|
return;
|
|
82
66
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
{
|
|
87
|
-
timestamp: new Date().toISOString(),
|
|
88
|
-
level: "info",
|
|
89
|
-
service: "logs",
|
|
90
|
-
message: "No log files found. Logs will appear here once services start logging.",
|
|
91
|
-
},
|
|
92
|
-
]);
|
|
93
|
-
return;
|
|
67
|
+
if (processRef.current) {
|
|
68
|
+
processRef.current.kill();
|
|
69
|
+
processRef.current = null;
|
|
94
70
|
}
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const entries = lines
|
|
105
|
-
.map((line) => {
|
|
106
|
-
try {
|
|
107
|
-
const parsed = JSON.parse(line);
|
|
108
|
-
return parsed;
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
// If not JSON, create a simple entry
|
|
112
|
-
return {
|
|
113
|
-
timestamp: new Date().toISOString(),
|
|
114
|
-
level: "info",
|
|
115
|
-
service: file.replace(".log", ""),
|
|
116
|
-
message: line,
|
|
117
|
-
_raw: line,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
.filter((entry) => entry.timestamp >= sessionStartTime);
|
|
122
|
-
setLogLines((prev) => {
|
|
123
|
-
const combined = [...prev, ...entries];
|
|
124
|
-
// Sort by timestamp and keep last 200 entries
|
|
125
|
-
const sorted = combined.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
126
|
-
return sorted.slice(-200);
|
|
127
|
-
});
|
|
71
|
+
// Clear output when command changes
|
|
72
|
+
setOutputLines([]);
|
|
73
|
+
const runId = ++commandRunIdRef.current;
|
|
74
|
+
const timeoutId = setTimeout(() => {
|
|
75
|
+
if (runId !== commandRunIdRef.current)
|
|
76
|
+
return;
|
|
77
|
+
const proc = spawn("sh", ["-c", activeCommand], {
|
|
78
|
+
cwd: logsDir,
|
|
79
|
+
env: { ...process.env, TERM: "xterm-256color" },
|
|
128
80
|
});
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
81
|
+
processRef.current = proc;
|
|
82
|
+
proc.stdout?.on("data", (data) => {
|
|
83
|
+
if (runId !== commandRunIdRef.current)
|
|
84
|
+
return;
|
|
85
|
+
const lines = data.toString().split("\n").filter(Boolean);
|
|
86
|
+
setOutputLines((prev) => [...prev, ...lines].slice(-MAX_LINES));
|
|
87
|
+
});
|
|
88
|
+
proc.stderr?.on("data", (data) => {
|
|
89
|
+
if (runId !== commandRunIdRef.current)
|
|
90
|
+
return;
|
|
91
|
+
const text = data.toString().trim();
|
|
92
|
+
if (text && !text.includes("parse error")) {
|
|
93
|
+
setOutputLines((prev) => [...prev, `[stderr] ${text}`].slice(-MAX_LINES));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
proc.on("error", (err) => {
|
|
97
|
+
if (runId !== commandRunIdRef.current)
|
|
98
|
+
return;
|
|
99
|
+
setError(`Failed to start: ${err.message}`);
|
|
100
|
+
});
|
|
101
|
+
}, 50);
|
|
132
102
|
return () => {
|
|
133
|
-
|
|
134
|
-
|
|
103
|
+
clearTimeout(timeoutId);
|
|
104
|
+
if (processRef.current) {
|
|
105
|
+
processRef.current.kill();
|
|
106
|
+
processRef.current = null;
|
|
135
107
|
}
|
|
136
108
|
};
|
|
137
|
-
}, [
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (serviceFilter && log.service !== serviceFilter) {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
if (levelFilter) {
|
|
144
|
-
const levelOrder = [
|
|
145
|
-
"trace",
|
|
146
|
-
"debug",
|
|
147
|
-
"info",
|
|
148
|
-
"warn",
|
|
149
|
-
"error",
|
|
150
|
-
"fatal",
|
|
151
|
-
];
|
|
152
|
-
const logLevelIndex = levelOrder.indexOf(log.level);
|
|
153
|
-
const filterLevelIndex = levelOrder.indexOf(levelFilter);
|
|
154
|
-
if (logLevelIndex < filterLevelIndex) {
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return true;
|
|
159
|
-
});
|
|
160
|
-
// Handle keyboard input for filtering and scrolling
|
|
161
|
-
useInput((input, key) => {
|
|
162
|
-
// '/' to enter search mode
|
|
163
|
-
if (input === "/" && !searchMode) {
|
|
164
|
-
setSearchMode(true);
|
|
109
|
+
}, [activeCommand, logsDir]);
|
|
110
|
+
const handleInput = useCallback((input, key) => {
|
|
111
|
+
if (key.ctrl && input === "c")
|
|
165
112
|
return;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
setSearchMode(false);
|
|
171
|
-
setSearchQuery("");
|
|
113
|
+
if (editMode) {
|
|
114
|
+
if (key.escape) {
|
|
115
|
+
setEditMode(false);
|
|
116
|
+
return;
|
|
172
117
|
}
|
|
173
|
-
|
|
174
|
-
|
|
118
|
+
if (key.return) {
|
|
119
|
+
setEditMode(false);
|
|
120
|
+
return;
|
|
175
121
|
}
|
|
176
122
|
return;
|
|
177
123
|
}
|
|
178
|
-
|
|
179
|
-
|
|
124
|
+
if (input === "e") {
|
|
125
|
+
setCustomCommand(activeCommand);
|
|
126
|
+
setEditMode(true);
|
|
180
127
|
return;
|
|
181
128
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
setServiceFilter(availableServices[0] ?? null);
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
const currentIndex = availableServices.indexOf(serviceFilter);
|
|
189
|
-
const nextIndex = (currentIndex + 1) % (availableServices.length + 1);
|
|
190
|
-
setServiceFilter(nextIndex === availableServices.length
|
|
191
|
-
? null
|
|
192
|
-
: (availableServices[nextIndex] ?? null));
|
|
193
|
-
}
|
|
129
|
+
if (input === "f") {
|
|
130
|
+
setPretty((prev) => !prev);
|
|
131
|
+
return;
|
|
194
132
|
}
|
|
195
|
-
// 'l' to cycle through level filters
|
|
196
133
|
if (input === "l") {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
"
|
|
201
|
-
|
|
202
|
-
"error",
|
|
203
|
-
"fatal",
|
|
204
|
-
];
|
|
205
|
-
const currentIndex = levelFilter === null ? 0 : levels.indexOf(levelFilter);
|
|
206
|
-
const nextIndex = (currentIndex + 1) % levels.length;
|
|
207
|
-
const nextLevel = levels[nextIndex];
|
|
208
|
-
setLevelFilter(nextLevel ?? null);
|
|
209
|
-
}
|
|
210
|
-
// 'c' to clear all filters, logs, and reset scroll
|
|
211
|
-
if (input === "c") {
|
|
212
|
-
setServiceFilter(null);
|
|
213
|
-
setLevelFilter(null);
|
|
214
|
-
setLogLines([]); // Clear the log buffer
|
|
215
|
-
setScrollOffset(0); // Also jump to bottom when clearing
|
|
134
|
+
setLevel((prev) => {
|
|
135
|
+
const currentIndex = LOG_LEVELS.indexOf(prev);
|
|
136
|
+
const nextIndex = (currentIndex + 1) % LOG_LEVELS.length;
|
|
137
|
+
return LOG_LEVELS[nextIndex] ?? "all";
|
|
138
|
+
});
|
|
216
139
|
return;
|
|
217
140
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
setExpandedLogs((prev) => !prev);
|
|
141
|
+
if (input === "c" && !key.ctrl) {
|
|
142
|
+
setOutputLines([]);
|
|
221
143
|
return;
|
|
222
144
|
}
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// Calculate
|
|
229
|
-
// Reserve
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
// Calculate which slice of logs to show
|
|
239
|
-
const displayLogs = isAtBottom
|
|
240
|
-
? searchedLogs.slice(-maxLines) // Show last N lines when at bottom
|
|
241
|
-
: searchedLogs.slice(Math.max(0, totalLogs - maxLines - scrollOffset), totalLogs - scrollOffset);
|
|
242
|
-
const _canScrollUp = totalLogs > maxLines && scrollOffset < totalLogs - maxLines;
|
|
243
|
-
const _canScrollDown = scrollOffset > 0;
|
|
244
|
-
return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [searchMode && (_jsxs(Box, { borderStyle: "single", borderBottom: true, borderColor: "cyan", paddingX: 1, children: [
|
|
245
|
-
_jsx(Text, { color: "cyan", children: "Search: " }), _jsx(TextInput, { value: searchQuery, onChange: setSearchQuery, placeholder: "Type to search..." }), _jsx(Text, { dimColor: true, children: " (ESC to exit)" })
|
|
246
|
-
] })), _jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: displayLogs.length === 0 ? (_jsx(Text, { dimColor: true, children: "No logs match the current filters. Press 'c' to clear filters." })) : (displayLogs.map((log, idx) => {
|
|
247
|
-
const serviceColor = SERVICE_COLORS[availableServices.indexOf(log.service) % SERVICE_COLORS.length] || "white";
|
|
248
|
-
const levelColor = LOG_COLORS[log.level] || "white";
|
|
249
|
-
const time = new Date(log.timestamp).toLocaleTimeString();
|
|
250
|
-
const keyStr = `${log.timestamp}-${idx}`;
|
|
251
|
-
return (_jsxs(Box, { flexDirection: "column", children: [
|
|
145
|
+
}, [editMode, activeCommand]);
|
|
146
|
+
useInput(handleInput);
|
|
147
|
+
if (error) {
|
|
148
|
+
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(Text, { color: "red", children: error }) }));
|
|
149
|
+
}
|
|
150
|
+
// Calculate visible lines based on terminal height
|
|
151
|
+
// Reserve: footer (4 lines) + status bar (3 lines) + padding
|
|
152
|
+
const visibleLines = Math.max(5, terminalHeight - 10);
|
|
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: [
|
|
158
|
+
_jsx(Text, { dimColor: true, children: "$ " }), _jsx(Text, { children: activeCommand })
|
|
159
|
+
] })), _jsxs(Box, { justifyContent: "space-between", children: [
|
|
252
160
|
_jsxs(Box, { children: [
|
|
253
|
-
_jsxs(Text, {
|
|
254
|
-
] }),
|
|
255
|
-
|
|
256
|
-
Object.keys(log.metadata).length > 0 && (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, children: JSON.stringify(log.metadata, null, 2) }) }))] }, keyStr));
|
|
257
|
-
})) }), _jsxs(Box, { borderStyle: "single", borderTop: true, borderColor: "gray", paddingX: 1, justifyContent: "space-between", children: [
|
|
258
|
-
_jsxs(Box, { children: [serviceFilter && _jsxs(Text, { children: ["[", serviceFilter, "] "] }), levelFilter && (_jsxs(Text, { children: ["[", ">=", levelFilter, "]", " "] })), searchQuery && _jsxs(Text, { color: "cyan", children: ["[SEARCH: ", searchQuery, "] "] }), !isAtBottom && _jsx(Text, { color: "yellow", children: "[SCROLLED] " }), expandedLogs && _jsx(Text, { color: "green", children: "[EXPANDED] " }), _jsxs(Text, { dimColor: true, children: [displayLogs.length, "/", totalLogs, " logs", scrollOffset > 0 && ` (${scrollOffset} from bottom)`] })
|
|
259
|
-
] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "(/)search | (s)ervice | (l)evel | (e)xpand | (c)lear | ESC" }) })
|
|
161
|
+
_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: "(e)dit | (f)ormat | (l)evel | (c)lear" })
|
|
163
|
+
] })
|
|
260
164
|
] })
|
|
261
165
|
] }));
|
|
262
166
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { appendFileSync, existsSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { LOG_FILE_NAME } from "@townco/core";
|
|
4
5
|
import { Box, Text, useApp, useInput } from "ink";
|
|
5
6
|
import { useEffect, useRef, useState } from "react";
|
|
6
7
|
import { LogsPane } from "./LogsPane.js";
|
|
@@ -71,7 +72,7 @@ export function TabbedOutput({ processes, customTabs = [], logsDir, onExit, onPo
|
|
|
71
72
|
markAsRunning();
|
|
72
73
|
// Write to log file if logsDir is provided
|
|
73
74
|
if (logsDir) {
|
|
74
|
-
const logFile = join(logsDir,
|
|
75
|
+
const logFile = join(logsDir, LOG_FILE_NAME);
|
|
75
76
|
try {
|
|
76
77
|
const timestamp = new Date().toISOString();
|
|
77
78
|
const logEntry = JSON.stringify({
|
|
@@ -115,7 +116,7 @@ export function TabbedOutput({ processes, customTabs = [], logsDir, onExit, onPo
|
|
|
115
116
|
markAsRunning();
|
|
116
117
|
// Write to log file if logsDir is provided
|
|
117
118
|
if (logsDir) {
|
|
118
|
-
const logFile = join(logsDir,
|
|
119
|
+
const logFile = join(logsDir, LOG_FILE_NAME);
|
|
119
120
|
try {
|
|
120
121
|
const timestamp = new Date().toISOString();
|
|
121
122
|
const logEntry = JSON.stringify({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@townco/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.105",
|
|
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.97",
|
|
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.108",
|
|
28
|
+
"@townco/apiclient": "0.0.20",
|
|
29
|
+
"@townco/core": "0.0.78",
|
|
30
|
+
"@townco/debugger": "0.1.56",
|
|
31
|
+
"@townco/env": "0.1.50",
|
|
32
|
+
"@townco/secret": "0.1.100",
|
|
33
|
+
"@townco/ui": "0.1.100",
|
|
34
34
|
"@trpc/client": "^11.7.2",
|
|
35
35
|
"archiver": "^7.0.1",
|
|
36
36
|
"eventsource": "^4.1.0",
|