@townco/cli 0.1.82 → 0.1.83
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/batch-wrapper.d.ts +26 -0
- package/dist/commands/batch-wrapper.js +35 -0
- package/dist/commands/batch.js +2 -2
- package/dist/commands/create-wrapper.d.ts +29 -0
- package/dist/commands/create-wrapper.js +92 -0
- package/dist/commands/create.js +38 -12
- package/dist/commands/deploy.d.ts +14 -0
- package/dist/commands/deploy.js +88 -0
- package/dist/commands/login.js +1 -1
- package/dist/commands/run-wrapper.d.ts +32 -0
- package/dist/commands/run-wrapper.js +32 -0
- package/dist/commands/run.js +23 -16
- package/dist/commands/secret.d.ts +62 -0
- package/dist/commands/secret.js +119 -0
- package/dist/components/LogsPane.d.ts +1 -1
- package/dist/components/LogsPane.js +12 -3
- package/dist/components/ProcessPane.d.ts +1 -1
- package/dist/components/ProcessPane.js +9 -1
- package/dist/components/StatusLine.js +4 -1
- package/dist/components/TabbedOutput.d.ts +1 -1
- package/dist/components/TabbedOutput.js +2 -1
- package/dist/index.js +21 -338
- package/dist/lib/command.d.ts +8 -0
- package/dist/lib/command.js +1 -0
- package/package.json +11 -15
- package/dist/commands/delete.d.ts +0 -1
- package/dist/commands/delete.js +0 -60
- package/dist/commands/edit.d.ts +0 -1
- package/dist/commands/edit.js +0 -92
- package/dist/commands/list.d.ts +0 -1
- package/dist/commands/list.js +0 -55
- package/dist/commands/mcp-add.d.ts +0 -14
- package/dist/commands/mcp-add.js +0 -494
- package/dist/commands/mcp-list.d.ts +0 -3
- package/dist/commands/mcp-list.js +0 -63
- package/dist/commands/mcp-remove.d.ts +0 -3
- package/dist/commands/mcp-remove.js +0 -120
- package/dist/commands/tool-add.d.ts +0 -6
- package/dist/commands/tool-add.js +0 -349
- package/dist/commands/tool-list.d.ts +0 -3
- package/dist/commands/tool-list.js +0 -61
- package/dist/commands/tool-register.d.ts +0 -7
- package/dist/commands/tool-register.js +0 -291
- package/dist/commands/tool-remove.d.ts +0 -3
- package/dist/commands/tool-remove.js +0 -202
- package/dist/components/MergedLogsPane.d.ts +0 -11
- package/dist/components/MergedLogsPane.js +0 -205
- package/dist/lib/auth-storage.d.ts +0 -38
- package/dist/lib/auth-storage.js +0 -89
- package/dist/lib/mcp-storage.d.ts +0 -32
- package/dist/lib/mcp-storage.js +0 -111
package/dist/commands/run.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
@@ -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 { ensurePortAvailable } from "../lib/port-utils.js";
|
|
16
|
-
function TuiRunner({ agentPath, workingDir, noSession, onExit, }) {
|
|
16
|
+
function TuiRunner({ agentPath, workingDir, agentName, 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
|
|
@@ -39,6 +39,7 @@ function TuiRunner({ agentPath, workingDir, noSession, onExit, }) {
|
|
|
39
39
|
agentPath,
|
|
40
40
|
workingDirectory: workingDir,
|
|
41
41
|
environment: {
|
|
42
|
+
AGENT_NAME: agentName,
|
|
42
43
|
ENABLE_TELEMETRY: "true",
|
|
43
44
|
...(noSession ? { TOWN_NO_SESSION: "true" } : {}),
|
|
44
45
|
},
|
|
@@ -58,14 +59,16 @@ function TuiRunner({ agentPath, workingDir, noSession, onExit, }) {
|
|
|
58
59
|
setError(errorMsg);
|
|
59
60
|
return undefined;
|
|
60
61
|
}
|
|
61
|
-
}, [agentPath, workingDir, logger, noSession]);
|
|
62
|
+
}, [agentPath, workingDir, logger, noSession, agentName]);
|
|
62
63
|
const customTabs = useMemo(() => [
|
|
63
64
|
{
|
|
64
65
|
name: "Chat",
|
|
65
66
|
type: "custom",
|
|
66
67
|
render: () => {
|
|
67
68
|
if (error) {
|
|
68
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [
|
|
69
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [
|
|
70
|
+
_jsxs(Text, { color: "red", bold: true, children: ["Error: ", error] }), _jsx(Text, { color: "gray", children: "Please check your agent path and try again." })
|
|
71
|
+
] }));
|
|
69
72
|
}
|
|
70
73
|
if (!client) {
|
|
71
74
|
return (_jsx(Box, { padding: 1, children: _jsx(Text, { dimColor: true, children: "Loading chat interface..." }) }));
|
|
@@ -155,7 +158,7 @@ async function runCliMode(options) {
|
|
|
155
158
|
let retries = 30; // 3 seconds
|
|
156
159
|
while (!debuggerReady && retries > 0) {
|
|
157
160
|
try {
|
|
158
|
-
const
|
|
161
|
+
const _response = await fetch(`http://localhost:4318/v1/traces`, {
|
|
159
162
|
method: "POST",
|
|
160
163
|
headers: { "Content-Type": "application/json" },
|
|
161
164
|
body: JSON.stringify({}),
|
|
@@ -164,7 +167,7 @@ async function runCliMode(options) {
|
|
|
164
167
|
debuggerReady = true;
|
|
165
168
|
break;
|
|
166
169
|
}
|
|
167
|
-
catch (
|
|
170
|
+
catch (_e) {
|
|
168
171
|
// Server not ready yet
|
|
169
172
|
}
|
|
170
173
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
@@ -186,6 +189,7 @@ async function runCliMode(options) {
|
|
|
186
189
|
env: {
|
|
187
190
|
...process.env,
|
|
188
191
|
...configEnvVars,
|
|
192
|
+
AGENT_NAME: options.agentName,
|
|
189
193
|
PORT: String(agentPort),
|
|
190
194
|
ENABLE_TELEMETRY: "true",
|
|
191
195
|
...(noSession ? { TOWN_NO_SESSION: "true" } : {}),
|
|
@@ -243,7 +247,7 @@ async function runCliMode(options) {
|
|
|
243
247
|
await client.sendMessage(prompt, sessionId);
|
|
244
248
|
// Track tool calls and message state
|
|
245
249
|
const toolCalls = new Map();
|
|
246
|
-
let
|
|
250
|
+
let _currentAssistantMessage = "";
|
|
247
251
|
let isStreamingText = false;
|
|
248
252
|
// Receive and render messages
|
|
249
253
|
for await (const chunk of client.receiveMessages()) {
|
|
@@ -256,7 +260,7 @@ async function runCliMode(options) {
|
|
|
256
260
|
isStreamingText = true;
|
|
257
261
|
}
|
|
258
262
|
process.stdout.write(chunk.contentDelta.text);
|
|
259
|
-
|
|
263
|
+
_currentAssistantMessage += chunk.contentDelta.text;
|
|
260
264
|
}
|
|
261
265
|
// Check if complete after processing content
|
|
262
266
|
if (chunk.isComplete) {
|
|
@@ -346,7 +350,7 @@ async function runCliMode(options) {
|
|
|
346
350
|
setTimeout(() => resolve(), 3000);
|
|
347
351
|
});
|
|
348
352
|
}
|
|
349
|
-
catch (
|
|
353
|
+
catch (_e) {
|
|
350
354
|
// Process may already be dead
|
|
351
355
|
}
|
|
352
356
|
}
|
|
@@ -363,7 +367,7 @@ async function runCliMode(options) {
|
|
|
363
367
|
try {
|
|
364
368
|
agentProcess.kill("SIGKILL");
|
|
365
369
|
}
|
|
366
|
-
catch (
|
|
370
|
+
catch (_e) {
|
|
367
371
|
// Process may already be dead
|
|
368
372
|
}
|
|
369
373
|
process.exit(1);
|
|
@@ -400,7 +404,7 @@ export async function runCommand(options) {
|
|
|
400
404
|
// Looking for patterns like: name: "bibliotecha" or name: 'bibliotecha'
|
|
401
405
|
usesBibliotechaMcp = /name:\s*["']bibliotecha["']/.test(content);
|
|
402
406
|
}
|
|
403
|
-
catch (
|
|
407
|
+
catch (_error) {
|
|
404
408
|
// If we can't read the agent definition, just use the directory name
|
|
405
409
|
// Silently fail - the directory name is a reasonable fallback
|
|
406
410
|
}
|
|
@@ -436,7 +440,7 @@ export async function runCommand(options) {
|
|
|
436
440
|
try {
|
|
437
441
|
debuggerProcess.kill("SIGTERM");
|
|
438
442
|
}
|
|
439
|
-
catch (
|
|
443
|
+
catch (_e) {
|
|
440
444
|
// Process may already be dead
|
|
441
445
|
}
|
|
442
446
|
}
|
|
@@ -501,6 +505,7 @@ export async function runCommand(options) {
|
|
|
501
505
|
env: {
|
|
502
506
|
...process.env,
|
|
503
507
|
...configEnvVars,
|
|
508
|
+
AGENT_NAME: name,
|
|
504
509
|
NODE_ENV: process.env.NODE_ENV || "production",
|
|
505
510
|
PORT: port.toString(),
|
|
506
511
|
ENABLE_TELEMETRY: "true",
|
|
@@ -538,13 +543,13 @@ export async function runCommand(options) {
|
|
|
538
543
|
try {
|
|
539
544
|
agentProcess.kill("SIGTERM");
|
|
540
545
|
}
|
|
541
|
-
catch (
|
|
546
|
+
catch (_e) {
|
|
542
547
|
// Process may already be dead
|
|
543
548
|
}
|
|
544
549
|
try {
|
|
545
550
|
guiProcess.kill("SIGTERM");
|
|
546
551
|
}
|
|
547
|
-
catch (
|
|
552
|
+
catch (_e) {
|
|
548
553
|
// Process may already be dead
|
|
549
554
|
}
|
|
550
555
|
// Also cleanup debugger
|
|
@@ -588,6 +593,7 @@ export async function runCommand(options) {
|
|
|
588
593
|
env: {
|
|
589
594
|
...process.env,
|
|
590
595
|
...configEnvVars,
|
|
596
|
+
AGENT_NAME: name,
|
|
591
597
|
NODE_ENV: process.env.NODE_ENV || "production",
|
|
592
598
|
PORT: port.toString(),
|
|
593
599
|
ENABLE_TELEMETRY: "true",
|
|
@@ -603,7 +609,7 @@ export async function runCommand(options) {
|
|
|
603
609
|
try {
|
|
604
610
|
agentProcess.kill("SIGTERM");
|
|
605
611
|
}
|
|
606
|
-
catch (
|
|
612
|
+
catch (_e) {
|
|
607
613
|
// Process may already be dead
|
|
608
614
|
}
|
|
609
615
|
// Also cleanup debugger
|
|
@@ -643,6 +649,7 @@ export async function runCommand(options) {
|
|
|
643
649
|
await runCliMode({
|
|
644
650
|
binPath,
|
|
645
651
|
agentPath,
|
|
652
|
+
agentName: name,
|
|
646
653
|
workingDir: agentPath,
|
|
647
654
|
prompt,
|
|
648
655
|
configEnvVars,
|
|
@@ -673,7 +680,7 @@ export async function runCommand(options) {
|
|
|
673
680
|
process.on("SIGINT", handleTuiSigint);
|
|
674
681
|
process.on("SIGTERM", handleTuiSigint);
|
|
675
682
|
// Render the tabbed UI with Chat and Logs
|
|
676
|
-
const { waitUntilExit } = render(_jsx(TuiRunner, { agentPath: binPath, workingDir: agentPath, noSession: noSession, onExit: () => {
|
|
683
|
+
const { waitUntilExit } = render(_jsx(TuiRunner, { agentPath: binPath, workingDir: agentPath, agentName: name, noSession: noSession, onExit: () => {
|
|
677
684
|
// Cleanup is handled by the ACP client disconnect
|
|
678
685
|
cleanupDebugger?.();
|
|
679
686
|
} }));
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
def: import("@optique/core").Parser<{
|
|
3
|
+
readonly command: "secret";
|
|
4
|
+
readonly subcommand: {
|
|
5
|
+
readonly action: "list";
|
|
6
|
+
} | {
|
|
7
|
+
readonly action: "add";
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly value: string | undefined;
|
|
10
|
+
} | {
|
|
11
|
+
readonly action: "update";
|
|
12
|
+
readonly name: string | undefined;
|
|
13
|
+
readonly value: string | undefined;
|
|
14
|
+
readonly genenv: true | undefined;
|
|
15
|
+
} | {
|
|
16
|
+
readonly action: "remove";
|
|
17
|
+
readonly name: string;
|
|
18
|
+
} | {
|
|
19
|
+
readonly action: "genenv";
|
|
20
|
+
};
|
|
21
|
+
}, ["matched", string] | ["parsing", {
|
|
22
|
+
readonly command: "secret";
|
|
23
|
+
readonly subcommand: [0, import("@optique/core").ParserResult<["matched", string] | ["parsing", {
|
|
24
|
+
readonly action: "list";
|
|
25
|
+
}] | undefined>] | [1, import("@optique/core").ParserResult<["matched", string] | ["parsing", {
|
|
26
|
+
readonly action: "add";
|
|
27
|
+
readonly name: import("@optique/core").ValueParserResult<string> | undefined;
|
|
28
|
+
readonly value: [import("@optique/core").ValueParserResult<string> | undefined] | undefined;
|
|
29
|
+
}] | undefined>] | [2, import("@optique/core").ParserResult<["matched", string] | ["parsing", {
|
|
30
|
+
readonly action: "update";
|
|
31
|
+
readonly name: [import("@optique/core").ValueParserResult<string> | undefined] | undefined;
|
|
32
|
+
readonly value: [import("@optique/core").ValueParserResult<string> | undefined] | undefined;
|
|
33
|
+
readonly genenv: [import("@optique/core").ValueParserResult<true> | undefined] | undefined;
|
|
34
|
+
}] | undefined>] | [3, import("@optique/core").ParserResult<["matched", string] | ["parsing", {
|
|
35
|
+
readonly action: "remove";
|
|
36
|
+
readonly name: import("@optique/core").ValueParserResult<string> | undefined;
|
|
37
|
+
}] | undefined>] | [4, import("@optique/core").ParserResult<["matched", string] | ["parsing", {
|
|
38
|
+
readonly action: "genenv";
|
|
39
|
+
}] | undefined>] | undefined;
|
|
40
|
+
}] | undefined>;
|
|
41
|
+
impl: (def: {
|
|
42
|
+
readonly command: "secret";
|
|
43
|
+
readonly subcommand: {
|
|
44
|
+
readonly action: "list";
|
|
45
|
+
} | {
|
|
46
|
+
readonly action: "add";
|
|
47
|
+
readonly name: string;
|
|
48
|
+
readonly value: string | undefined;
|
|
49
|
+
} | {
|
|
50
|
+
readonly action: "update";
|
|
51
|
+
readonly name: string | undefined;
|
|
52
|
+
readonly value: string | undefined;
|
|
53
|
+
readonly genenv: true | undefined;
|
|
54
|
+
} | {
|
|
55
|
+
readonly action: "remove";
|
|
56
|
+
readonly name: string;
|
|
57
|
+
} | {
|
|
58
|
+
readonly action: "genenv";
|
|
59
|
+
};
|
|
60
|
+
}) => unknown;
|
|
61
|
+
};
|
|
62
|
+
export default _default;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { argument, command, constant, flag, message, object, optional, or, string, } from "@optique/core";
|
|
2
|
+
import { updateSchema as updateEnvSchema } from "@townco/env/update-schema";
|
|
3
|
+
import { createSecret, deleteSecret, genenv, listSecrets, updateSecret, } from "@townco/secret";
|
|
4
|
+
import inquirer from "inquirer";
|
|
5
|
+
import { match } from "ts-pattern";
|
|
6
|
+
import { createCommand } from "@/lib/command";
|
|
7
|
+
/**
|
|
8
|
+
* Securely prompt for a secret value without echoing to the terminal
|
|
9
|
+
*/
|
|
10
|
+
const promptSecret = async (secretName) => {
|
|
11
|
+
const answers = await inquirer.prompt([
|
|
12
|
+
{
|
|
13
|
+
type: "password",
|
|
14
|
+
name: "value",
|
|
15
|
+
message: `Enter value for secret '${secretName}':`,
|
|
16
|
+
mask: "*",
|
|
17
|
+
},
|
|
18
|
+
]);
|
|
19
|
+
return answers.value;
|
|
20
|
+
};
|
|
21
|
+
export default createCommand({
|
|
22
|
+
def: command("secret", object({
|
|
23
|
+
command: constant("secret"),
|
|
24
|
+
subcommand: or(command("list", object({ action: constant("list") }), {
|
|
25
|
+
brief: message `List secrets.`,
|
|
26
|
+
}), command("add", object({
|
|
27
|
+
action: constant("add"),
|
|
28
|
+
name: argument(string({ metavar: "NAME" })),
|
|
29
|
+
value: optional(argument(string({ metavar: "VALUE" }))),
|
|
30
|
+
}), { brief: message `Add a secret.` }), command("update", object({
|
|
31
|
+
action: constant("update"),
|
|
32
|
+
name: optional(argument(string({ metavar: "NAME" }))),
|
|
33
|
+
value: optional(argument(string({ metavar: "VALUE" }))),
|
|
34
|
+
genenv: optional(flag("-g", "--genenv", {
|
|
35
|
+
description: message `Regenerate .env file.`,
|
|
36
|
+
})),
|
|
37
|
+
}), { brief: message `Update a secret.` }), command("remove", object({
|
|
38
|
+
action: constant("remove"),
|
|
39
|
+
name: argument(string({ metavar: "NAME" })),
|
|
40
|
+
}), { brief: message `Remove a secret.` }), command("genenv", object({ action: constant("genenv") }), {
|
|
41
|
+
brief: message `Generate .env file.`,
|
|
42
|
+
})),
|
|
43
|
+
}), { brief: message `Secrets management.` }),
|
|
44
|
+
impl: async ({ subcommand }) => {
|
|
45
|
+
await match(subcommand)
|
|
46
|
+
.with({ action: "list" }, async () => {
|
|
47
|
+
const truncate = (str, maxLength = 50) => {
|
|
48
|
+
if (str.length <= maxLength)
|
|
49
|
+
return str;
|
|
50
|
+
return `${str.slice(0, maxLength - 3)}...`;
|
|
51
|
+
};
|
|
52
|
+
console.table((await listSecrets()).map((secret) => ({
|
|
53
|
+
Key: secret.key,
|
|
54
|
+
Valid: secret.valid ? "✓" : "✗",
|
|
55
|
+
Error: truncate(secret.error ?? ""),
|
|
56
|
+
})));
|
|
57
|
+
})
|
|
58
|
+
.with({ action: "add" }, async ({ name, value }) => {
|
|
59
|
+
// If value is not provided, prompt securely
|
|
60
|
+
const secretValue = value ?? (await promptSecret(name));
|
|
61
|
+
if (!secretValue) {
|
|
62
|
+
console.error("Error: Secret value cannot be empty");
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
await createSecret(name, secretValue);
|
|
66
|
+
await updateEnvSchema();
|
|
67
|
+
console.log(`Secret '${name}' added successfully (& @packages/env schema updated).`);
|
|
68
|
+
})
|
|
69
|
+
.with({ action: "update" }, async ({ name, value, genenv: regen }) => {
|
|
70
|
+
let secretName = name;
|
|
71
|
+
// If name is not provided, show a list prompt to select from existing secrets
|
|
72
|
+
if (!secretName) {
|
|
73
|
+
const secrets = await listSecrets();
|
|
74
|
+
if (secrets.length === 0) {
|
|
75
|
+
console.error("No secrets found to update.");
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
const answer = await inquirer.prompt([
|
|
79
|
+
{
|
|
80
|
+
type: "list",
|
|
81
|
+
name: "selectedSecret",
|
|
82
|
+
message: "Select a secret to update:",
|
|
83
|
+
choices: secrets.map((s) => ({
|
|
84
|
+
name: `${s.key} ${s.valid ? "✓" : "✗"}`,
|
|
85
|
+
value: s.key,
|
|
86
|
+
})),
|
|
87
|
+
},
|
|
88
|
+
]);
|
|
89
|
+
secretName = answer.selectedSecret;
|
|
90
|
+
}
|
|
91
|
+
// If value is not provided, prompt securely
|
|
92
|
+
if (!secretName) {
|
|
93
|
+
console.error("Error: Secret name is required");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
const secretValue = value ?? (await promptSecret(secretName));
|
|
97
|
+
if (!secretValue) {
|
|
98
|
+
console.error("Error: Secret value cannot be empty");
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
await updateSecret(secretName, secretValue);
|
|
102
|
+
console.log(`Secret '${secretName}' updated successfully.`);
|
|
103
|
+
if (regen) {
|
|
104
|
+
await genenv();
|
|
105
|
+
console.log(".env file generated successfully.");
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
.with({ action: "remove" }, async ({ name }) => {
|
|
109
|
+
await deleteSecret(name);
|
|
110
|
+
await updateEnvSchema();
|
|
111
|
+
console.log(`Secret '${name}' removed successfully (& @packages/env schema updated).`);
|
|
112
|
+
})
|
|
113
|
+
.with({ action: "genenv" }, async () => {
|
|
114
|
+
await genenv();
|
|
115
|
+
console.log(".env file generated successfully.");
|
|
116
|
+
})
|
|
117
|
+
.exhaustive();
|
|
118
|
+
},
|
|
119
|
+
});
|
|
@@ -2,4 +2,4 @@ export interface LogsPaneProps {
|
|
|
2
2
|
logsDir?: string;
|
|
3
3
|
sessionStartTime?: string;
|
|
4
4
|
}
|
|
5
|
-
export declare function LogsPane({ logsDir: customLogsDir, sessionStartTime: providedSessionStartTime
|
|
5
|
+
export declare function LogsPane({ logsDir: customLogsDir, sessionStartTime: providedSessionStartTime }?: LogsPaneProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -241,13 +241,22 @@ export function LogsPane({ logsDir: customLogsDir, sessionStartTime: providedSes
|
|
|
241
241
|
: searchedLogs.slice(Math.max(0, totalLogs - maxLines - scrollOffset), totalLogs - scrollOffset);
|
|
242
242
|
const _canScrollUp = totalLogs > maxLines && scrollOffset < totalLogs - maxLines;
|
|
243
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: [
|
|
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) => {
|
|
245
247
|
const serviceColor = SERVICE_COLORS[availableServices.indexOf(log.service) % SERVICE_COLORS.length] || "white";
|
|
246
248
|
const levelColor = LOG_COLORS[log.level] || "white";
|
|
247
249
|
const time = new Date(log.timestamp).toLocaleTimeString();
|
|
248
250
|
const keyStr = `${log.timestamp}-${idx}`;
|
|
249
|
-
return (_jsxs(Box, { flexDirection: "column", children: [
|
|
251
|
+
return (_jsxs(Box, { flexDirection: "column", children: [
|
|
252
|
+
_jsxs(Box, { children: [
|
|
253
|
+
_jsxs(Text, { dimColor: true, children: ["[", time, "]"] }), _jsxs(Text, { color: serviceColor, children: [" [", log.service, "]"] }), _jsxs(Text, { color: levelColor, children: [" [", log.level.toUpperCase(), "]"] }), _jsxs(Text, { children: [" ", log.message] })
|
|
254
|
+
] }), expandedLogs &&
|
|
250
255
|
log.metadata &&
|
|
251
256
|
Object.keys(log.metadata).length > 0 && (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, children: JSON.stringify(log.metadata, null, 2) }) }))] }, keyStr));
|
|
252
|
-
})) }), _jsxs(Box, { borderStyle: "single", borderTop: true, borderColor: "gray", paddingX: 1, justifyContent: "space-between", children: [
|
|
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" }) })
|
|
260
|
+
] })
|
|
261
|
+
] }));
|
|
253
262
|
}
|
|
@@ -5,4 +5,4 @@ export interface ProcessPaneProps {
|
|
|
5
5
|
status: "starting" | "running" | "stopped" | "error";
|
|
6
6
|
onClear?: () => void;
|
|
7
7
|
}
|
|
8
|
-
export declare function ProcessPane({ title, output, port, status, onClear
|
|
8
|
+
export declare function ProcessPane({ title, output, port, status, onClear }: ProcessPaneProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -84,5 +84,13 @@ export function ProcessPane({ title, output, port, status, onClear, }) {
|
|
|
84
84
|
: status === "starting"
|
|
85
85
|
? "yellow"
|
|
86
86
|
: "gray";
|
|
87
|
-
return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [
|
|
87
|
+
return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [
|
|
88
|
+
_jsxs(Box, { borderStyle: "single", paddingX: 1, marginBottom: 1, flexShrink: 0, justifyContent: "space-between", children: [
|
|
89
|
+
_jsxs(Box, { children: [
|
|
90
|
+
_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" })
|
|
91
|
+
] }), searchMode && (_jsxs(Box, { borderStyle: "single", borderBottom: true, borderColor: "cyan", paddingX: 1, marginBottom: 1, flexShrink: 0, children: [
|
|
92
|
+
_jsx(Text, { color: "cyan", children: "Search: " }), _jsx(TextInput, { value: searchQuery, onChange: setSearchQuery, placeholder: "Type to search..." }), _jsx(Text, { dimColor: true, children: " (ESC to exit)" })
|
|
93
|
+
] })), _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: [
|
|
94
|
+
_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 && "↓"] }))] })
|
|
95
|
+
] }));
|
|
88
96
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
export function StatusLine({ activeTab, tabs, rightContent }) {
|
|
4
|
-
return (_jsxs(Box, { borderStyle: "single", borderColor: "blue", paddingX: 1, justifyContent: "space-between", children: [
|
|
4
|
+
return (_jsxs(Box, { borderStyle: "single", borderColor: "blue", paddingX: 1, justifyContent: "space-between", children: [
|
|
5
|
+
_jsxs(Box, { children: [tabs.map((tab, idx) => (_jsxs(Text, { children: [
|
|
6
|
+
_jsx(Text, { color: activeTab === idx ? "green" : "gray", children: tab }), idx < tabs.length - 1 && _jsx(Text, { color: "gray", children: " | " })] }, tab))), _jsx(Text, { color: "gray", children: " | " }), _jsx(Text, { color: "yellow", children: "tab" }), _jsx(Text, { color: "gray", children: " to switch" })
|
|
7
|
+
] }), rightContent && _jsx(Box, { children: rightContent })] }));
|
|
5
8
|
}
|
|
@@ -19,4 +19,4 @@ export interface TabbedOutputProps {
|
|
|
19
19
|
onExit: () => void;
|
|
20
20
|
onPortDetected?: (processIndex: number, port: number) => void;
|
|
21
21
|
}
|
|
22
|
-
export declare function TabbedOutput({ processes, customTabs, logsDir, onExit, onPortDetected
|
|
22
|
+
export declare function TabbedOutput({ processes, customTabs, logsDir, onExit, onPortDetected }: TabbedOutputProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -195,5 +195,6 @@ export function TabbedOutput({ processes, customTabs = [], logsDir, onExit, onPo
|
|
|
195
195
|
const statusContent = isCustomTab(currentTab) && currentTab.renderStatus
|
|
196
196
|
? currentTab.renderStatus()
|
|
197
197
|
: null;
|
|
198
|
-
return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [isProcessTab(currentTab) ? (_jsx(ProcessPane, { title: currentTab.name, output: outputs[activeTab] || [], port: ports[activeTab], status: statuses[activeTab] || "starting", onClear: handleClearOutput })) : isCustomTab(currentTab) ? (currentTab.render()) : null, _jsx(StatusLine, { activeTab: activeTab, tabs: allTabs.map((t) => t.name), rightContent: statusContent })
|
|
198
|
+
return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [isProcessTab(currentTab) ? (_jsx(ProcessPane, { title: currentTab.name, output: outputs[activeTab] || [], port: ports[activeTab], status: statuses[activeTab] || "starting", onClear: handleClearOutput })) : isCustomTab(currentTab) ? (currentTab.render()) : null, _jsx(StatusLine, { activeTab: activeTab, tabs: allTabs.map((t) => t.name), rightContent: statusContent })
|
|
199
|
+
] }));
|
|
199
200
|
}
|