@truss-harness/tui 0.1.2 → 0.1.4
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/README.md +12 -0
- package/dist/bin.js +72 -26
- package/dist/theme.d.ts +22 -0
- package/dist/theme.js +57 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -17,6 +17,18 @@ On Windows, use PowerShell or Windows Terminal. When launching from Git Bash, Tr
|
|
|
17
17
|
|
|
18
18
|
The TUI reads the same `.truss-harness/config.json` profiles as `@truss-harness/cli`, or lets you select a detected Ollama, LM Studio, llama.cpp, or custom compatible endpoint at startup.
|
|
19
19
|
|
|
20
|
+
## Themes
|
|
21
|
+
|
|
22
|
+
The default `forest` theme uses restrained green, sage, and teal accents with soft syntax colors. Choose `sage` for a lighter green palette or `dusk` for a quieter deep-green palette. Set a persistent preference in the shared configuration file or use `TRUSS_HARNESS_TUI_THEME`:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"tuiTheme": "forest"
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Inside the TUI, open settings with `m`, Tab to **Theme**, and use Left or Right to preview a preset for the current session.
|
|
31
|
+
|
|
20
32
|
Configured MCP stdio servers are connected when an Agent session starts, and their connection status appears in the Terminal pane. Plan mode loads only servers explicitly marked `readOnly`. Run `truss-cli config path` to locate the shared user and workspace configuration files.
|
|
21
33
|
|
|
22
34
|
## Controls
|
package/dist/bin.js
CHANGED
|
@@ -9,23 +9,34 @@ import { promisify } from "node:util";
|
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { Box, render, Text, useApp, useInput } from "ink";
|
|
11
11
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
12
|
-
import { detectLocalEndpoints, listLocalModels } from "@truss-harness/provider-openai-compatible";
|
|
12
|
+
import { cloudProviderDefinition, detectLocalEndpoints, isCloudProviderId, isLocalEndpointKind, listLocalModels } from "@truss-harness/provider-openai-compatible";
|
|
13
13
|
import { brand } from "@truss-harness/branding";
|
|
14
14
|
import { resolveConfiguration } from "@truss-harness/cli/config";
|
|
15
15
|
import { createClientRuntime } from "@truss-harness/cli/runtime";
|
|
16
16
|
import { executeWorkspaceCommand, FileWorkspacePlanStore, workspaceCommandHelp } from "@truss-harness/runtime";
|
|
17
17
|
import { buildFileTree, fuzzyFiles, syntaxTokens, wrapSyntaxTokens } from "./file-browser.js";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
import { tuiTheme, tuiThemeNames } from "./theme.js";
|
|
19
|
+
const tuiHelp = `${brand.productName} TUI
|
|
20
|
+
|
|
21
|
+
Full-screen terminal workspace for local coding models.
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
${brand.tuiCommand}
|
|
25
|
+
|
|
26
|
+
The TUI starts in the current workspace and reads the same configuration
|
|
27
|
+
profiles as ${brand.cliCommand}. Press ? inside the TUI for its complete
|
|
28
|
+
keyboard-control reference.
|
|
28
29
|
`;
|
|
30
|
+
const tuiControlHelp = [
|
|
31
|
+
["NAVIGATION", "Tab / Shift+Tab move focus; Ctrl+Left / Ctrl+Right move to an adjacent pane."],
|
|
32
|
+
["FILES", "/ opens fuzzy file search; Up/Down select; Left/Right collapse or expand; Enter opens."],
|
|
33
|
+
["EDITOR", "Up/Down scroll; d toggles Git diff; o opens the detected preview URL externally."],
|
|
34
|
+
["AGENT", "Enter sends; Up/Down scroll chat; n starts a new conversation outside the chat input."],
|
|
35
|
+
["TERMINAL", "Type directly and press Enter; Up/Down scroll output; slash commands run through Truss."],
|
|
36
|
+
["SETTINGS", "m opens settings outside chat; Tab changes fields; Theme previews forest, sage, and dusk."],
|
|
37
|
+
["APPROVALS", "Y or Enter allows; N or Escape denies."],
|
|
38
|
+
["CANCEL / EXIT", "Escape cancels an active agent run. Ctrl+C stops a process or run, and exits while idle."]
|
|
39
|
+
];
|
|
29
40
|
if (process.argv.slice(2).some((argument) => argument === "--help" || argument === "-h")) {
|
|
30
41
|
process.stdout.write(tuiHelp);
|
|
31
42
|
process.exit(0);
|
|
@@ -82,9 +93,31 @@ function wrapText(value, width) {
|
|
|
82
93
|
}
|
|
83
94
|
return result;
|
|
84
95
|
}
|
|
96
|
+
function plainChatText(value) {
|
|
97
|
+
let inCodeBlock = false;
|
|
98
|
+
return value.split(/\r?\n/).flatMap((sourceLine) => {
|
|
99
|
+
if (/^\s*```/.test(sourceLine)) {
|
|
100
|
+
inCodeBlock = !inCodeBlock;
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
if (inCodeBlock)
|
|
104
|
+
return [` ${sourceLine}`];
|
|
105
|
+
const line = sourceLine
|
|
106
|
+
.replace(/^\s{0,3}#{1,6}\s+/, "")
|
|
107
|
+
.replace(/^\s*>\s?/, "")
|
|
108
|
+
.replace(/^\s*[-*+]\s+/, "- ")
|
|
109
|
+
.replace(/^\s*\d+[.)]\s+/, "- ")
|
|
110
|
+
.replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
111
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
112
|
+
.replace(/(\*\*|__|\*|_|~~)/g, "");
|
|
113
|
+
return /^\s*[-:|]+\s*$/.test(line) ? [] : [line];
|
|
114
|
+
}).join("\n");
|
|
115
|
+
}
|
|
85
116
|
function chatDisplayLines(messages, busy, width) {
|
|
86
117
|
const transcript = messages.flatMap((message) => {
|
|
87
|
-
const content = message.content
|
|
118
|
+
const content = message.content
|
|
119
|
+
? message.role === "assistant" ? plainChatText(message.content) : message.content
|
|
120
|
+
: busy && message.role === "assistant" ? "Thinking..." : "";
|
|
88
121
|
return [
|
|
89
122
|
{ role: message.role, text: message.role === "user" ? "YOU" : "AGENT", header: true },
|
|
90
123
|
...wrapText(content, width).map((text) => ({ role: message.role, text, header: false }))
|
|
@@ -157,8 +190,8 @@ async function stopProcessTree(child) {
|
|
|
157
190
|
}
|
|
158
191
|
child.kill();
|
|
159
192
|
}
|
|
160
|
-
function Panel({ title, active, children }) {
|
|
161
|
-
return _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: active ?
|
|
193
|
+
function Panel({ title, active, theme, children }) {
|
|
194
|
+
return _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: active ? theme.focus : theme.panel, paddingX: 1, width: "100%", height: "100%", overflow: "hidden", children: [_jsx(Text, { color: active ? theme.focus : theme.muted, bold: true, children: title }), children] });
|
|
162
195
|
}
|
|
163
196
|
function App({ initialConfiguration }) {
|
|
164
197
|
const { exit } = useApp();
|
|
@@ -196,6 +229,7 @@ function App({ initialConfiguration }) {
|
|
|
196
229
|
const [models, setModels] = useState([]);
|
|
197
230
|
const [modelIndex, setModelIndex] = useState(0);
|
|
198
231
|
const [settingsField, setSettingsField] = useState("server");
|
|
232
|
+
const [themeName, setThemeName] = useState(initialConfiguration?.tuiTheme ?? "forest");
|
|
199
233
|
const [endpointInput, setEndpointInput] = useState(initialConfiguration?.baseUrl ?? "http://127.0.0.1:11434");
|
|
200
234
|
const [modelInput, setModelInput] = useState(initialConfiguration?.model ?? "");
|
|
201
235
|
const [providerKind, setProviderKind] = useState(initialConfiguration?.provider ?? "ollama");
|
|
@@ -210,6 +244,7 @@ function App({ initialConfiguration }) {
|
|
|
210
244
|
const tokensPerSecond = streamMetrics.startedAt && streamMetrics.generatedTokens
|
|
211
245
|
? streamMetrics.generatedTokens / Math.max((Date.now() - streamMetrics.startedAt) / 1_000, 0.1)
|
|
212
246
|
: undefined;
|
|
247
|
+
const theme = tuiTheme(themeName);
|
|
213
248
|
const appendTerminal = (output) => {
|
|
214
249
|
const next = output.replace(/\r/g, "").split("\n").filter(Boolean);
|
|
215
250
|
if (!next.length)
|
|
@@ -292,7 +327,7 @@ function App({ initialConfiguration }) {
|
|
|
292
327
|
return () => { process.stdout.off("resize", refreshViewport); };
|
|
293
328
|
}, []);
|
|
294
329
|
useEffect(() => {
|
|
295
|
-
if (!selectedEndpoint || !endpointInput)
|
|
330
|
+
if (!selectedEndpoint || !endpointInput || !isLocalEndpointKind(providerKind))
|
|
296
331
|
return;
|
|
297
332
|
const endpoint = { ...selectedEndpoint, kind: providerKind, baseUrl: endpointInput };
|
|
298
333
|
void listLocalModels(endpoint).then((available) => {
|
|
@@ -409,7 +444,9 @@ function App({ initialConfiguration }) {
|
|
|
409
444
|
provider: providerKind,
|
|
410
445
|
baseUrl: endpointInput.trim(),
|
|
411
446
|
model: modelInput.trim(),
|
|
412
|
-
apiKey:
|
|
447
|
+
apiKey: initialConfiguration?.provider === providerKind
|
|
448
|
+
? initialConfiguration.apiKey
|
|
449
|
+
: process.env.TRUSS_HARNESS_API_KEY ?? (isCloudProviderId(providerKind) ? process.env[cloudProviderDefinition(providerKind).apiKeyEnvironmentVariable] : undefined),
|
|
413
450
|
systemPrompt: process.env.TRUSS_HARNESS_SYSTEM_PROMPT,
|
|
414
451
|
mode: agentMode,
|
|
415
452
|
internetAccess,
|
|
@@ -610,7 +647,7 @@ function App({ initialConfiguration }) {
|
|
|
610
647
|
return;
|
|
611
648
|
}
|
|
612
649
|
if (key.tab) {
|
|
613
|
-
const fields = ["server", "endpoint", "model", "internet"];
|
|
650
|
+
const fields = ["server", "endpoint", "model", "internet", "theme"];
|
|
614
651
|
setSettingsField((current) => {
|
|
615
652
|
const index = fields.indexOf(current);
|
|
616
653
|
return fields[(index + (key.shift ? -1 : 1) + fields.length) % fields.length];
|
|
@@ -644,6 +681,15 @@ function App({ initialConfiguration }) {
|
|
|
644
681
|
void configureRuntime();
|
|
645
682
|
return;
|
|
646
683
|
}
|
|
684
|
+
if (settingsField === "theme") {
|
|
685
|
+
if (key.leftArrow || key.upArrow)
|
|
686
|
+
setThemeName((current) => tuiThemeNames[(tuiThemeNames.indexOf(current) - 1 + tuiThemeNames.length) % tuiThemeNames.length]);
|
|
687
|
+
if (key.rightArrow || key.downArrow || input === " ")
|
|
688
|
+
setThemeName((current) => tuiThemeNames[(tuiThemeNames.indexOf(current) + 1) % tuiThemeNames.length]);
|
|
689
|
+
if (key.return && endpointInput && modelInput)
|
|
690
|
+
void configureRuntime();
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
647
693
|
if (key.return && endpointInput && modelInput) {
|
|
648
694
|
void configureRuntime();
|
|
649
695
|
return;
|
|
@@ -764,23 +810,23 @@ function App({ initialConfiguration }) {
|
|
|
764
810
|
else if (input)
|
|
765
811
|
setChatInput((current) => current + input);
|
|
766
812
|
});
|
|
767
|
-
const filesPanel = _jsxs(Panel, { title: "FILES [/] find", active: focus === "files", children: [visibleFileTree.map((entry, visibleIndex) => {
|
|
813
|
+
const filesPanel = _jsxs(Panel, { title: "FILES [/] find", active: focus === "files", theme: theme, children: [visibleFileTree.map((entry, visibleIndex) => {
|
|
768
814
|
const selected = fileTreeStart + visibleIndex === fileIndex;
|
|
769
815
|
const marker = entry.kind === "directory" ? (entry.expanded ? "v " : "> ") : " ";
|
|
770
816
|
const indentation = " ".repeat(entry.depth * 2);
|
|
771
817
|
const width = Math.max(6, filesWidth - indentation.length - 7);
|
|
772
|
-
return _jsxs(Text, { wrap: "truncate-end", color: selected ?
|
|
773
|
-
}), !fileTree.length && _jsx(Text, { color:
|
|
774
|
-
const editorPanel = _jsx(Panel, { title: `${editorTitle} [up/down] scroll [d] diff${previewUrl ? " [o] browser" : ""}`, active: focus === "editor", children: visibleEditorRows.map((row, index) => {
|
|
818
|
+
return _jsxs(Text, { wrap: "truncate-end", color: selected ? theme.focus : entry.kind === "directory" ? theme.directory : entry.path === openFilePath ? theme.success : theme.text, bold: selected || entry.kind === "directory", children: [selected ? "> " : " ", indentation, marker, truncate(entry.name, width)] }, `${entry.kind}:${entry.path}`);
|
|
819
|
+
}), !fileTree.length && _jsx(Text, { color: theme.muted, children: "No workspace files found." })] });
|
|
820
|
+
const editorPanel = _jsx(Panel, { title: `${editorTitle} [up/down] scroll [d] diff${previewUrl ? " [o] browser" : ""}`, active: focus === "editor", theme: theme, children: visibleEditorRows.map((row, index) => {
|
|
775
821
|
const lineLabel = row.continuation ? " |" : String(row.sourceLine).padStart(3);
|
|
776
|
-
return _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color:
|
|
822
|
+
return _jsxs(Text, { wrap: "truncate-end", color: theme.text, children: [_jsx(Text, { color: theme.muted, children: lineLabel }), " ", row.tokens.map((token, tokenIndex) => _jsx(Text, { color: token.color ? theme.syntax[token.color] : theme.text, dimColor: token.dim, children: token.text }, `${tokenIndex}-${token.text}`))] }, `${editorScroll + index}-${row.sourceLine}-${row.continuation}`);
|
|
777
823
|
}) });
|
|
778
|
-
const agentPanel = _jsxs(Panel, { title: chatScroll ? `AGENT [${chatScroll} lines above]` : "AGENT [Enter] send", active: focus === "chat", children: [activePlan && _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { bold: true, color:
|
|
779
|
-
return _jsxs(Box, { flexDirection: "column", height: viewport.rows, paddingX: 1, overflow: "hidden", children: [_jsxs(Box, { height: 1, justifyContent: "space-between", marginBottom: 1, children: [_jsx(Text, { bold: true, color:
|
|
824
|
+
const agentPanel = _jsxs(Panel, { title: chatScroll ? `AGENT [${chatScroll} lines above]` : "AGENT [Enter] send", active: focus === "chat", theme: theme, children: [activePlan && _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { bold: true, color: theme.accent, wrap: "truncate-end", children: ["PLAN: ", truncate(activePlan.title, Math.max(8, chatWidth - 9))] }), activePlan.steps.slice(0, 3).map((step) => _jsxs(Text, { wrap: "truncate-end", color: step.status === "completed" ? theme.success : step.status === "in_progress" ? theme.warning : theme.muted, children: [step.status === "completed" ? "[x]" : step.status === "in_progress" ? "[..]" : "[ ]", " ", truncate(step.content, Math.max(8, chatWidth - 7))] }, step.id))] }), chatLines.map((line, index) => _jsx(Text, { color: line.role === "user" ? theme.user : theme.agent, bold: line.header, children: line.text }, `${line.role}-${line.header}-${index}-${line.text}`)), _jsx(Box, { flexGrow: 1 }), _jsxs(Text, { wrap: "truncate-end", color: focus === "chat" ? theme.focus : theme.muted, children: [focus === "chat" ? "> " : " ", truncate(chatInput || (busy ? "Working... Escape cancels" : "Ask about this workspace"), Math.max(12, chatWidth - 5))] })] });
|
|
825
|
+
return _jsxs(Box, { flexDirection: "column", height: viewport.rows, paddingX: 1, overflow: "hidden", children: [_jsxs(Box, { height: 1, justifyContent: "space-between", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.accent, children: brand.productName.toUpperCase() }), _jsx(Text, { color: theme.muted, children: configuration ? `${configuration.provider} / ${configuration.model}` : "No model selected" }), _jsx(Text, { color: runStatus === "waiting" ? theme.warning : busy ? theme.focus : theme.success, children: runStatus === "waiting" ? "APPROVAL" : runStatus === "tool" ? "TOOL" : busy ? "WORKING" : "READY" })] }), _jsxs(Box, { height: 1, justifyContent: "space-between", marginBottom: 1, children: [_jsxs(Text, { color: theme.muted, children: ["CONTEXT ", _jsxs(Text, { color: contextTokens / contextWindow >= 0.9 ? theme.error : contextTokens / contextWindow >= 0.7 ? theme.warning : theme.accent, children: [formatTokenCount(contextTokens), " / ", formatTokenCount(contextWindow)] }), " estimated"] }), _jsxs(Text, { color: theme.muted, children: ["SPEED ", _jsx(Text, { color: busy ? theme.success : theme.muted, children: tokensPerSecond ? `${tokensPerSecond.toFixed(1)} tok/s` : "-- tok/s" }), previewUrl ? ` PREVIEW ${truncate(previewUrl, Math.max(16, Math.floor(viewport.columns * 0.3)))}` : ""] })] }), compactLayout ? _jsxs(Box, { height: workspaceHeight, flexDirection: "column", gap: 1, overflow: "hidden", children: [_jsxs(Box, { height: editorHeight, flexDirection: "row", gap: 1, overflow: "hidden", children: [_jsx(Box, { width: filesWidth, height: "100%", children: filesPanel }), _jsx(Box, { width: editorWidth, height: "100%", children: editorPanel })] }), _jsx(Box, { height: compactChatHeight, width: "100%", children: agentPanel })] }) : _jsxs(Box, { height: workspaceHeight, flexDirection: "row", gap: 1, overflow: "hidden", children: [_jsx(Box, { width: filesWidth, height: "100%", children: filesPanel }), _jsx(Box, { width: editorWidth, height: "100%", children: editorPanel }), _jsx(Box, { width: chatWidth, height: "100%", children: agentPanel })] }), _jsx(Box, { height: terminalHeight, marginTop: 1, children: _jsxs(Panel, { title: "TERMINAL [Enter] run", active: focus === "terminal", theme: theme, children: [visibleTerminalLines.map((line, index) => _jsx(Text, { wrap: "truncate-end", color: line.startsWith("[agent error]") || line.startsWith("[terminal error]") ? theme.error : line.startsWith("[tool") ? theme.warning : theme.muted, children: truncate(line, Math.max(20, viewport.columns - 8)) }, `${index}-${line}`)), _jsx(Box, { flexGrow: 1 }), _jsxs(Text, { wrap: "truncate-end", color: focus === "terminal" ? theme.focus : theme.muted, children: [focus === "terminal" ? "> " : " ", truncate(commandInput || "Type a workspace command", Math.max(16, viewport.columns - 10))] })] }) }), _jsxs(Box, { height: 1, marginTop: 1, justifyContent: "space-between", children: [_jsx(Text, { color: theme.muted, wrap: "truncate-end", children: "Tab focus | Ctrl+Left/Right pane | / find | m settings | Esc cancel run | Ctrl+C exit idle" }), _jsx(Text, { color: theme.muted, children: sessionId ? `session ${sessionId.slice(0, 8)}` : "new session" })] }), screen === "file-search" && _jsxs(Box, { position: "absolute", flexDirection: "column", borderStyle: "double", borderColor: theme.focus, paddingX: 2, paddingY: 1, width: overlayWidth, marginLeft: 2, marginTop: 3, backgroundColor: theme.overlay, children: [_jsx(Text, { bold: true, color: theme.accent, children: "FIND FILE" }), _jsxs(Text, { color: theme.text, children: [_jsx(Text, { color: theme.focus, children: "> " }), fileSearchInput || _jsx(Text, { color: theme.muted, children: "Type part of a file name or path" })] }), _jsx(Text, { color: theme.muted, children: "Up/Down select Enter open Escape close" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [fileSearchResults.map((entry, index) => {
|
|
780
826
|
const selected = index === Math.min(fileSearchIndex, Math.max(0, fileSearchResults.length - 1));
|
|
781
827
|
const fileName = entry.path.split("/").at(-1) ?? entry.path;
|
|
782
|
-
return _jsxs(Text, { color: selected ?
|
|
783
|
-
}), !fileSearchResults.length && _jsx(Text, { color:
|
|
828
|
+
return _jsxs(Text, { color: selected ? theme.focus : theme.text, bold: selected, children: [selected ? "> " : " ", fileName, _jsxs(Text, { color: theme.muted, children: [" ", truncate(entry.path, Math.max(12, overlayWidth - fileName.length - 10))] })] }, entry.path);
|
|
829
|
+
}), !fileSearchResults.length && _jsx(Text, { color: theme.warning, children: "No matching files." })] })] }), screen === "settings" && _jsxs(Box, { position: "absolute", flexDirection: "column", borderStyle: "double", borderColor: theme.focus, paddingX: 2, paddingY: 1, width: overlayWidth, marginLeft: 2, marginTop: 3, backgroundColor: theme.overlay, children: [_jsx(Text, { bold: true, color: theme.accent, children: "LOCAL MODEL CONFIGURATION" }), _jsx(Text, { color: theme.muted, children: "Tab changes fields. Enter selects an item or saves when endpoint and model are set." }), _jsxs(Text, { color: settingsField === "server" ? theme.focus : theme.text, children: ["SERVER: ", selectedEndpoint?.label ?? "Scanning local servers..."] }), _jsxs(Text, { color: settingsField === "endpoint" ? theme.focus : theme.text, children: ["ENDPOINT: ", endpointInput] }), _jsxs(Text, { color: settingsField === "model" ? theme.focus : theme.text, children: ["MODEL: ", modelInput || models[modelIndex] || "Choose or type a model"] }), _jsxs(Text, { color: settingsField === "internet" ? theme.focus : theme.text, children: ["INTERNET RESEARCH: ", internetAccess ? "enabled" : "disabled", " ", settingsField === "internet" ? "[Space toggles]" : ""] }), _jsxs(Text, { color: settingsField === "theme" ? theme.focus : theme.text, children: ["THEME: ", themeName, " ", settingsField === "theme" ? "[Left/Right changes]" : ""] }), _jsxs(Text, { color: theme.muted, children: ["Detected models: ", models.slice(0, 5).join(", ") || "none; type one manually"] }), _jsxs(Text, { color: theme.warning, children: ["Provider: ", providerKind, ". Local endpoints use the server selector. Configure cloud BYOK profiles with truss-cli setup, then relaunch the TUI."] })] }), screen === "approval" && _jsxs(Box, { position: "absolute", flexDirection: "column", borderStyle: "double", borderColor: theme.warning, paddingX: 2, paddingY: 1, width: overlayWidth, marginLeft: 2, marginTop: 8, backgroundColor: theme.overlay, children: [_jsx(Text, { bold: true, color: theme.warning, children: "TOOL APPROVAL REQUIRED" }), _jsxs(Text, { color: theme.text, children: [pendingTool?.name, " ", JSON.stringify(pendingTool?.input)] }), _jsx(Text, { color: theme.muted, children: "Press Y or Enter to allow. Press N or Escape to deny." })] }), screen === "help" && _jsxs(Box, { position: "absolute", flexDirection: "column", borderStyle: "double", borderColor: theme.focus, paddingX: 2, paddingY: 1, width: overlayWidth, marginLeft: 2, marginTop: 7, backgroundColor: theme.overlay, children: [_jsx(Text, { bold: true, color: theme.accent, children: "TUI CONTROLS" }), tuiControlHelp.map(([label, detail]) => _jsxs(Text, { color: theme.text, wrap: "wrap", children: [_jsxs(Text, { bold: true, color: theme.focus, children: [label, ": "] }), detail] }, label)), _jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, color: theme.accent, children: "WORKSPACE COMMANDS" }) }), workspaceCommandHelp().split("\n").slice(1).map((line) => _jsx(Text, { color: theme.text, children: line }, line)), _jsx(Text, { color: theme.muted, children: "Press any key to close this reference." })] })] });
|
|
784
830
|
}
|
|
785
831
|
const initialConfiguration = await resolveConfiguration({ workspaceRoot: cwd() }).catch((error) => {
|
|
786
832
|
const message = error instanceof Error ? error.message : String(error);
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SyntaxToken } from "./file-browser.js";
|
|
2
|
+
export declare const tuiThemeNames: readonly ["forest", "sage", "dusk"];
|
|
3
|
+
export type TuiThemeName = (typeof tuiThemeNames)[number];
|
|
4
|
+
export interface TuiTheme {
|
|
5
|
+
readonly name: TuiThemeName;
|
|
6
|
+
readonly panel: string;
|
|
7
|
+
readonly focus: string;
|
|
8
|
+
readonly accent: string;
|
|
9
|
+
readonly muted: string;
|
|
10
|
+
readonly text: string;
|
|
11
|
+
readonly directory: string;
|
|
12
|
+
readonly agent: string;
|
|
13
|
+
readonly user: string;
|
|
14
|
+
readonly success: string;
|
|
15
|
+
readonly warning: string;
|
|
16
|
+
readonly error: string;
|
|
17
|
+
readonly overlay: string;
|
|
18
|
+
readonly syntax: Readonly<Record<NonNullable<SyntaxToken["color"]>, string>>;
|
|
19
|
+
}
|
|
20
|
+
export declare const tuiThemes: Readonly<Record<TuiThemeName, TuiTheme>>;
|
|
21
|
+
export declare function isTuiThemeName(value: unknown): value is TuiThemeName;
|
|
22
|
+
export declare function tuiTheme(value: TuiThemeName | undefined): TuiTheme;
|
package/dist/theme.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export const tuiThemeNames = ["forest", "sage", "dusk"];
|
|
2
|
+
export const tuiThemes = {
|
|
3
|
+
forest: {
|
|
4
|
+
name: "forest",
|
|
5
|
+
panel: "#416b4c",
|
|
6
|
+
focus: "#7fc58a",
|
|
7
|
+
accent: "#a4dca0",
|
|
8
|
+
muted: "#7e9e81",
|
|
9
|
+
text: "#d8e6d7",
|
|
10
|
+
directory: "#8fba7e",
|
|
11
|
+
agent: "#b4d59a",
|
|
12
|
+
user: "#77bd8a",
|
|
13
|
+
success: "#91ca92",
|
|
14
|
+
warning: "#d3b978",
|
|
15
|
+
error: "#d79a8e",
|
|
16
|
+
overlay: "#13231d",
|
|
17
|
+
syntax: { blue: "#9eb4cf", cyan: "#8cc8bd", gray: "#71867d", green: "#a9c994", magenta: "#c4a8c8", red: "#d4958a", yellow: "#d6c184" }
|
|
18
|
+
},
|
|
19
|
+
sage: {
|
|
20
|
+
name: "sage",
|
|
21
|
+
panel: "#5d7666",
|
|
22
|
+
focus: "#a5c6ab",
|
|
23
|
+
accent: "#b3d5ba",
|
|
24
|
+
muted: "#8e9f91",
|
|
25
|
+
text: "#dce5dd",
|
|
26
|
+
directory: "#9bbcab",
|
|
27
|
+
agent: "#bad1a0",
|
|
28
|
+
user: "#8ab9b0",
|
|
29
|
+
success: "#9dca9c",
|
|
30
|
+
warning: "#d6ba83",
|
|
31
|
+
error: "#d69b91",
|
|
32
|
+
overlay: "#1a241d",
|
|
33
|
+
syntax: { blue: "#9eafcf", cyan: "#89c0b4", gray: "#7d8d80", green: "#afcc9a", magenta: "#c5aac4", red: "#d5958d", yellow: "#d7c183" }
|
|
34
|
+
},
|
|
35
|
+
dusk: {
|
|
36
|
+
name: "dusk",
|
|
37
|
+
panel: "#3d665d",
|
|
38
|
+
focus: "#78aa95",
|
|
39
|
+
accent: "#8cc2aa",
|
|
40
|
+
muted: "#728a82",
|
|
41
|
+
text: "#d1dfda",
|
|
42
|
+
directory: "#7eaaa0",
|
|
43
|
+
agent: "#a7c38e",
|
|
44
|
+
user: "#7cadba",
|
|
45
|
+
success: "#83bb91",
|
|
46
|
+
warning: "#ceb776",
|
|
47
|
+
error: "#d29289",
|
|
48
|
+
overlay: "#101d1c",
|
|
49
|
+
syntax: { blue: "#93aaca", cyan: "#7eb9ae", gray: "#687d76", green: "#9dbf8c", magenta: "#bda2c3", red: "#cf9188", yellow: "#d0ba80" }
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
export function isTuiThemeName(value) {
|
|
53
|
+
return typeof value === "string" && tuiThemeNames.includes(value);
|
|
54
|
+
}
|
|
55
|
+
export function tuiTheme(value) {
|
|
56
|
+
return tuiThemes[value ?? "forest"];
|
|
57
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truss-harness/tui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Interactive terminal client for Truss.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"ink": "^6.8.0",
|
|
44
44
|
"react": "^19.2.0",
|
|
45
45
|
"@truss-harness/branding": "0.1.0",
|
|
46
|
-
"@truss-harness/cli": "0.1.
|
|
47
|
-
"@truss-harness/provider-openai-compatible": "0.1.
|
|
48
|
-
"@truss-harness/runtime": "0.1.
|
|
46
|
+
"@truss-harness/cli": "0.1.4",
|
|
47
|
+
"@truss-harness/provider-openai-compatible": "0.1.1",
|
|
48
|
+
"@truss-harness/runtime": "0.1.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/react": "^19.2.0"
|