@townco/cli 0.1.32 → 0.1.34

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.
@@ -3,5 +3,6 @@ export interface RunCommandOptions {
3
3
  http?: boolean;
4
4
  gui?: boolean;
5
5
  port?: number;
6
+ noSession?: boolean;
6
7
  }
7
8
  export declare function runCommand(options: RunCommandOptions): Promise<void>;
@@ -13,7 +13,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
13
13
  import { LogsPane } from "../components/LogsPane.js";
14
14
  import { TabbedOutput } from "../components/TabbedOutput.js";
15
15
  import { findAvailablePort } from "../lib/port-utils.js";
16
- function TuiRunner({ agentPath, workingDir, onExit }) {
16
+ function TuiRunner({ agentPath, workingDir, noSession, onExit, }) {
17
17
  const [client, setClient] = useState(null);
18
18
  const [error, setError] = useState(null);
19
19
  // Configure logs directory for UI package loggers BEFORE any loggers are created
@@ -38,6 +38,7 @@ function TuiRunner({ agentPath, workingDir, onExit }) {
38
38
  options: {
39
39
  agentPath,
40
40
  workingDirectory: workingDir,
41
+ environment: noSession ? { TOWN_NO_SESSION: "true" } : {},
41
42
  },
42
43
  });
43
44
  setClient(newClient);
@@ -54,7 +55,7 @@ function TuiRunner({ agentPath, workingDir, onExit }) {
54
55
  setError(errorMsg);
55
56
  return undefined;
56
57
  }
57
- }, [agentPath, workingDir, logger]);
58
+ }, [agentPath, workingDir, logger, noSession]);
58
59
  const customTabs = useMemo(() => [
59
60
  {
60
61
  name: "Chat",
@@ -143,7 +144,7 @@ async function loadEnvVars(projectRoot, logger) {
143
144
  return envVars;
144
145
  }
145
146
  export async function runCommand(options) {
146
- const { name, http = false, gui = false, port = 3100 } = options;
147
+ const { name, http = false, gui = false, port = 3100, noSession = false, } = options;
147
148
  // Check if we're inside a Town project
148
149
  const projectRoot = await isInsideTownProject();
149
150
  if (projectRoot === null) {
@@ -219,6 +220,7 @@ export async function runCommand(options) {
219
220
  ...configEnvVars,
220
221
  NODE_ENV: process.env.NODE_ENV || "production",
221
222
  PORT: availablePort.toString(),
223
+ ...(noSession ? { TOWN_NO_SESSION: "true" } : {}),
222
224
  },
223
225
  });
224
226
  // Start the GUI dev server (no package.json, run vite directly)
@@ -264,6 +266,7 @@ export async function runCommand(options) {
264
266
  ...configEnvVars,
265
267
  NODE_ENV: process.env.NODE_ENV || "production",
266
268
  PORT: availablePort.toString(),
269
+ ...(noSession ? { TOWN_NO_SESSION: "true" } : {}),
267
270
  },
268
271
  });
269
272
  agentProcess.on("error", (error) => {
@@ -291,7 +294,7 @@ export async function runCommand(options) {
291
294
  process.stdin.setRawMode(true);
292
295
  }
293
296
  // Render the tabbed UI with Chat and Logs
294
- const { waitUntilExit } = render(_jsx(TuiRunner, { agentPath: binPath, workingDir: agentPath, onExit: () => {
297
+ const { waitUntilExit } = render(_jsx(TuiRunner, { agentPath: binPath, workingDir: agentPath, noSession: noSession, onExit: () => {
295
298
  // Cleanup is handled by the ACP client disconnect
296
299
  } }));
297
300
  await waitUntilExit();
@@ -39,7 +39,6 @@ export function MergedLogsPane({ services, onClear }) {
39
39
  const [searchQuery, setSearchQuery] = useState("");
40
40
  const [serviceFilter, setServiceFilter] = useState(null);
41
41
  const [levelFilter, setLevelFilter] = useState(null);
42
- const maxLines = 25;
43
42
  // Only include services that actually have output
44
43
  const availableServices = useMemo(() => services.filter((s) => s.output.length > 0).map((s) => s.service), [services]);
45
44
  // Clear service filter if the selected service is no longer available
@@ -153,16 +152,13 @@ export function MergedLogsPane({ services, onClear }) {
153
152
  return levelFilteredLines.filter((log) => fuzzyMatch(searchQuery, log.line));
154
153
  }, [levelFilteredLines, searchQuery]);
155
154
  const isAtBottom = scrollOffset === 0;
156
- // Calculate which slice to show
155
+ // Show all lines - no truncation
157
156
  const displayLines = useMemo(() => {
158
157
  if (isAtBottom) {
159
- return searchedLines.slice(-maxLines);
158
+ return searchedLines; // Show all logs when at bottom
160
159
  }
161
- return searchedLines.slice(Math.max(0, searchedLines.length - maxLines - scrollOffset), searchedLines.length - scrollOffset);
160
+ return searchedLines.slice(0, searchedLines.length - scrollOffset);
162
161
  }, [searchedLines, scrollOffset, isAtBottom]);
163
- const _canScrollUp = searchedLines.length > maxLines &&
164
- scrollOffset < searchedLines.length - maxLines;
165
- const _canScrollDown = scrollOffset > 0;
166
162
  // Get overall status (error if any error, stopped if all stopped, etc.)
167
163
  const overallStatus = useMemo(() => {
168
164
  if (services.some((s) => s.status === "error"))
@@ -20,7 +20,6 @@ export function ProcessPane({ title, output, port, status, onClear, }) {
20
20
  const [scrollOffset, setScrollOffset] = useState(0); // 0 = at bottom, positive = scrolled up
21
21
  const [searchMode, setSearchMode] = useState(false);
22
22
  const [searchQuery, setSearchQuery] = useState("");
23
- const maxLines = 25;
24
23
  // Handle keyboard input for scrolling, clearing, and search
25
24
  useInput((input, key) => {
26
25
  // '/' to enter search mode
@@ -45,7 +44,7 @@ export function ProcessPane({ title, output, port, status, onClear, }) {
45
44
  }
46
45
  // Up arrow to scroll up
47
46
  if (key.upArrow) {
48
- setScrollOffset((prev) => Math.min(prev + 1, Math.max(0, output.length - maxLines)));
47
+ setScrollOffset((prev) => Math.min(prev + 1, output.length - 1));
49
48
  return;
50
49
  }
51
50
  // Down arrow to scroll down
@@ -69,16 +68,14 @@ export function ProcessPane({ title, output, port, status, onClear, }) {
69
68
  return output;
70
69
  return output.filter((line) => fuzzyMatch(searchQuery, line));
71
70
  }, [output, searchQuery]);
72
- // Calculate which slice of output to show
71
+ // Show all lines - no truncation
73
72
  const displayOutput = useMemo(() => {
74
- const source = searchedOutput;
75
73
  if (isAtBottom) {
76
- return source.slice(-maxLines); // Show last 25 when at bottom
74
+ return searchedOutput; // Show all logs when at bottom
77
75
  }
78
- return source.slice(Math.max(0, source.length - maxLines - scrollOffset), source.length - scrollOffset);
76
+ return searchedOutput.slice(0, searchedOutput.length - scrollOffset);
79
77
  }, [searchedOutput, scrollOffset, isAtBottom]);
80
- const canScrollUp = searchedOutput.length > maxLines &&
81
- scrollOffset < searchedOutput.length - maxLines;
78
+ const canScrollUp = scrollOffset < searchedOutput.length - 1;
82
79
  const canScrollDown = scrollOffset > 0;
83
80
  const statusColor = status === "running"
84
81
  ? "green"
@@ -87,5 +84,5 @@ export function ProcessPane({ title, output, port, status, onClear, }) {
87
84
  : status === "starting"
88
85
  ? "yellow"
89
86
  : "gray";
90
- return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsxs(Box, { borderStyle: "single", paddingX: 1, marginBottom: 1, flexShrink: 0, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: "cyan", bold: true, children: title }), port && _jsxs(Text, { color: "gray", children: [" - http://localhost:", port] }), _jsx(Text, { children: " " }), _jsx(Text, { color: statusColor, children: "\u25CF" }), _jsxs(Text, { children: [" ", status] }), searchQuery && _jsxs(Text, { color: "cyan", children: [" [SEARCH: ", searchQuery, "]"] }), !isAtBottom && _jsx(Text, { color: "yellow", children: " [SCROLLED]" })] }), _jsx(Text, { color: "gray", children: "(/)search | \u2191\u2193 | (c)lear | ESC" })] }), searchMode && (_jsxs(Box, { borderStyle: "single", borderBottom: true, borderColor: "cyan", paddingX: 1, marginBottom: 1, flexShrink: 0, children: [_jsx(Text, { color: "cyan", children: "Search: " }), _jsx(TextInput, { value: searchQuery, onChange: setSearchQuery, placeholder: "Type to search..." }), _jsx(Text, { dimColor: true, children: " (ESC to exit)" })] })), _jsx(Box, { flexDirection: "column", flexGrow: 1, minHeight: 0, paddingX: 1, children: displayOutput.length === 0 ? (_jsx(Text, { color: "gray", children: "Waiting for output..." })) : (displayOutput.map((line, idx) => (_jsx(Text, { children: line }, `${idx}-${line.slice(0, 20)}`)))) }), _jsxs(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexShrink: 0, justifyContent: "space-between", children: [_jsxs(Text, { color: "gray", children: ["Showing ", displayOutput.length, " of ", searchedOutput.length, " lines", searchQuery && ` (${searchedOutput.length} matches)`, scrollOffset > 0 && ` (${scrollOffset} from bottom)`] }), searchedOutput.length > maxLines && (_jsxs(Text, { color: "gray", children: [canScrollUp && "↑ ", canScrollDown && "↓"] }))] })] }));
87
+ return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsxs(Box, { borderStyle: "single", paddingX: 1, marginBottom: 1, flexShrink: 0, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: "cyan", bold: true, children: title }), port && _jsxs(Text, { color: "gray", children: [" - http://localhost:", port] }), _jsx(Text, { children: " " }), _jsx(Text, { color: statusColor, children: "\u25CF" }), _jsxs(Text, { children: [" ", status] }), searchQuery && _jsxs(Text, { color: "cyan", children: [" [SEARCH: ", searchQuery, "]"] }), !isAtBottom && _jsx(Text, { color: "yellow", children: " [SCROLLED]" })] }), _jsx(Text, { color: "gray", children: "(/)search | \u2191\u2193 | (c)lear | ESC" })] }), searchMode && (_jsxs(Box, { borderStyle: "single", borderBottom: true, borderColor: "cyan", paddingX: 1, marginBottom: 1, flexShrink: 0, children: [_jsx(Text, { color: "cyan", children: "Search: " }), _jsx(TextInput, { value: searchQuery, onChange: setSearchQuery, placeholder: "Type to search..." }), _jsx(Text, { dimColor: true, children: " (ESC to exit)" })] })), _jsx(Box, { flexDirection: "column", flexGrow: 1, minHeight: 0, paddingX: 1, children: displayOutput.length === 0 ? (_jsx(Text, { color: "gray", children: "Waiting for output..." })) : (displayOutput.map((line, idx) => (_jsx(Text, { children: line }, `${idx}-${line.slice(0, 20)}`)))) }), _jsxs(Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexShrink: 0, justifyContent: "space-between", children: [_jsxs(Text, { color: "gray", children: ["Showing ", displayOutput.length, " of ", searchedOutput.length, " lines", searchQuery && ` (${searchedOutput.length} matches)`, scrollOffset > 0 && ` (${scrollOffset} from bottom)`] }), (canScrollUp || canScrollDown) && (_jsxs(Text, { color: "gray", children: [canScrollUp && "↑ ", canScrollDown && "↓"] }))] })] }));
91
88
  }
