@townco/cli 0.1.50 → 0.1.51
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
|
@@ -6,11 +6,21 @@ import TextInput from "ink-text-input";
|
|
|
6
6
|
import { useEffect, useState } from "react";
|
|
7
7
|
import { openInEditor } from "../lib/editor-utils";
|
|
8
8
|
const AVAILABLE_MODELS = [
|
|
9
|
+
{
|
|
10
|
+
label: "Claude Opus 4.5",
|
|
11
|
+
value: "claude-opus-4-5-20250514",
|
|
12
|
+
description: "Most capable model - best for complex tasks",
|
|
13
|
+
},
|
|
9
14
|
{
|
|
10
15
|
label: "Claude Sonnet 4.5",
|
|
11
16
|
value: "claude-sonnet-4-5-20250929",
|
|
12
17
|
description: "Latest Sonnet model - balanced performance and speed",
|
|
13
18
|
},
|
|
19
|
+
{
|
|
20
|
+
label: "Claude Haiku 4.5",
|
|
21
|
+
value: "claude-haiku-4-5-20250919",
|
|
22
|
+
description: "Fastest Claude 4 model - great for simple tasks",
|
|
23
|
+
},
|
|
14
24
|
{
|
|
15
25
|
label: "Claude Sonnet 4",
|
|
16
26
|
value: "claude-sonnet-4-20250514",
|
|
@@ -19,7 +29,7 @@ const AVAILABLE_MODELS = [
|
|
|
19
29
|
{
|
|
20
30
|
label: "Claude Opus 4",
|
|
21
31
|
value: "claude-opus-4-20250514",
|
|
22
|
-
description: "
|
|
32
|
+
description: "Previous Opus version",
|
|
23
33
|
},
|
|
24
34
|
{
|
|
25
35
|
label: "Claude 3.5 Sonnet",
|
|
@@ -29,7 +39,7 @@ const AVAILABLE_MODELS = [
|
|
|
29
39
|
{
|
|
30
40
|
label: "Claude 3.5 Haiku",
|
|
31
41
|
value: "claude-3-5-haiku-20241022",
|
|
32
|
-
description: "
|
|
42
|
+
description: "Claude 3.5 generation Haiku",
|
|
33
43
|
},
|
|
34
44
|
];
|
|
35
45
|
const AVAILABLE_TOOLS = [
|
|
@@ -98,10 +108,18 @@ function CreateApp({ name: initialName, model: initialModel, tools: initialTools
|
|
|
98
108
|
systemPrompt: agentDef.systemPrompt || "You are a helpful assistant.",
|
|
99
109
|
tools: agentDef.tools || [],
|
|
100
110
|
hooks: [
|
|
111
|
+
{
|
|
112
|
+
type: "tool_response",
|
|
113
|
+
setting: {
|
|
114
|
+
maxContextThreshold: 80,
|
|
115
|
+
responseTruncationThreshold: 80,
|
|
116
|
+
},
|
|
117
|
+
callback: "tool_response_compactor",
|
|
118
|
+
},
|
|
101
119
|
{
|
|
102
120
|
type: "context_size",
|
|
103
121
|
setting: {
|
|
104
|
-
threshold:
|
|
122
|
+
threshold: 80,
|
|
105
123
|
},
|
|
106
124
|
callback: "compaction_tool",
|
|
107
125
|
},
|
|
@@ -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.51",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"town": "./dist/index.js"
|
|
@@ -15,17 +15,17 @@
|
|
|
15
15
|
"build": "tsc"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@townco/tsconfig": "0.1.
|
|
18
|
+
"@townco/tsconfig": "0.1.43",
|
|
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/core": "0.0.
|
|
27
|
-
"@townco/secret": "0.1.
|
|
28
|
-
"@townco/ui": "0.1.
|
|
25
|
+
"@townco/agent": "0.1.51",
|
|
26
|
+
"@townco/core": "0.0.24",
|
|
27
|
+
"@townco/secret": "0.1.46",
|
|
28
|
+
"@townco/ui": "0.1.46",
|
|
29
29
|
"@types/inquirer": "^9.0.9",
|
|
30
30
|
"ink": "^6.4.0",
|
|
31
31
|
"ink-text-input": "^6.0.0",
|