@truss-harness/tui 0.1.8 → 0.1.13
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/LICENSE +233 -21
- package/README.md +15 -1
- package/dist/bin.js +357 -84
- package/package.json +5 -5
package/LICENSE
CHANGED
|
@@ -1,21 +1,233 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Truss contributors
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
Truss Collaborative Source License, Version 1.0
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jared Hooker and Truss contributors
|
|
4
|
+
|
|
5
|
+
Required Notice: This software is based on Truss.
|
|
6
|
+
Required Notice: Truss original project: https://github.com/truss-agent/truss-harness
|
|
7
|
+
|
|
8
|
+
This is a source-available license. It is not an Open Source Initiative
|
|
9
|
+
approved open-source license.
|
|
10
|
+
|
|
11
|
+
1. Acceptance
|
|
12
|
+
|
|
13
|
+
To receive any license under these terms, you must accept them as strict
|
|
14
|
+
obligations and as conditions of every license granted below. If you do not
|
|
15
|
+
accept these terms, you receive no license.
|
|
16
|
+
|
|
17
|
+
2. Definitions
|
|
18
|
+
|
|
19
|
+
"Software" means the Truss source code, object code, documentation, artwork,
|
|
20
|
+
and other material distributed with these terms, excluding Third-Party
|
|
21
|
+
Components identified below.
|
|
22
|
+
|
|
23
|
+
"Licensor" means each person or entity that owns copyright or patent rights in
|
|
24
|
+
the Software and has authority to license those rights.
|
|
25
|
+
|
|
26
|
+
"Official Project" means the Truss project maintained at
|
|
27
|
+
https://github.com/truss-agent/truss-harness and its official releases.
|
|
28
|
+
|
|
29
|
+
"Project Steward" means Jared Hooker or a successor publicly identified as the
|
|
30
|
+
project owner in the Official Project repository.
|
|
31
|
+
|
|
32
|
+
"You" means the individual or legal entity exercising rights under these
|
|
33
|
+
terms. For a legal entity, You includes entities that control, are controlled
|
|
34
|
+
by, or are under common control with that entity.
|
|
35
|
+
|
|
36
|
+
"Commercial Purpose" means any purpose intended to provide, or that does
|
|
37
|
+
provide, direct or indirect commercial advantage or monetary compensation to
|
|
38
|
+
You or another person. It includes internal business use by a for-profit
|
|
39
|
+
entity; use in developing, operating, or supporting a revenue-generating
|
|
40
|
+
product or service; sale, licensing, subscription, hosting, paid support,
|
|
41
|
+
consulting, advertising, or other monetization; and use intended to reduce the
|
|
42
|
+
cost of a commercial product or service.
|
|
43
|
+
|
|
44
|
+
"Competing Product" means any product or service, whether paid or free, that
|
|
45
|
+
provides the same or substantially similar functionality as the Software or an
|
|
46
|
+
official Truss product, or that is marketed or made available as a substitute
|
|
47
|
+
for any of them. Products and services may compete even when they use a
|
|
48
|
+
different interface, platform, programming language, architecture, or delivery
|
|
49
|
+
model. Coding-agent runtimes, harnesses, clients, development tools, model
|
|
50
|
+
routing systems, agent orchestration systems, and hosted versions of any of
|
|
51
|
+
those may be Competing Products.
|
|
52
|
+
|
|
53
|
+
"Contribution Activity" means reproducing, modifying, running, and sharing the
|
|
54
|
+
Software only as reasonably necessary to prepare, test, review, or submit a
|
|
55
|
+
good-faith contribution to the Official Project.
|
|
56
|
+
|
|
57
|
+
"Distribute" means to provide any copy of the Software or a work based on the
|
|
58
|
+
Software to another person or entity, including by publishing, transferring,
|
|
59
|
+
sublicensing, offering as a network service, or making it available for
|
|
60
|
+
download.
|
|
61
|
+
|
|
62
|
+
"Corresponding Source" means the preferred form for making modifications,
|
|
63
|
+
including build scripts and other material reasonably necessary to build and
|
|
64
|
+
run the distributed version, but excluding generally available unmodified
|
|
65
|
+
tools and dependencies.
|
|
66
|
+
|
|
67
|
+
"Third-Party Components" means material that is identified as being governed
|
|
68
|
+
by separate license terms. Those separate terms continue to control that
|
|
69
|
+
material.
|
|
70
|
+
|
|
71
|
+
3. Copyright License
|
|
72
|
+
|
|
73
|
+
Subject to these terms, each Licensor grants You a worldwide, royalty-free,
|
|
74
|
+
non-exclusive, non-transferable, non-sublicensable copyright license to use,
|
|
75
|
+
reproduce, modify, and create works based on the Software solely for:
|
|
76
|
+
|
|
77
|
+
a. a purpose that is both noncommercial and does not provide or support a
|
|
78
|
+
Competing Product; or
|
|
79
|
+
|
|
80
|
+
b. Contribution Activity.
|
|
81
|
+
|
|
82
|
+
No permission is granted for a Commercial Purpose or a Competing Product
|
|
83
|
+
except under Section 8.
|
|
84
|
+
|
|
85
|
+
4. Distribution License and Conditions
|
|
86
|
+
|
|
87
|
+
Subject to these terms, each Licensor grants You a worldwide, royalty-free,
|
|
88
|
+
non-exclusive, non-transferable, non-sublicensable license to Distribute the
|
|
89
|
+
Software, with or without modifications, only for a purpose permitted by
|
|
90
|
+
Section 3 and when You satisfy every condition below. You may not market,
|
|
91
|
+
direct, or knowingly provide a distribution for a Commercial Purpose or a
|
|
92
|
+
Competing Product. A public fork maintained in good faith for Contribution
|
|
93
|
+
Activity is permitted.
|
|
94
|
+
|
|
95
|
+
a. You must provide the complete Corresponding Source at no charge with the
|
|
96
|
+
distribution or through a durable, publicly accessible location clearly
|
|
97
|
+
identified in the distribution.
|
|
98
|
+
|
|
99
|
+
b. You must license the Software and every work based on the Software under
|
|
100
|
+
this exact license. You may not offer additional or conflicting terms
|
|
101
|
+
that restrict or expand the rights granted by this license.
|
|
102
|
+
|
|
103
|
+
c. You must include an unmodified copy of this license and preserve every
|
|
104
|
+
copyright, attribution, and Required Notice supplied with the Software.
|
|
105
|
+
|
|
106
|
+
d. The primary README or equivalent documentation must prominently state:
|
|
107
|
+
"Based on Truss (https://github.com/truss-agent/truss-harness)."
|
|
108
|
+
|
|
109
|
+
e. A program with a graphical or command-line user interface must display
|
|
110
|
+
the statement in Section 4(d) in a reasonably accessible About, Credits,
|
|
111
|
+
Help, or version view. A library or service without such an interface
|
|
112
|
+
must display it in its primary documentation and package metadata where
|
|
113
|
+
that metadata format supports attribution.
|
|
114
|
+
|
|
115
|
+
f. You must identify significant modifications and must not imply that a
|
|
116
|
+
modified version is an official Truss release or is endorsed by the
|
|
117
|
+
Official Project.
|
|
118
|
+
|
|
119
|
+
g. You may not remove, obscure, minimize, or bypass the notices required by
|
|
120
|
+
this section.
|
|
121
|
+
|
|
122
|
+
Merely creating a private clone does not require publication, but every notice
|
|
123
|
+
already present in that clone must remain intact. Distribution through a fork,
|
|
124
|
+
mirror, package, archive, installer, container, application, or network service
|
|
125
|
+
is subject to this section.
|
|
126
|
+
|
|
127
|
+
5. No Commercial Use
|
|
128
|
+
|
|
129
|
+
You may not use, reproduce, modify, create works based on, or Distribute the
|
|
130
|
+
Software for a Commercial Purpose without separate written permission under
|
|
131
|
+
Section 8. A commercial activity does not become noncommercial because the
|
|
132
|
+
Software is provided without a separate charge.
|
|
133
|
+
|
|
134
|
+
6. No Competitive Use
|
|
135
|
+
|
|
136
|
+
You may not use, reproduce, modify, create works based on, or Distribute the
|
|
137
|
+
Software to build, provide, maintain, market, fund, or support a Competing
|
|
138
|
+
Product without separate written permission under Section 8. This restriction
|
|
139
|
+
applies even if the Competing Product is free, source-available, internal,
|
|
140
|
+
nonprofit, or not yet publicly released.
|
|
141
|
+
|
|
142
|
+
7. Contributions Are Welcome
|
|
143
|
+
|
|
144
|
+
You may perform Contribution Activity even when You work for a commercial
|
|
145
|
+
organization, provided the activity is genuinely directed toward improving
|
|
146
|
+
the Official Project and is not used to develop or support a separate
|
|
147
|
+
Commercial Purpose or Competing Product.
|
|
148
|
+
|
|
149
|
+
If You intentionally submit a contribution for inclusion in the Official
|
|
150
|
+
Project, You retain ownership of Your contribution and grant the Project
|
|
151
|
+
Steward a perpetual, worldwide, irrevocable, non-exclusive, royalty-free
|
|
152
|
+
copyright license to use, reproduce, modify, prepare derivative works of,
|
|
153
|
+
publicly display, publicly perform, Distribute, sublicense, and relicense that
|
|
154
|
+
contribution as part of or in connection with Truss. Recipients receive rights
|
|
155
|
+
to the contribution only under the license that accompanies the Official
|
|
156
|
+
Project version they receive. You also grant the Project Steward a patent
|
|
157
|
+
license covering patent claims You can license that would necessarily be
|
|
158
|
+
infringed by the contribution alone or in combination with the Official
|
|
159
|
+
Project, with the right to sublicense that patent license to recipients under
|
|
160
|
+
the terms accompanying the Official Project.
|
|
161
|
+
|
|
162
|
+
You represent that You have the right to grant these permissions and that You
|
|
163
|
+
will identify any contribution governed by separate third-party terms.
|
|
164
|
+
|
|
165
|
+
8. Separate Commercial or Competitive License
|
|
166
|
+
|
|
167
|
+
The Project Steward or applicable Licensor may offer separate written terms
|
|
168
|
+
for a Commercial Purpose, a Competing Product, reduced attribution, or another
|
|
169
|
+
use not permitted here. Permission is effective only when stated in a separate
|
|
170
|
+
written agreement signed or expressly issued by the party authorized to grant
|
|
171
|
+
it. Payment, sponsorship, contribution, or prior communication alone does not
|
|
172
|
+
create permission.
|
|
173
|
+
|
|
174
|
+
9. Patents
|
|
175
|
+
|
|
176
|
+
Subject to these terms, each Licensor grants You a worldwide, royalty-free,
|
|
177
|
+
non-exclusive patent license under patent claims the Licensor can license that
|
|
178
|
+
You would necessarily infringe by exercising the copyright licenses granted
|
|
179
|
+
here. If You or Your organization makes a written claim that the Software
|
|
180
|
+
infringes or contributes to infringement of a patent, every patent license
|
|
181
|
+
granted to You under these terms ends immediately.
|
|
182
|
+
|
|
183
|
+
10. Trademarks
|
|
184
|
+
|
|
185
|
+
These terms do not grant permission to use the Truss name, logos, trade dress,
|
|
186
|
+
or other marks except as strictly necessary to provide the attribution
|
|
187
|
+
required by Section 4. You may not use any mark in a way that suggests
|
|
188
|
+
affiliation with or endorsement by the Official Project.
|
|
189
|
+
|
|
190
|
+
11. Third-Party Components
|
|
191
|
+
|
|
192
|
+
Third-Party Components remain governed by their own license terms. Nothing in
|
|
193
|
+
this license limits rights You receive directly under those terms, and nothing
|
|
194
|
+
in those terms expands Your rights in the Software.
|
|
195
|
+
|
|
196
|
+
12. Termination and Cure
|
|
197
|
+
|
|
198
|
+
Any license granted under these terms terminates automatically when You breach
|
|
199
|
+
them. If this is Your first breach and You cure it fully within 30 days after
|
|
200
|
+
receiving written notice from a Licensor, Your license is reinstated
|
|
201
|
+
retroactively. A later breach terminates Your license immediately and
|
|
202
|
+
permanently unless the applicable Licensor agrees otherwise in writing.
|
|
203
|
+
|
|
204
|
+
Sections that by their nature should survive termination, including Sections
|
|
205
|
+
7, 10, 13, 14, and 15, survive.
|
|
206
|
+
|
|
207
|
+
13. Disclaimer of Warranty
|
|
208
|
+
|
|
209
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS
|
|
210
|
+
OR IMPLIED, INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
211
|
+
PURPOSE, TITLE, AND NON-INFRINGEMENT. YOU BEAR ALL RISK ARISING FROM USE OR
|
|
212
|
+
DISTRIBUTION OF THE SOFTWARE.
|
|
213
|
+
|
|
214
|
+
14. Limitation of Liability
|
|
215
|
+
|
|
216
|
+
TO THE MAXIMUM EXTENT PERMITTED BY LAW, NO LICENSOR, PROJECT STEWARD, OR
|
|
217
|
+
CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
218
|
+
EXEMPLARY, CONSEQUENTIAL, OR OTHER DAMAGES, OR FOR LOST PROFITS, DATA,
|
|
219
|
+
GOODWILL, OR BUSINESS INTERRUPTION, ARISING FROM OR RELATED TO THE SOFTWARE OR
|
|
220
|
+
THESE TERMS, UNDER ANY LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY.
|
|
221
|
+
|
|
222
|
+
15. General Terms
|
|
223
|
+
|
|
224
|
+
If a provision of these terms is unenforceable, it will be modified only to
|
|
225
|
+
the minimum extent necessary to make it enforceable, and the remaining terms
|
|
226
|
+
will continue in effect. Failure to enforce a provision is not a waiver. These
|
|
227
|
+
terms do not limit rights You may have under applicable law without a license,
|
|
228
|
+
such as fair use.
|
|
229
|
+
|
|
230
|
+
This license applies only to Software versions distributed with it. Software
|
|
231
|
+
versions previously received under another license remain governed by the
|
|
232
|
+
license that accompanied those versions. No future license version applies
|
|
233
|
+
unless the Software is distributed with that version.
|
package/README.md
CHANGED
|
@@ -29,7 +29,12 @@ The default `forest` theme uses restrained green, sage, and teal accents with so
|
|
|
29
29
|
|
|
30
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
31
|
|
|
32
|
-
Configured MCP stdio servers are connected when an Agent session starts, and
|
|
32
|
+
Configured MCP stdio servers are connected when an Agent session starts, and
|
|
33
|
+
their connection status appears in the Terminal pane. Press `c` outside the
|
|
34
|
+
chat/terminal panes to inspect safe server and tool status, then `R` to test
|
|
35
|
+
the configured servers. Plan mode loads only servers explicitly marked
|
|
36
|
+
`readOnly`. Run `truss-cli config path` to locate the shared user and workspace
|
|
37
|
+
configuration files.
|
|
33
38
|
|
|
34
39
|
## Controls
|
|
35
40
|
|
|
@@ -41,6 +46,7 @@ Configured MCP stdio servers are connected when an Agent session starts, and the
|
|
|
41
46
|
| `Enter` | Send the chat input or open the selected file. |
|
|
42
47
|
| `Left` / `Right` | Collapse or expand the selected directory. |
|
|
43
48
|
| `m` | Open local model settings outside the chat pane. |
|
|
49
|
+
| `c` | Open credential-safe MCP server and tool status. |
|
|
44
50
|
| `d` | Toggle the selected file's Git diff. |
|
|
45
51
|
| Type directly | Enter a command while the terminal pane has focus. |
|
|
46
52
|
| `?` | Open workspace command help. |
|
|
@@ -48,3 +54,11 @@ Configured MCP stdio servers are connected when an Agent session starts, and the
|
|
|
48
54
|
| `Ctrl+C` | Stop generation, or exit while idle. |
|
|
49
55
|
|
|
50
56
|
Type `/init`, `/update [note]`, `/status`, `/clear-memory`, or `/help` into the Agent pane. They run locally without a model call. Approvals for model-initiated tools still respect the selected permission policy.
|
|
57
|
+
|
|
58
|
+
## License and contributions
|
|
59
|
+
|
|
60
|
+
This client is source-available under the [Truss Collaborative Source
|
|
61
|
+
License](LICENSE). Contributions to the official project are welcome. Every
|
|
62
|
+
copy, fork, or derivative must preserve the license and prominently state:
|
|
63
|
+
**Based on Truss (https://github.com/truss-agent/truss-harness).** Commercial
|
|
64
|
+
use and competing products or services require separate written permission.
|
package/dist/bin.js
CHANGED
|
@@ -9,13 +9,14 @@ 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 { cloudProviderDefinition, detectLocalEndpoints, isCloudProviderId, isLocalEndpointKind, 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
|
-
import { resolveConfiguration } from "@truss-harness/cli/config";
|
|
15
|
-
import { createClientRuntime } from "@truss-harness/cli/runtime";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
14
|
+
import { resolveConfiguration, } from "@truss-harness/cli/config";
|
|
15
|
+
import { createClientRuntime, } from "@truss-harness/cli/runtime";
|
|
16
|
+
import { FileAgentProfileStore } from "@truss-harness/cli/agents";
|
|
17
|
+
import { executeWorkspaceCommand, FileWorkspacePlanStore, workspaceCommandHelp, } from "@truss-harness/runtime";
|
|
18
|
+
import { buildFileTree, fuzzyFiles, syntaxTokens, wrapSyntaxTokens, } from "./file-browser.js";
|
|
19
|
+
import { tuiTheme, tuiThemeNames, } from "./theme.js";
|
|
19
20
|
const tuiHelp = `${brand.productName} TUI
|
|
20
21
|
|
|
21
22
|
Full-screen terminal workspace for local coding models.
|
|
@@ -28,16 +29,43 @@ profiles as ${brand.cliCommand}. Press ? inside the TUI for its complete
|
|
|
28
29
|
keyboard-control reference.
|
|
29
30
|
`;
|
|
30
31
|
const tuiControlHelp = [
|
|
31
|
-
[
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
[
|
|
36
|
-
|
|
32
|
+
[
|
|
33
|
+
"NAVIGATION",
|
|
34
|
+
"Tab / Shift+Tab move focus; Ctrl+Left / Ctrl+Right move to an adjacent pane.",
|
|
35
|
+
],
|
|
36
|
+
[
|
|
37
|
+
"FILES",
|
|
38
|
+
"/ opens fuzzy file search; Up/Down select; Left/Right collapse or expand; Enter opens.",
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
"EDITOR",
|
|
42
|
+
"Up/Down scroll; d toggles Git diff; o opens the detected preview URL externally.",
|
|
43
|
+
],
|
|
44
|
+
[
|
|
45
|
+
"AGENT",
|
|
46
|
+
"Enter sends; a opens saved multi-agent profiles; Up/Down scroll chat; n starts a new conversation outside the chat input.",
|
|
47
|
+
],
|
|
48
|
+
[
|
|
49
|
+
"MCP",
|
|
50
|
+
"c opens credential-safe server and tool status; R tests configured servers.",
|
|
51
|
+
],
|
|
52
|
+
[
|
|
53
|
+
"TERMINAL",
|
|
54
|
+
"Type directly and press Enter; Up/Down scroll output; slash commands run through Truss.",
|
|
55
|
+
],
|
|
56
|
+
[
|
|
57
|
+
"SETTINGS",
|
|
58
|
+
"m opens settings outside chat; Tab changes fields; Theme previews forest, sage, and dusk.",
|
|
59
|
+
],
|
|
37
60
|
["APPROVALS", "Y or Enter allows; N or Escape denies."],
|
|
38
|
-
[
|
|
61
|
+
[
|
|
62
|
+
"CANCEL / EXIT",
|
|
63
|
+
"Escape cancels an active agent run. Ctrl+C stops a process or run, and exits while idle.",
|
|
64
|
+
],
|
|
39
65
|
];
|
|
40
|
-
if (process.argv
|
|
66
|
+
if (process.argv
|
|
67
|
+
.slice(2)
|
|
68
|
+
.some((argument) => argument === "--help" || argument === "-h")) {
|
|
41
69
|
process.stdout.write(tuiHelp);
|
|
42
70
|
process.exit(0);
|
|
43
71
|
}
|
|
@@ -46,23 +74,43 @@ function ensureInteractiveTerminal() {
|
|
|
46
74
|
return;
|
|
47
75
|
// Git Bash exposes a pipe to Node for some console programs. winpty creates
|
|
48
76
|
// the Windows console bridge Ink needs for keyboard input.
|
|
49
|
-
if (process.platform === "win32" &&
|
|
50
|
-
|
|
77
|
+
if (process.platform === "win32" &&
|
|
78
|
+
process.env.MSYSTEM &&
|
|
79
|
+
process.env.TRUSS_TUI_WINPTY !== "1") {
|
|
80
|
+
const result = spawnSync("winpty", [
|
|
81
|
+
process.execPath,
|
|
82
|
+
fileURLToPath(import.meta.url),
|
|
83
|
+
...process.argv.slice(2),
|
|
84
|
+
], {
|
|
51
85
|
stdio: "inherit",
|
|
52
|
-
env: { ...process.env, TRUSS_TUI_WINPTY: "1" }
|
|
86
|
+
env: { ...process.env, TRUSS_TUI_WINPTY: "1" },
|
|
53
87
|
});
|
|
54
88
|
if (!result.error)
|
|
55
89
|
process.exit(result.status ?? 1);
|
|
56
90
|
}
|
|
57
|
-
process.stderr.write(brand.productName +
|
|
91
|
+
process.stderr.write(brand.productName +
|
|
92
|
+
" TUI needs an interactive terminal with raw keyboard input. Open PowerShell or Windows Terminal and run " +
|
|
93
|
+
brand.tuiCommand +
|
|
94
|
+
", or run winpty " +
|
|
95
|
+
brand.tuiCommand +
|
|
96
|
+
" from Git Bash.\n");
|
|
58
97
|
process.exit(1);
|
|
59
98
|
}
|
|
60
99
|
ensureInteractiveTerminal();
|
|
61
100
|
const execFile = promisify(execFileCallback);
|
|
62
|
-
const ignoredDirectories = new Set([
|
|
101
|
+
const ignoredDirectories = new Set([
|
|
102
|
+
".git",
|
|
103
|
+
".next",
|
|
104
|
+
".truss-harness",
|
|
105
|
+
"node_modules",
|
|
106
|
+
"dist",
|
|
107
|
+
"coverage",
|
|
108
|
+
]);
|
|
63
109
|
const focusOrder = ["files", "editor", "chat", "terminal"];
|
|
64
110
|
function truncate(value, length) {
|
|
65
|
-
return value.length <= length
|
|
111
|
+
return value.length <= length
|
|
112
|
+
? value
|
|
113
|
+
: `${value.slice(0, Math.max(0, length - 3))}...`;
|
|
66
114
|
}
|
|
67
115
|
function clamp(value, minimum, maximum) {
|
|
68
116
|
return Math.min(Math.max(value, minimum), Math.max(minimum, maximum));
|
|
@@ -95,7 +143,9 @@ function wrapText(value, width) {
|
|
|
95
143
|
}
|
|
96
144
|
function plainChatText(value) {
|
|
97
145
|
let inCodeBlock = false;
|
|
98
|
-
return value
|
|
146
|
+
return value
|
|
147
|
+
.split(/\r?\n/)
|
|
148
|
+
.flatMap((sourceLine) => {
|
|
99
149
|
if (/^\s*```/.test(sourceLine)) {
|
|
100
150
|
inCodeBlock = !inCodeBlock;
|
|
101
151
|
return [];
|
|
@@ -111,23 +161,38 @@ function plainChatText(value) {
|
|
|
111
161
|
.replace(/`([^`]+)`/g, "$1")
|
|
112
162
|
.replace(/(\*\*|__|\*|_|~~)/g, "");
|
|
113
163
|
return /^\s*[-:|]+\s*$/.test(line) ? [] : [line];
|
|
114
|
-
})
|
|
164
|
+
})
|
|
165
|
+
.join("\n");
|
|
115
166
|
}
|
|
116
167
|
function chatDisplayLines(messages, busy, width) {
|
|
117
168
|
const transcript = messages.flatMap((message) => {
|
|
118
169
|
const content = message.content
|
|
119
|
-
? message.role === "assistant"
|
|
120
|
-
|
|
170
|
+
? message.role === "assistant"
|
|
171
|
+
? plainChatText(message.content)
|
|
172
|
+
: message.content
|
|
173
|
+
: busy && message.role === "assistant"
|
|
174
|
+
? "Thinking..."
|
|
175
|
+
: "";
|
|
121
176
|
return [
|
|
122
|
-
{
|
|
123
|
-
|
|
177
|
+
{
|
|
178
|
+
role: message.role,
|
|
179
|
+
text: message.role === "user" ? "YOU" : "AGENT",
|
|
180
|
+
header: true,
|
|
181
|
+
},
|
|
182
|
+
...wrapText(content, width).map((text) => ({
|
|
183
|
+
role: message.role,
|
|
184
|
+
text,
|
|
185
|
+
header: false,
|
|
186
|
+
})),
|
|
124
187
|
];
|
|
125
188
|
});
|
|
126
189
|
return transcript;
|
|
127
190
|
}
|
|
128
191
|
function configuredContextWindow() {
|
|
129
192
|
const configured = Number.parseInt(process.env.TRUSS_HARNESS_CONTEXT_WINDOW ?? "8192", 10);
|
|
130
|
-
return Number.isFinite(configured)
|
|
193
|
+
return Number.isFinite(configured)
|
|
194
|
+
? Math.max(512, Math.min(1_000_000, configured))
|
|
195
|
+
: 8_192;
|
|
131
196
|
}
|
|
132
197
|
async function collectFiles(root, current = root, result = []) {
|
|
133
198
|
if (result.length >= 2_000)
|
|
@@ -144,7 +209,11 @@ async function collectFiles(root, current = root, result = []) {
|
|
|
144
209
|
}
|
|
145
210
|
async function runTrackedCommand(command, workspaceRoot, onOutput, onProcess) {
|
|
146
211
|
await new Promise((resolve, reject) => {
|
|
147
|
-
const child = spawn(command, {
|
|
212
|
+
const child = spawn(command, {
|
|
213
|
+
cwd: workspaceRoot,
|
|
214
|
+
shell: true,
|
|
215
|
+
windowsHide: true,
|
|
216
|
+
});
|
|
148
217
|
onProcess?.(child);
|
|
149
218
|
child.stdout.on("data", (data) => onOutput(data.toString()));
|
|
150
219
|
child.stderr.on("data", (data) => onOutput(data.toString()));
|
|
@@ -161,7 +230,9 @@ function detectedPreviewUrl(output) {
|
|
|
161
230
|
return match?.[0].replace(/[),.;]+$/, "").replace("0.0.0.0", "127.0.0.1");
|
|
162
231
|
}
|
|
163
232
|
function normalizedPreviewUrl(value) {
|
|
164
|
-
const normalized = /^[a-z][a-z\d+.-]*:\/\//i.test(value.trim())
|
|
233
|
+
const normalized = /^[a-z][a-z\d+.-]*:\/\//i.test(value.trim())
|
|
234
|
+
? value.trim()
|
|
235
|
+
: `http://${value.trim()}`;
|
|
165
236
|
const url = new URL(normalized);
|
|
166
237
|
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
167
238
|
throw new Error("Preview URLs must use HTTP or HTTPS.");
|
|
@@ -174,7 +245,11 @@ function openExternalPreview(value) {
|
|
|
174
245
|
: process.platform === "darwin"
|
|
175
246
|
? ["open", [url]]
|
|
176
247
|
: ["xdg-open", [url]];
|
|
177
|
-
const child = spawn(command, arguments_, {
|
|
248
|
+
const child = spawn(command, arguments_, {
|
|
249
|
+
detached: true,
|
|
250
|
+
stdio: "ignore",
|
|
251
|
+
windowsHide: true,
|
|
252
|
+
});
|
|
178
253
|
child.once("error", () => undefined);
|
|
179
254
|
child.unref();
|
|
180
255
|
}
|
|
@@ -190,15 +265,23 @@ async function stopProcessTree(child) {
|
|
|
190
265
|
}
|
|
191
266
|
child.kill();
|
|
192
267
|
}
|
|
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] });
|
|
268
|
+
function Panel({ title, active, theme, children, }) {
|
|
269
|
+
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] }));
|
|
195
270
|
}
|
|
196
|
-
function App({ initialConfiguration }) {
|
|
271
|
+
function App({ initialConfiguration, }) {
|
|
197
272
|
const { exit } = useApp();
|
|
198
|
-
const [viewport, setViewport] = useState(() => ({
|
|
273
|
+
const [viewport, setViewport] = useState(() => ({
|
|
274
|
+
columns: process.stdout.columns || 120,
|
|
275
|
+
rows: process.stdout.rows || 36,
|
|
276
|
+
}));
|
|
199
277
|
const workspaceRoot = useMemo(() => cwd(), []);
|
|
200
278
|
const [configuration, setConfiguration] = useState(initialConfiguration);
|
|
201
279
|
const [client, setClient] = useState();
|
|
280
|
+
const [mcpStatuses, setMcpStatuses] = useState(() => Object.entries(initialConfiguration?.mcpServers ?? {}).map(([name, server]) => ({
|
|
281
|
+
name,
|
|
282
|
+
state: server.enabled === false ? "disabled" : "idle",
|
|
283
|
+
toolCount: 0,
|
|
284
|
+
})));
|
|
202
285
|
const [sessionId, setSessionId] = useState();
|
|
203
286
|
const [files, setFiles] = useState([]);
|
|
204
287
|
const [fileIndex, setFileIndex] = useState(0);
|
|
@@ -214,8 +297,13 @@ function App({ initialConfiguration }) {
|
|
|
214
297
|
const [chatInput, setChatInput] = useState("");
|
|
215
298
|
const [chat, setChat] = useState([]);
|
|
216
299
|
const [chatScroll, setChatScroll] = useState(0);
|
|
217
|
-
const [streamMetrics, setStreamMetrics] = useState({
|
|
218
|
-
|
|
300
|
+
const [streamMetrics, setStreamMetrics] = useState({
|
|
301
|
+
startedAt: 0,
|
|
302
|
+
generatedTokens: 0,
|
|
303
|
+
});
|
|
304
|
+
const [terminalLines, setTerminalLines] = useState([
|
|
305
|
+
"Terminal ready. Shell commands run in the workspace root; slash commands run locally.",
|
|
306
|
+
]);
|
|
219
307
|
const [terminalScroll, setTerminalScroll] = useState(0);
|
|
220
308
|
const [previewUrl, setPreviewUrl] = useState();
|
|
221
309
|
const [editorScroll, setEditorScroll] = useState(0);
|
|
@@ -227,6 +315,7 @@ function App({ initialConfiguration }) {
|
|
|
227
315
|
const [endpoints, setEndpoints] = useState([]);
|
|
228
316
|
const [serverIndex, setServerIndex] = useState(0);
|
|
229
317
|
const [models, setModels] = useState([]);
|
|
318
|
+
const [agentProfiles, setAgentProfiles] = useState([]);
|
|
230
319
|
const [modelIndex, setModelIndex] = useState(0);
|
|
231
320
|
const [settingsField, setSettingsField] = useState("server");
|
|
232
321
|
const [themeName, setThemeName] = useState(initialConfiguration?.tuiTheme ?? "forest");
|
|
@@ -242,7 +331,8 @@ function App({ initialConfiguration }) {
|
|
|
242
331
|
const contextWindow = useMemo(configuredContextWindow, []);
|
|
243
332
|
const contextTokens = useMemo(() => chat.reduce((total, message) => total + estimateTokens(message.content), 400), [chat]);
|
|
244
333
|
const tokensPerSecond = streamMetrics.startedAt && streamMetrics.generatedTokens
|
|
245
|
-
? streamMetrics.generatedTokens /
|
|
334
|
+
? streamMetrics.generatedTokens /
|
|
335
|
+
Math.max((Date.now() - streamMetrics.startedAt) / 1_000, 0.1)
|
|
246
336
|
: undefined;
|
|
247
337
|
const theme = tuiTheme(themeName);
|
|
248
338
|
const appendTerminal = (output) => {
|
|
@@ -257,7 +347,12 @@ function App({ initialConfiguration }) {
|
|
|
257
347
|
};
|
|
258
348
|
const candidates = useMemo(() => [
|
|
259
349
|
...endpoints,
|
|
260
|
-
{
|
|
350
|
+
{
|
|
351
|
+
id: "custom",
|
|
352
|
+
label: "Custom compatible endpoint",
|
|
353
|
+
kind: "openai-compatible",
|
|
354
|
+
baseUrl: "",
|
|
355
|
+
},
|
|
261
356
|
], [endpoints]);
|
|
262
357
|
const selectedEndpoint = candidates[Math.min(serverIndex, Math.max(candidates.length - 1, 0))];
|
|
263
358
|
const fileTree = useMemo(() => buildFileTree(files, collapsedDirectories), [files, collapsedDirectories]);
|
|
@@ -267,10 +362,18 @@ function App({ initialConfiguration }) {
|
|
|
267
362
|
const terminalHeight = clamp(Math.floor(viewport.rows * 0.24), 7, 11);
|
|
268
363
|
const workspaceHeight = Math.max(compactLayout ? 14 : 9, viewport.rows - terminalHeight - 6);
|
|
269
364
|
const filesWidth = Math.max(20, Math.min(36, Math.floor(viewport.columns * 0.24)));
|
|
270
|
-
const chatWidth = compactLayout
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
365
|
+
const chatWidth = compactLayout
|
|
366
|
+
? Math.max(40, viewport.columns - 4)
|
|
367
|
+
: Math.max(28, Math.min(48, Math.floor(viewport.columns * 0.31)));
|
|
368
|
+
const editorWidth = compactLayout
|
|
369
|
+
? Math.max(30, viewport.columns - filesWidth - 4)
|
|
370
|
+
: Math.max(30, viewport.columns - filesWidth - chatWidth - 4);
|
|
371
|
+
const compactChatHeight = compactLayout
|
|
372
|
+
? clamp(Math.floor(workspaceHeight * 0.4), 6, 10)
|
|
373
|
+
: workspaceHeight;
|
|
374
|
+
const editorHeight = compactLayout
|
|
375
|
+
? Math.max(6, workspaceHeight - compactChatHeight - 1)
|
|
376
|
+
: workspaceHeight;
|
|
274
377
|
const editorLineCount = Math.max(2, editorHeight - 3);
|
|
275
378
|
const chatLineCount = Math.max(2, compactChatHeight - (activePlan ? 7 : 0) - 4);
|
|
276
379
|
const chatTranscript = chatDisplayLines(chat, busy, Math.max(12, chatWidth - 5));
|
|
@@ -285,11 +388,13 @@ function App({ initialConfiguration }) {
|
|
|
285
388
|
? "red"
|
|
286
389
|
: undefined
|
|
287
390
|
: undefined;
|
|
288
|
-
const tokens = isDiff
|
|
391
|
+
const tokens = isDiff
|
|
392
|
+
? [{ text: line, color }]
|
|
393
|
+
: syntaxTokens(line, openFilePath ?? "");
|
|
289
394
|
return wrapSyntaxTokens(tokens, maximumCharacters).map((row, rowIndex) => ({
|
|
290
395
|
sourceLine: sourceIndex + 1,
|
|
291
396
|
continuation: rowIndex > 0,
|
|
292
|
-
tokens: row
|
|
397
|
+
tokens: row,
|
|
293
398
|
}));
|
|
294
399
|
});
|
|
295
400
|
}, [editor, editorWidth, isDiff, openFilePath]);
|
|
@@ -299,11 +404,23 @@ function App({ initialConfiguration }) {
|
|
|
299
404
|
const visibleFileTree = fileTree.slice(fileTreeStart, fileTreeStart + editorLineCount);
|
|
300
405
|
const overlayWidth = clamp(viewport.columns - 4, 42, 90);
|
|
301
406
|
useEffect(() => {
|
|
302
|
-
void collectFiles(workspaceRoot)
|
|
303
|
-
|
|
407
|
+
void collectFiles(workspaceRoot)
|
|
408
|
+
.then(setFiles)
|
|
409
|
+
.catch((error) => setEditor(`Unable to list workspace files: ${String(error)}`));
|
|
410
|
+
void new FileAgentProfileStore(workspaceRoot)
|
|
411
|
+
.list()
|
|
412
|
+
.then(setAgentProfiles)
|
|
413
|
+
.catch((error) => appendTerminal(`[agents] ${error instanceof Error ? error.message : String(error)}`));
|
|
414
|
+
void new FileWorkspacePlanStore(workspaceRoot)
|
|
415
|
+
.load()
|
|
416
|
+
.then(setActivePlan)
|
|
417
|
+
.catch(() => undefined);
|
|
304
418
|
void detectLocalEndpoints().then((detected) => {
|
|
305
419
|
setEndpoints(detected);
|
|
306
|
-
const preferred = configuration
|
|
420
|
+
const preferred = configuration
|
|
421
|
+
? detected.findIndex((endpoint) => endpoint.kind === configuration.provider &&
|
|
422
|
+
endpoint.baseUrl === configuration.baseUrl)
|
|
423
|
+
: -1;
|
|
307
424
|
if (preferred >= 0)
|
|
308
425
|
setServerIndex(preferred);
|
|
309
426
|
});
|
|
@@ -322,35 +439,60 @@ function App({ initialConfiguration }) {
|
|
|
322
439
|
setFileIndex(index);
|
|
323
440
|
}, [fileTree, openFilePath]);
|
|
324
441
|
useEffect(() => {
|
|
325
|
-
const refreshViewport = () => setViewport({
|
|
442
|
+
const refreshViewport = () => setViewport({
|
|
443
|
+
columns: process.stdout.columns || 120,
|
|
444
|
+
rows: process.stdout.rows || 36,
|
|
445
|
+
});
|
|
326
446
|
process.stdout.on("resize", refreshViewport);
|
|
327
|
-
return () => {
|
|
447
|
+
return () => {
|
|
448
|
+
process.stdout.off("resize", refreshViewport);
|
|
449
|
+
};
|
|
328
450
|
}, []);
|
|
329
451
|
useEffect(() => {
|
|
330
|
-
if (!selectedEndpoint ||
|
|
452
|
+
if (!selectedEndpoint ||
|
|
453
|
+
!endpointInput ||
|
|
454
|
+
!isLocalEndpointKind(providerKind))
|
|
331
455
|
return;
|
|
332
|
-
const endpoint = {
|
|
333
|
-
|
|
456
|
+
const endpoint = {
|
|
457
|
+
...selectedEndpoint,
|
|
458
|
+
kind: providerKind,
|
|
459
|
+
baseUrl: endpointInput,
|
|
460
|
+
};
|
|
461
|
+
void listLocalModels(endpoint)
|
|
462
|
+
.then((available) => {
|
|
334
463
|
setModels(available.map((model) => model.name));
|
|
335
464
|
const selected = available.findIndex((model) => model.name === modelInput);
|
|
336
465
|
setModelIndex(Math.max(0, selected));
|
|
337
|
-
})
|
|
466
|
+
})
|
|
467
|
+
.catch(() => setModels([]));
|
|
338
468
|
}, [selectedEndpoint?.id, endpointInput, providerKind]);
|
|
339
469
|
useEffect(() => {
|
|
340
470
|
if (!client)
|
|
341
471
|
return;
|
|
472
|
+
const unsubscribeMcp = client.mcp.subscribe((statuses) => setMcpStatuses((current) => {
|
|
473
|
+
const merged = new Map(current.map((status) => [status.name, status]));
|
|
474
|
+
for (const status of statuses)
|
|
475
|
+
merged.set(status.name, status);
|
|
476
|
+
return [...merged.values()];
|
|
477
|
+
}));
|
|
342
478
|
const unsubscribe = client.events.subscribe((event) => {
|
|
343
479
|
if (event.type === "text_delta") {
|
|
344
480
|
setRunStatus("thinking");
|
|
345
481
|
setChatScroll(0);
|
|
346
482
|
setStreamMetrics((current) => current.startedAt
|
|
347
|
-
? {
|
|
483
|
+
? {
|
|
484
|
+
...current,
|
|
485
|
+
generatedTokens: current.generatedTokens + estimateTokens(event.text),
|
|
486
|
+
}
|
|
348
487
|
: current);
|
|
349
488
|
setChat((current) => {
|
|
350
489
|
const last = current.at(-1);
|
|
351
490
|
if (!last || last.role !== "assistant")
|
|
352
491
|
return [...current, { role: "assistant", content: event.text }];
|
|
353
|
-
return [
|
|
492
|
+
return [
|
|
493
|
+
...current.slice(0, -1),
|
|
494
|
+
{ role: "assistant", content: last.content + event.text },
|
|
495
|
+
];
|
|
354
496
|
});
|
|
355
497
|
}
|
|
356
498
|
if (event.type === "tool_call_requested") {
|
|
@@ -370,6 +512,7 @@ function App({ initialConfiguration }) {
|
|
|
370
512
|
});
|
|
371
513
|
return () => {
|
|
372
514
|
unsubscribe();
|
|
515
|
+
unsubscribeMcp();
|
|
373
516
|
void client.dispose();
|
|
374
517
|
};
|
|
375
518
|
}, [client]);
|
|
@@ -446,14 +589,25 @@ function App({ initialConfiguration }) {
|
|
|
446
589
|
model: modelInput.trim(),
|
|
447
590
|
apiKey: initialConfiguration?.provider === providerKind
|
|
448
591
|
? initialConfiguration.apiKey
|
|
449
|
-
: process.env.TRUSS_HARNESS_API_KEY ??
|
|
592
|
+
: (process.env.TRUSS_HARNESS_API_KEY ??
|
|
593
|
+
(isCloudProviderId(providerKind)
|
|
594
|
+
? process.env[cloudProviderDefinition(providerKind)
|
|
595
|
+
.apiKeyEnvironmentVariable]
|
|
596
|
+
: undefined)),
|
|
450
597
|
systemPrompt: process.env.TRUSS_HARNESS_SYSTEM_PROMPT,
|
|
451
598
|
mode: agentMode,
|
|
452
599
|
internetAccess,
|
|
600
|
+
mcpServers: initialConfiguration?.mcpServers,
|
|
453
601
|
approval: {
|
|
454
602
|
approve: async (call) => {
|
|
455
|
-
const readOnly = [
|
|
456
|
-
|
|
603
|
+
const readOnly = [
|
|
604
|
+
"read_file",
|
|
605
|
+
"list_directory",
|
|
606
|
+
"search_files",
|
|
607
|
+
"grep",
|
|
608
|
+
].includes(call.name);
|
|
609
|
+
if (permissionMode === "auto-all" ||
|
|
610
|
+
(permissionMode === "auto-read" && readOnly))
|
|
457
611
|
return true;
|
|
458
612
|
return new Promise((resolve) => {
|
|
459
613
|
approvalResolvers.current.set(call.id, resolve);
|
|
@@ -461,8 +615,8 @@ function App({ initialConfiguration }) {
|
|
|
461
615
|
setRunStatus("waiting");
|
|
462
616
|
setScreen("approval");
|
|
463
617
|
});
|
|
464
|
-
}
|
|
465
|
-
}
|
|
618
|
+
},
|
|
619
|
+
},
|
|
466
620
|
};
|
|
467
621
|
setConfiguration(nextConfiguration);
|
|
468
622
|
setRunStatus("thinking");
|
|
@@ -498,11 +652,13 @@ function App({ initialConfiguration }) {
|
|
|
498
652
|
const maximumCharacters = Math.max(2_000, Math.min(20_000, contextWindow));
|
|
499
653
|
const content = await readFile(join(workspaceRoot, openFilePath), "utf8");
|
|
500
654
|
const clipped = content.slice(0, maximumCharacters);
|
|
501
|
-
return [
|
|
655
|
+
return [
|
|
656
|
+
{
|
|
502
657
|
source: `active-file:${openFilePath}`,
|
|
503
658
|
content: `This is the currently open workspace file and the primary context for this request. Tool results produced later in the run take precedence over this request-start snapshot.\n\n${clipped}`,
|
|
504
|
-
priority: 1_000
|
|
505
|
-
}
|
|
659
|
+
priority: 1_000,
|
|
660
|
+
},
|
|
661
|
+
];
|
|
506
662
|
}
|
|
507
663
|
catch {
|
|
508
664
|
return [];
|
|
@@ -512,10 +668,17 @@ function App({ initialConfiguration }) {
|
|
|
512
668
|
const prompt = chatInput.trim();
|
|
513
669
|
if (!prompt || busy)
|
|
514
670
|
return;
|
|
515
|
-
const command = await executeWorkspaceCommand({
|
|
671
|
+
const command = await executeWorkspaceCommand({
|
|
672
|
+
workspaceRoot,
|
|
673
|
+
input: prompt,
|
|
674
|
+
});
|
|
516
675
|
if (command.handled) {
|
|
517
676
|
setChatInput("");
|
|
518
|
-
setChat((current) => [
|
|
677
|
+
setChat((current) => [
|
|
678
|
+
...current,
|
|
679
|
+
{ role: "user", content: prompt },
|
|
680
|
+
{ role: "assistant", content: command.message },
|
|
681
|
+
]);
|
|
519
682
|
appendTerminal(`[workspace command] ${command.command ?? prompt}: ${command.ok ? "completed" : "failed"}`);
|
|
520
683
|
return;
|
|
521
684
|
}
|
|
@@ -529,11 +692,17 @@ function App({ initialConfiguration }) {
|
|
|
529
692
|
setBusy(true);
|
|
530
693
|
setRunStatus("thinking");
|
|
531
694
|
setStreamMetrics({ startedAt: Date.now(), generatedTokens: 0 });
|
|
532
|
-
setChat((current) => [
|
|
695
|
+
setChat((current) => [
|
|
696
|
+
...current,
|
|
697
|
+
{ role: "user", content: prompt },
|
|
698
|
+
{ role: "assistant", content: "" },
|
|
699
|
+
]);
|
|
533
700
|
const controller = new AbortController();
|
|
534
701
|
abortController.current = controller;
|
|
535
702
|
try {
|
|
536
|
-
const session = sessionId
|
|
703
|
+
const session = sessionId
|
|
704
|
+
? await client.runtime.getSession(sessionId)
|
|
705
|
+
: await client.runtime.createSession();
|
|
537
706
|
if (!session)
|
|
538
707
|
throw new Error("Conversation is unavailable.");
|
|
539
708
|
setSessionId(session.id);
|
|
@@ -575,7 +744,9 @@ function App({ initialConfiguration }) {
|
|
|
575
744
|
appendTerminal(command.message);
|
|
576
745
|
return;
|
|
577
746
|
}
|
|
578
|
-
await runTrackedCommand(input, workspaceRoot, appendTerminal, (process) => {
|
|
747
|
+
await runTrackedCommand(input, workspaceRoot, appendTerminal, (process) => {
|
|
748
|
+
terminalProcess.current = process;
|
|
749
|
+
});
|
|
579
750
|
};
|
|
580
751
|
const moveFocus = (direction) => {
|
|
581
752
|
setFocus((current) => {
|
|
@@ -641,13 +812,45 @@ function App({ initialConfiguration }) {
|
|
|
641
812
|
setScreen("workspace");
|
|
642
813
|
return;
|
|
643
814
|
}
|
|
815
|
+
if (screen === "agents") {
|
|
816
|
+
if (key.escape || input.toLowerCase() === "a")
|
|
817
|
+
setScreen("workspace");
|
|
818
|
+
if (input.toLowerCase() === "r")
|
|
819
|
+
void new FileAgentProfileStore(workspaceRoot)
|
|
820
|
+
.list()
|
|
821
|
+
.then(setAgentProfiles)
|
|
822
|
+
.catch((error) => appendTerminal(`[agents] ${error instanceof Error ? error.message : String(error)}`));
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (screen === "mcp") {
|
|
826
|
+
if (key.escape || input.toLowerCase() === "c") {
|
|
827
|
+
setScreen("workspace");
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
if (input.toLowerCase() === "r" && configuration) {
|
|
831
|
+
void createClientRuntime({ ...configuration, mode: "edit" })
|
|
832
|
+
.then(async (inspection) => {
|
|
833
|
+
setMcpStatuses(inspection.mcpServers);
|
|
834
|
+
appendTerminal("[mcp] Tested configured servers.");
|
|
835
|
+
await inspection.dispose();
|
|
836
|
+
})
|
|
837
|
+
.catch((error) => appendTerminal(`[mcp] ${error instanceof Error ? error.message : String(error)}`));
|
|
838
|
+
}
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
644
841
|
if (screen === "settings") {
|
|
645
842
|
if (key.escape) {
|
|
646
843
|
setScreen("workspace");
|
|
647
844
|
return;
|
|
648
845
|
}
|
|
649
846
|
if (key.tab) {
|
|
650
|
-
const fields = [
|
|
847
|
+
const fields = [
|
|
848
|
+
"server",
|
|
849
|
+
"endpoint",
|
|
850
|
+
"model",
|
|
851
|
+
"internet",
|
|
852
|
+
"theme",
|
|
853
|
+
];
|
|
651
854
|
setSettingsField((current) => {
|
|
652
855
|
const index = fields.indexOf(current);
|
|
653
856
|
return fields[(index + (key.shift ? -1 : 1) + fields.length) % fields.length];
|
|
@@ -683,7 +886,8 @@ function App({ initialConfiguration }) {
|
|
|
683
886
|
}
|
|
684
887
|
if (settingsField === "theme") {
|
|
685
888
|
if (key.leftArrow || key.upArrow)
|
|
686
|
-
setThemeName((current) => tuiThemeNames[(tuiThemeNames.indexOf(current) - 1 + tuiThemeNames.length) %
|
|
889
|
+
setThemeName((current) => tuiThemeNames[(tuiThemeNames.indexOf(current) - 1 + tuiThemeNames.length) %
|
|
890
|
+
tuiThemeNames.length]);
|
|
687
891
|
if (key.rightArrow || key.downArrow || input === " ")
|
|
688
892
|
setThemeName((current) => tuiThemeNames[(tuiThemeNames.indexOf(current) + 1) % tuiThemeNames.length]);
|
|
689
893
|
if (key.return && endpointInput && modelInput)
|
|
@@ -705,6 +909,12 @@ function App({ initialConfiguration }) {
|
|
|
705
909
|
moveFocus(key.leftArrow ? -1 : 1);
|
|
706
910
|
return;
|
|
707
911
|
}
|
|
912
|
+
if (input.toLowerCase() === "a" &&
|
|
913
|
+
focus !== "chat" &&
|
|
914
|
+
focus !== "terminal") {
|
|
915
|
+
setScreen("agents");
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
708
918
|
if (key.tab) {
|
|
709
919
|
moveFocus(key.shift ? -1 : 1);
|
|
710
920
|
return;
|
|
@@ -715,7 +925,10 @@ function App({ initialConfiguration }) {
|
|
|
715
925
|
appendTerminal("[agent] Cancellation requested.");
|
|
716
926
|
return;
|
|
717
927
|
}
|
|
718
|
-
if (input === "o" &&
|
|
928
|
+
if (input === "o" &&
|
|
929
|
+
focus !== "chat" &&
|
|
930
|
+
focus !== "terminal" &&
|
|
931
|
+
previewUrl) {
|
|
719
932
|
try {
|
|
720
933
|
openExternalPreview(previewUrl);
|
|
721
934
|
appendTerminal(`[preview] Opened ${previewUrl}`);
|
|
@@ -755,6 +968,10 @@ function App({ initialConfiguration }) {
|
|
|
755
968
|
setScreen("settings");
|
|
756
969
|
return;
|
|
757
970
|
}
|
|
971
|
+
if (input.toLowerCase() === "c" && focus !== "chat") {
|
|
972
|
+
setScreen("mcp");
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
758
975
|
if (input === "n" && focus !== "chat") {
|
|
759
976
|
startNewConversation();
|
|
760
977
|
return;
|
|
@@ -810,25 +1027,81 @@ function App({ initialConfiguration }) {
|
|
|
810
1027
|
else if (input)
|
|
811
1028
|
setChatInput((current) => current + input);
|
|
812
1029
|
});
|
|
813
|
-
const filesPanel = _jsxs(Panel, { title: "FILES [/] find", active: focus === "files", theme: theme, children: [visibleFileTree.map((entry, visibleIndex) => {
|
|
1030
|
+
const filesPanel = (_jsxs(Panel, { title: "FILES [/] find", active: focus === "files", theme: theme, children: [visibleFileTree.map((entry, visibleIndex) => {
|
|
814
1031
|
const selected = fileTreeStart + visibleIndex === fileIndex;
|
|
815
1032
|
const marker = entry.kind === "directory" ? (entry.expanded ? "v " : "> ") : " ";
|
|
816
1033
|
const indentation = " ".repeat(entry.depth * 2);
|
|
817
1034
|
const width = Math.max(6, filesWidth - indentation.length - 7);
|
|
818
|
-
return _jsxs(Text, { wrap: "truncate-end", color: selected
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1035
|
+
return (_jsxs(Text, { wrap: "truncate-end", color: selected
|
|
1036
|
+
? theme.focus
|
|
1037
|
+
: entry.kind === "directory"
|
|
1038
|
+
? theme.directory
|
|
1039
|
+
: entry.path === openFilePath
|
|
1040
|
+
? theme.success
|
|
1041
|
+
: theme.text, bold: selected || entry.kind === "directory", children: [selected ? "> " : " ", indentation, marker, truncate(entry.name, width)] }, `${entry.kind}:${entry.path}`));
|
|
1042
|
+
}), !fileTree.length && (_jsx(Text, { color: theme.muted, children: "No workspace files found." }))] }));
|
|
1043
|
+
const editorPanel = (_jsx(Panel, { title: `${editorTitle} [up/down] scroll [d] diff${previewUrl ? " [o] browser" : ""}`, active: focus === "editor", theme: theme, children: visibleEditorRows.map((row, index) => {
|
|
1044
|
+
const lineLabel = row.continuation
|
|
1045
|
+
? " |"
|
|
1046
|
+
: String(row.sourceLine).padStart(3);
|
|
1047
|
+
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}`));
|
|
1048
|
+
}) }));
|
|
1049
|
+
const agentPanel = (_jsxs(Panel, { title: chatScroll
|
|
1050
|
+
? `AGENT [${chatScroll} lines above]`
|
|
1051
|
+
: "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"
|
|
1052
|
+
? theme.success
|
|
1053
|
+
: step.status === "in_progress"
|
|
1054
|
+
? theme.warning
|
|
1055
|
+
: theme.muted, children: [step.status === "completed"
|
|
1056
|
+
? "[x]"
|
|
1057
|
+
: step.status === "in_progress"
|
|
1058
|
+
? "[..]"
|
|
1059
|
+
: "[ ]", " ", 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 ||
|
|
1060
|
+
(busy ? "Working... Escape cancels" : "Ask about this workspace"), Math.max(12, chatWidth - 5))] })] }));
|
|
1061
|
+
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
|
|
1062
|
+
? `${configuration.provider} / ${configuration.model}`
|
|
1063
|
+
: "No model selected" }), _jsx(Text, { color: runStatus === "waiting"
|
|
1064
|
+
? theme.warning
|
|
1065
|
+
: busy
|
|
1066
|
+
? theme.focus
|
|
1067
|
+
: theme.success, children: runStatus === "waiting"
|
|
1068
|
+
? "APPROVAL"
|
|
1069
|
+
: runStatus === "tool"
|
|
1070
|
+
? "TOOL"
|
|
1071
|
+
: busy
|
|
1072
|
+
? "WORKING"
|
|
1073
|
+
: "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
|
|
1074
|
+
? theme.error
|
|
1075
|
+
: contextTokens / contextWindow >= 0.7
|
|
1076
|
+
? theme.warning
|
|
1077
|
+
: 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
|
|
1078
|
+
? `${tokensPerSecond.toFixed(1)} tok/s`
|
|
1079
|
+
: "-- tok/s" }), previewUrl
|
|
1080
|
+
? ` PREVIEW ${truncate(previewUrl, Math.max(16, Math.floor(viewport.columns * 0.3)))}`
|
|
1081
|
+
: ""] })] }), 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]") ||
|
|
1082
|
+
line.startsWith("[terminal error]")
|
|
1083
|
+
? theme.error
|
|
1084
|
+
: line.startsWith("[tool")
|
|
1085
|
+
? theme.warning
|
|
1086
|
+
: 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 | a agents | c MCP | 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) => {
|
|
1087
|
+
const selected = index ===
|
|
1088
|
+
Math.min(fileSearchIndex, Math.max(0, fileSearchResults.length - 1));
|
|
827
1089
|
const fileName = entry.path.split("/").at(-1) ?? entry.path;
|
|
828
|
-
return _jsxs(Text, { color: selected ? theme.focus : theme.text, bold: selected, children: [selected ? "> " : " ", fileName, _jsxs(Text, { color: theme.muted, children: ["
|
|
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 === "
|
|
1090
|
+
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));
|
|
1091
|
+
}), !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 === "agents" && (_jsxs(Box, { position: "absolute", flexDirection: "column", borderStyle: "double", borderColor: theme.focus, paddingX: 2, paddingY: 1, width: overlayWidth, marginLeft: 2, marginTop: 6, backgroundColor: theme.overlay, children: [_jsx(Text, { bold: true, color: theme.accent, children: "MULTI-AGENT PROFILES" }), _jsx(Text, { color: theme.muted, children: "Profiles are shared with truss-cli in .truss-harness/agents.json. Use `truss agents run <id> <task>` for independent concurrent runs." }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [agentProfiles.map((profile) => (_jsxs(Text, { color: theme.text, children: [_jsx(Text, { color: theme.focus, children: profile.displayName }), _jsxs(Text, { color: theme.muted, children: [" ", profile.id.slice(0, 8), " ", profile.provider.providerId, "/", profile.provider.modelId, " ", profile.mode, " ", profile.approvalPolicy] })] }, profile.id))), !agentProfiles.length && (_jsx(Text, { color: theme.warning, children: "No profiles yet. Add one with `truss agents add <name>`." }))] }), _jsx(Text, { color: theme.muted, children: "R refreshes. A or Escape closes." })] })), screen === "mcp" && (_jsxs(Box, { position: "absolute", flexDirection: "column", borderStyle: "double", borderColor: theme.focus, paddingX: 2, paddingY: 1, width: overlayWidth, marginLeft: 2, marginTop: 6, backgroundColor: theme.overlay, children: [_jsx(Text, { bold: true, color: theme.accent, children: "MCP CONNECTIONS" }), _jsx(Text, { color: theme.muted, children: "Status and discovered tools only. Commands, environment values, and credentials remain hidden." }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [mcpStatuses.map((server) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: server.state === "failed"
|
|
1092
|
+
? theme.error
|
|
1093
|
+
: server.state === "connected"
|
|
1094
|
+
? theme.success
|
|
1095
|
+
: theme.warning, children: [_jsx(Text, { bold: true, children: server.name }), " ", server.state, server.error
|
|
1096
|
+
? ` — ${server.error}`
|
|
1097
|
+
: ` — ${server.toolCount} tool${server.toolCount === 1 ? "" : "s"}`] }), (server.tools ?? []).map((tool) => (_jsxs(Text, { color: theme.muted, children: [" ", tool.name, " [", tool.readOnly ? "read-only" : "approval", "]"] }, `${server.name}:${tool.name}`)))] }, server.name))), !mcpStatuses.length && (_jsx(Text, { color: theme.warning, children: "No MCP servers configured." }))] }), _jsx(Text, { color: theme.muted, children: "R tests configured servers. C or Escape closes." })] })), 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()
|
|
1098
|
+
.split("\n")
|
|
1099
|
+
.slice(1)
|
|
1100
|
+
.map((line) => (_jsx(Text, { color: theme.text, children: line }, line))), _jsx(Text, { color: theme.muted, children: "Press any key to close this reference." })] }))] }));
|
|
830
1101
|
}
|
|
831
|
-
const initialConfiguration = await resolveConfiguration({
|
|
1102
|
+
const initialConfiguration = await resolveConfiguration({
|
|
1103
|
+
workspaceRoot: cwd(),
|
|
1104
|
+
}).catch((error) => {
|
|
832
1105
|
const message = error instanceof Error ? error.message : String(error);
|
|
833
1106
|
if (message.startsWith("Set a model"))
|
|
834
1107
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truss-harness/tui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "Interactive terminal client for Truss.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"engines": {
|
|
20
20
|
"node": ">=20"
|
|
21
21
|
},
|
|
22
|
-
"license": "
|
|
22
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
25
|
"url": "git+https://github.com/truss-agent/truss-harness.git"
|
|
@@ -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.17",
|
|
47
|
+
"@truss-harness/provider-openai-compatible": "0.1.10",
|
|
48
|
+
"@truss-harness/runtime": "0.1.9"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/react": "^19.2.0"
|