@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.
@@ -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: "Most capable model - best for complex tasks",
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: "Fastest model - great for simple tasks",
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: 95,
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
- return lines;
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 { appendFileSync, existsSync, mkdirSync } from "node:fs";
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
- return (_jsx(Box, { flexDirection: "column", height: "100%", children: _jsx(MergedLogsPane, { services: serviceOutputs, onClear: handleClearMergedOutput }) }));
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.50",
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.42",
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.50",
26
- "@townco/core": "0.0.23",
27
- "@townco/secret": "0.1.45",
28
- "@townco/ui": "0.1.45",
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",