package/dist/index.js CHANGED
@@ -45,6 +45,7 @@ const parser = or(command("deploy", constant("deploy"), { brief: message `Deploy
45
45
  http: optional(flag("--http")),
46
46
  gui: optional(flag("--gui")),
47
47
  port: optional(option("-p", "--port", integer())),
48
+ noSession: optional(flag("--no-session")),
48
49
  }), { brief: message `Run an agent.` }), command("secret", object({
49
50
  command: constant("secret"),
50
51
  subcommand: or(command("list", constant("list"), { brief: message `List secrets.` }), command("add", object({
@@ -145,11 +146,12 @@ async function main(parser, meta) {
145
146
  }
146
147
  }
147
148
  })
148
- .with({ command: "run" }, async ({ name, http, gui, port }) => {
149
+ .with({ command: "run" }, async ({ name, http, gui, port, noSession }) => {
149
150
  const options = {
150
151
  name,
151
152
  http: http === true,
152
153
  gui: gui === true,
154
+ noSession: noSession === true,
153
155
  };
154
156
  if (port !== null && port !== undefined) {
155
157
  options.port = port;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@townco/cli",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
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.24",
18
+ "@townco/tsconfig": "0.1.26",
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.32",
26
- "@townco/core": "0.0.5",
27
- "@townco/secret": "0.1.27",
28
- "@townco/ui": "0.1.27",
25
+ "@townco/agent": "0.1.34",
26
+ "@townco/core": "0.0.7",
27
+ "@townco/secret": "0.1.29",
28
+ "@townco/ui": "0.1.29",
29
29
  "@types/inquirer": "^9.0.9",
30
30
  "ink": "^6.4.0",
31
31
  "ink-text-input": "^6.0.0",