@townco/cli 0.1.109 → 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 +30 -50
- 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,19 +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
6
|
import { Box, Static, Text, useInput } from "ink";
|
|
7
|
-
import TextInput from "ink-text-input";
|
|
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
|
-
];
|
|
8
|
+
const LOG_LEVELS = ["all", "info", "warn", "error"];
|
|
17
9
|
function buildCommand(logsDir, pretty, level, sessionStartTime) {
|
|
18
10
|
const logFile = join(logsDir, LOG_FILE_NAME);
|
|
19
11
|
const tailCmd = `tail -n +1 -f "${logFile}"`;
|
|
@@ -21,13 +13,8 @@ function buildCommand(logsDir, pretty, level, sessionStartTime) {
|
|
|
21
13
|
const conditions = [];
|
|
22
14
|
conditions.push(`.timestamp >= "${sessionStartTime}"`);
|
|
23
15
|
if (level !== "all") {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const allowedLevels = levelOrder.slice(levelIndex);
|
|
27
|
-
const levelCondition = allowedLevels
|
|
28
|
-
.map((l) => `.level == "${l}"`)
|
|
29
|
-
.join(" or ");
|
|
30
|
-
conditions.push(`(${levelCondition})`);
|
|
16
|
+
// Filter to exact level only
|
|
17
|
+
conditions.push(`.level == "${level}"`);
|
|
31
18
|
}
|
|
32
19
|
const jqFilter = `select(${conditions.join(" and ")})`;
|
|
33
20
|
const jqFlags = pretty ? "-C --unbuffered" : "-c --unbuffered";
|
|
@@ -35,24 +22,26 @@ function buildCommand(logsDir, pretty, level, sessionStartTime) {
|
|
|
35
22
|
}
|
|
36
23
|
export function LogsPane({ logsDir: customLogsDir } = {}) {
|
|
37
24
|
const logsDir = useMemo(() => customLogsDir ?? join(process.cwd(), ".logs"), [customLogsDir]);
|
|
38
|
-
const sessionStartTime =
|
|
25
|
+
const [sessionStartTime, setSessionStartTime] = useState(() => new Date().toISOString());
|
|
39
26
|
const [pretty, setPretty] = useState(true);
|
|
40
27
|
const [level, setLevel] = useState("all");
|
|
41
|
-
const [editMode, setEditMode] = useState(false);
|
|
42
|
-
const [customCommand, setCustomCommand] = useState("");
|
|
43
28
|
const [outputLines, setOutputLines] = useState([]);
|
|
44
29
|
const [error, setError] = useState(null);
|
|
45
30
|
const processRef = useRef(null);
|
|
46
31
|
const commandRunIdRef = useRef(0);
|
|
47
32
|
const lineIdRef = useRef(0);
|
|
48
|
-
const activeCommand = useMemo(() =>
|
|
49
|
-
? customCommand
|
|
50
|
-
: buildCommand(logsDir, pretty, level, sessionStartTime), [editMode, customCommand, logsDir, pretty, level, sessionStartTime]);
|
|
33
|
+
const activeCommand = useMemo(() => buildCommand(logsDir, pretty, level, sessionStartTime), [logsDir, pretty, level, sessionStartTime]);
|
|
51
34
|
// Spawn tail process
|
|
52
35
|
useEffect(() => {
|
|
36
|
+
// Create logs directory if it doesn't exist
|
|
53
37
|
if (!existsSync(logsDir)) {
|
|
54
|
-
|
|
55
|
-
|
|
38
|
+
try {
|
|
39
|
+
mkdirSync(logsDir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
catch (_error) {
|
|
42
|
+
setError(`Failed to create logs directory: ${logsDir}`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
56
45
|
}
|
|
57
46
|
if (processRef.current) {
|
|
58
47
|
processRef.current.kill();
|
|
@@ -62,7 +51,7 @@ export function LogsPane({ logsDir: customLogsDir } = {}) {
|
|
|
62
51
|
setOutputLines([]);
|
|
63
52
|
lineIdRef.current = 0;
|
|
64
53
|
const runId = ++commandRunIdRef.current;
|
|
65
|
-
const
|
|
54
|
+
const commandTimeoutId = setTimeout(() => {
|
|
66
55
|
if (runId !== commandRunIdRef.current)
|
|
67
56
|
return;
|
|
68
57
|
const proc = spawn("sh", ["-c", activeCommand], {
|
|
@@ -98,7 +87,7 @@ export function LogsPane({ logsDir: customLogsDir } = {}) {
|
|
|
98
87
|
});
|
|
99
88
|
}, 50);
|
|
100
89
|
return () => {
|
|
101
|
-
clearTimeout(
|
|
90
|
+
clearTimeout(commandTimeoutId);
|
|
102
91
|
if (processRef.current) {
|
|
103
92
|
processRef.current.kill();
|
|
104
93
|
processRef.current = null;
|
|
@@ -108,27 +97,17 @@ export function LogsPane({ logsDir: customLogsDir } = {}) {
|
|
|
108
97
|
const handleInput = useCallback((input, key) => {
|
|
109
98
|
if (key.ctrl && input === "c")
|
|
110
99
|
return;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
if (key.return) {
|
|
117
|
-
setEditMode(false);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if (input === "e") {
|
|
123
|
-
setCustomCommand(activeCommand);
|
|
124
|
-
setEditMode(true);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
100
|
+
// Helper to clear terminal before state changes
|
|
101
|
+
const clearTerminal = () => {
|
|
102
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
103
|
+
};
|
|
127
104
|
if (input === "f") {
|
|
105
|
+
clearTerminal();
|
|
128
106
|
setPretty((prev) => !prev);
|
|
129
107
|
return;
|
|
130
108
|
}
|
|
131
109
|
if (input === "l") {
|
|
110
|
+
clearTerminal();
|
|
132
111
|
setLevel((prev) => {
|
|
133
112
|
const currentIndex = LOG_LEVELS.indexOf(prev);
|
|
134
113
|
const nextIndex = (currentIndex + 1) % LOG_LEVELS.length;
|
|
@@ -137,23 +116,24 @@ export function LogsPane({ logsDir: customLogsDir } = {}) {
|
|
|
137
116
|
return;
|
|
138
117
|
}
|
|
139
118
|
if (input === "c" && !key.ctrl) {
|
|
140
|
-
|
|
119
|
+
// Clear terminal and update timestamp to only show new logs
|
|
120
|
+
clearTerminal();
|
|
121
|
+
setSessionStartTime(new Date().toISOString());
|
|
141
122
|
return;
|
|
142
123
|
}
|
|
143
|
-
}, [
|
|
124
|
+
}, []);
|
|
144
125
|
useInput(handleInput);
|
|
145
126
|
if (error) {
|
|
146
127
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(Text, { color: "red", children: error }) }));
|
|
147
128
|
}
|
|
148
129
|
return (_jsxs(Box, { flexDirection: "column", children: [
|
|
149
|
-
_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: [
|
|
150
|
-
|
|
151
|
-
] })) : (_jsxs(Box, { 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: [
|
|
152
132
|
_jsx(Text, { dimColor: true, children: "$ " }), _jsx(Text, { children: activeCommand })
|
|
153
|
-
] })
|
|
133
|
+
] }), _jsxs(Box, { justifyContent: "space-between", children: [
|
|
154
134
|
_jsxs(Box, { children: [
|
|
155
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"] })
|
|
156
|
-
] }), _jsx(Text, { dimColor: true, children: "(
|
|
136
|
+
] }), _jsx(Text, { dimColor: true, children: "(f)ormat | (l)evel | (c)lear" })
|
|
157
137
|
] })
|
|
158
138
|
] })
|
|
159
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",
|