@truss-harness/cli 0.1.0 → 0.1.3
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 +15 -1
- package/dist/bin.js +160 -5
- package/dist/config.d.ts +6 -0
- package/dist/config.js +24 -3
- package/package.json +2 -2
- package/scripts/postinstall.mjs +2 -3
package/README.md
CHANGED
|
@@ -23,13 +23,17 @@ truss-cli
|
|
|
23
23
|
truss-cli help
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
## Interactive setup
|
|
27
|
+
|
|
28
|
+
Run truss-cli setup after installation to discover local servers, choose a model, and save a default user-level profile. It is a command rather than an install-time prompt, so npm installs remain safe in CI and scripts.
|
|
29
|
+
|
|
26
30
|
## First run
|
|
27
31
|
|
|
28
32
|
Start your local model server, open a terminal in the project you want Truss to work on, and run:
|
|
29
33
|
|
|
30
34
|
```sh
|
|
31
35
|
truss-cli models
|
|
32
|
-
truss-cli chat "Explain this workspace"
|
|
36
|
+
truss-cli chat --mode edit "Explain this workspace"
|
|
33
37
|
```
|
|
34
38
|
|
|
35
39
|
Truss probes the standard Ollama, LM Studio, and llama.cpp endpoints when no model is configured. Create a reusable workspace profile when you want explicit settings:
|
|
@@ -50,6 +54,16 @@ truss-cli chat --internet-access "Check the current library documentation"
|
|
|
50
54
|
|
|
51
55
|
Direct CLI chat is non-interactive and auto-allows registered tools. Run it only in a trusted workspace. Public internet tools are disabled unless `--internet-access`, a profile, or the environment explicitly enables them.
|
|
52
56
|
|
|
57
|
+
## Persistent chat
|
|
58
|
+
|
|
59
|
+
Run `truss-cli chat` without a prompt to keep one local conversation open. The configuration flags supplied when it starts remain active, and history remains available as you send messages.
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
truss-cli chat --mode edit --permission auto-read
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Use `:mode chat`, `:mode plan`, or `:mode edit` to change the active mode. You can also prefix a message with `--mode edit`, for example `--mode edit Inspect the repository and explain it.` The mode change persists for the rest of that conversation. Use `:clear` for a fresh conversation, `:help` for controls, and `:exit` to close it.
|
|
66
|
+
|
|
53
67
|
## Workspace commands
|
|
54
68
|
|
|
55
69
|
These deterministic commands work without a configured model:
|
package/dist/bin.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { cwd } from "node:process";
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
3
4
|
import { detectLocalEndpoints, listLocalModels } from "@truss-harness/provider-openai-compatible";
|
|
4
5
|
import { brand } from "@truss-harness/branding";
|
|
5
6
|
import { executeWorkspaceCommand, workspaceCommandHelp } from "@truss-harness/runtime";
|
|
6
7
|
import { createClientRuntime } from "./runtime.js";
|
|
7
|
-
import { configurationPaths, initializeWorkspaceConfiguration, parseConfigurationOverrides, resolveConfiguration } from "./config.js";
|
|
8
|
+
import { configurationPaths, initializeWorkspaceConfiguration, parseConfigurationOverrides, resolveConfiguration, saveUserProfile } from "./config.js";
|
|
8
9
|
import { ProtocolToolApproval, runService } from "./protocol.js";
|
|
9
10
|
const help = `${brand.productName} CLI
|
|
10
11
|
|
|
@@ -16,11 +17,14 @@ Usage:
|
|
|
16
17
|
Quick start:
|
|
17
18
|
1. Start Ollama, LM Studio, llama.cpp, or a compatible local server.
|
|
18
19
|
2. Run: ${brand.cliCommand} models
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
3. Run: ${brand.cliCommand} chat "Explain this workspace"
|
|
21
|
+
3. Run: ${brand.cliCommand} setup
|
|
22
|
+
4. Run: ${brand.cliCommand} chat --mode edit
|
|
23
|
+
5. Optional: ${brand.cliCommand} config init
|
|
21
24
|
|
|
22
25
|
Commands:
|
|
23
|
-
chat
|
|
26
|
+
chat [prompt] Stream one response or open persistent chat
|
|
27
|
+
setup Interactively choose and save local-model defaults
|
|
24
28
|
models List detected local servers and models
|
|
25
29
|
config path Print user and workspace configuration paths
|
|
26
30
|
config init Create a workspace configuration template
|
|
@@ -74,6 +78,149 @@ Examples:
|
|
|
74
78
|
|
|
75
79
|
${workspaceCommandHelp()}
|
|
76
80
|
`;
|
|
81
|
+
function subscribeToRuntimeEvents(events) {
|
|
82
|
+
events.subscribe((event) => {
|
|
83
|
+
if (event.type === "text_delta")
|
|
84
|
+
process.stdout.write(event.text);
|
|
85
|
+
if (event.type === "tool_call_requested")
|
|
86
|
+
process.stderr.write("\n[tool] " + event.tool + "\n");
|
|
87
|
+
if (event.type === "plan_updated") {
|
|
88
|
+
const steps = event.plan.steps.map((step) => " " + (step.status === "completed" ? "[x]" : step.status === "in_progress" ? "[..]" : "[ ]") + " " + step.content).join("\n");
|
|
89
|
+
process.stderr.write("\n[plan] " + event.plan.title + "\n" + steps + "\n");
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function inlineMode(input) {
|
|
94
|
+
const match = input.trim().match(/^--mode\s+(chat|plan|edit)(?:\s+([\s\S]*))?$/);
|
|
95
|
+
return match ? { mode: match[1], prompt: match[2]?.trim() ?? "" } : { prompt: input.trim() };
|
|
96
|
+
}
|
|
97
|
+
async function runInteractiveChat(initialConfiguration) {
|
|
98
|
+
let configuration = initialConfiguration;
|
|
99
|
+
let client = await createClientRuntime(configuration);
|
|
100
|
+
let session = await client.runtime.createSession();
|
|
101
|
+
let messages = session.messages;
|
|
102
|
+
subscribeToRuntimeEvents(client.events);
|
|
103
|
+
const readline = createInterface({ input: process.stdin, output: process.stdout, terminal: process.stdin.isTTY === true });
|
|
104
|
+
process.stdout.write(brand.productName + " interactive chat. :help for controls. Mode: " + configuration.mode + ".\n");
|
|
105
|
+
const replaceRuntime = async (mode) => {
|
|
106
|
+
const current = await client.runtime.getSession(session.id);
|
|
107
|
+
messages = current?.messages ?? messages;
|
|
108
|
+
await client.dispose();
|
|
109
|
+
configuration = { ...configuration, mode };
|
|
110
|
+
client = await createClientRuntime(configuration);
|
|
111
|
+
session = await client.runtime.createSession(messages);
|
|
112
|
+
subscribeToRuntimeEvents(client.events);
|
|
113
|
+
process.stdout.write("\n[mode: " + mode + "]\n");
|
|
114
|
+
};
|
|
115
|
+
try {
|
|
116
|
+
while (true) {
|
|
117
|
+
const line = await readline.question(brand.cliCommand + " (" + configuration.mode + ") > ").catch(() => undefined);
|
|
118
|
+
if (line === undefined)
|
|
119
|
+
break;
|
|
120
|
+
const input = line.trim();
|
|
121
|
+
if (!input)
|
|
122
|
+
continue;
|
|
123
|
+
if (input === ":exit" || input === ":quit")
|
|
124
|
+
break;
|
|
125
|
+
if (input === ":help") {
|
|
126
|
+
process.stdout.write("Controls: :mode chat|plan|edit, :clear, :exit. You can also prefix a message with --mode chat|plan|edit.\n");
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (input === ":clear") {
|
|
130
|
+
session = await client.runtime.createSession();
|
|
131
|
+
messages = session.messages;
|
|
132
|
+
process.stdout.write("[conversation cleared]\n");
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const modeCommand = input.match(/^:mode\s+(chat|plan|edit)$/);
|
|
136
|
+
if (modeCommand) {
|
|
137
|
+
await replaceRuntime(modeCommand[1]);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (input.startsWith(":mode")) {
|
|
141
|
+
process.stdout.write("Use :mode chat, :mode plan, or :mode edit.\n");
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const next = inlineMode(input);
|
|
145
|
+
if (next.mode && next.mode !== configuration.mode)
|
|
146
|
+
await replaceRuntime(next.mode);
|
|
147
|
+
if (!next.prompt)
|
|
148
|
+
continue;
|
|
149
|
+
const workspaceCommand = await executeWorkspaceCommand({ workspaceRoot: cwd(), input: next.prompt });
|
|
150
|
+
if (workspaceCommand.handled) {
|
|
151
|
+
process.stdout.write(workspaceCommand.message + "\n");
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
await client.runtime.run(session.id, next.prompt);
|
|
156
|
+
process.stdout.write("\n");
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
process.stderr.write("\n" + (error instanceof Error ? error.message : String(error)) + "\n");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
readline.close();
|
|
165
|
+
await client.dispose();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function selectedIndex(input, count, fallback) {
|
|
169
|
+
const index = Number.parseInt(input, 10);
|
|
170
|
+
return Number.isInteger(index) && index >= 1 && index <= count ? index - 1 : fallback;
|
|
171
|
+
}
|
|
172
|
+
async function runSetup() {
|
|
173
|
+
const readline = createInterface({ input: process.stdin, output: process.stdout, terminal: process.stdin.isTTY === true });
|
|
174
|
+
const ask = async (label, fallback) => {
|
|
175
|
+
const answer = (await readline.question(label + " [" + fallback + "]: ")).trim();
|
|
176
|
+
return answer || fallback;
|
|
177
|
+
};
|
|
178
|
+
try {
|
|
179
|
+
const endpoints = await detectLocalEndpoints();
|
|
180
|
+
process.stdout.write(brand.productName + " setup\n");
|
|
181
|
+
process.stdout.write("Choose a local model server. Values in brackets are defaults.\n\n");
|
|
182
|
+
endpoints.forEach((endpoint, index) => process.stdout.write(String(index + 1) + ". " + endpoint.label + " (" + endpoint.baseUrl + ")\n"));
|
|
183
|
+
process.stdout.write(String(endpoints.length + 1) + ". Custom endpoint\n");
|
|
184
|
+
const endpointChoice = selectedIndex(await ask("Server", "1"), endpoints.length + 1, 0);
|
|
185
|
+
let endpoint;
|
|
186
|
+
if (endpointChoice < endpoints.length) {
|
|
187
|
+
endpoint = endpoints[endpointChoice];
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const provider = await ask("Provider: ollama or openai-compatible", "openai-compatible");
|
|
191
|
+
if (provider !== "ollama" && provider !== "openai-compatible")
|
|
192
|
+
throw new Error("Provider must be ollama or openai-compatible.");
|
|
193
|
+
const fallbackUrl = provider === "ollama" ? "http://127.0.0.1:11434" : "http://127.0.0.1:1234/v1";
|
|
194
|
+
endpoint = { id: "custom", label: "Custom endpoint", kind: provider, baseUrl: await ask("Endpoint URL", fallbackUrl) };
|
|
195
|
+
}
|
|
196
|
+
const models = await listLocalModels(endpoint).catch(() => []);
|
|
197
|
+
models.forEach((model, index) => process.stdout.write(String(index + 1) + ". " + model.name + "\n"));
|
|
198
|
+
const model = models.length
|
|
199
|
+
? models[selectedIndex(await ask("Model", "1"), models.length, 0)].name
|
|
200
|
+
: await ask("Model ID", "local-model");
|
|
201
|
+
const profileName = await ask("Profile name", endpoint.id === "custom" ? "local" : endpoint.id);
|
|
202
|
+
const mode = await ask("Default mode: chat, plan, or edit", "edit");
|
|
203
|
+
if (mode !== "chat" && mode !== "plan" && mode !== "edit")
|
|
204
|
+
throw new Error("Mode must be chat, plan, or edit.");
|
|
205
|
+
const permission = await ask("Default permission: ask, auto-read, or auto-all", "auto-read");
|
|
206
|
+
if (permission !== "ask" && permission !== "auto-read" && permission !== "auto-all")
|
|
207
|
+
throw new Error("Permission must be ask, auto-read, or auto-all.");
|
|
208
|
+
const internet = await ask("Enable internet research: yes or no", "no");
|
|
209
|
+
const path = await saveUserProfile(cwd(), profileName, {
|
|
210
|
+
provider: endpoint.kind,
|
|
211
|
+
baseUrl: endpoint.baseUrl,
|
|
212
|
+
model,
|
|
213
|
+
mode,
|
|
214
|
+
permission,
|
|
215
|
+
internetAccess: /^(y|yes|true|1)$/i.test(internet)
|
|
216
|
+
});
|
|
217
|
+
process.stdout.write("\nSaved profile '" + profileName + "' to " + path + "\n");
|
|
218
|
+
process.stdout.write("Start a persistent session with: " + brand.cliCommand + " chat --profile " + profileName + "\n");
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
readline.close();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
77
224
|
async function main() {
|
|
78
225
|
const [command = "help", ...rawArgs] = process.argv.slice(2);
|
|
79
226
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
@@ -86,6 +233,10 @@ async function main() {
|
|
|
86
233
|
process.stdout.write(`${JSON.stringify(models, null, 2)}\n`);
|
|
87
234
|
return;
|
|
88
235
|
}
|
|
236
|
+
if (command === "setup") {
|
|
237
|
+
await runSetup();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
89
240
|
if (command === "config") {
|
|
90
241
|
const [action = "path"] = rawArgs;
|
|
91
242
|
const paths = configurationPaths(cwd());
|
|
@@ -113,7 +264,7 @@ async function main() {
|
|
|
113
264
|
process.exitCode = 1;
|
|
114
265
|
return;
|
|
115
266
|
}
|
|
116
|
-
if (command === "chat") {
|
|
267
|
+
if (command === "chat" && args.length) {
|
|
117
268
|
const prompt = args.join(" ").trim();
|
|
118
269
|
if (!prompt)
|
|
119
270
|
throw new Error(`Provide a prompt: ${brand.cliCommand} chat <prompt>`);
|
|
@@ -140,6 +291,10 @@ async function main() {
|
|
|
140
291
|
}
|
|
141
292
|
return;
|
|
142
293
|
}
|
|
294
|
+
if (command === "chat" && !args.join(" ").trim()) {
|
|
295
|
+
await runInteractiveChat(configuration);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
143
298
|
const client = await createClientRuntime(configuration);
|
|
144
299
|
const { runtime, events } = client;
|
|
145
300
|
for (const server of client.mcpServers) {
|
package/dist/config.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type McpServerConfigurations } from "@truss-harness/mcp";
|
|
|
2
2
|
import { type LocalEndpointKind } from "@truss-harness/provider-openai-compatible";
|
|
3
3
|
import type { AgentMode, ClientConfiguration } from "./runtime.js";
|
|
4
4
|
import type { PermissionMode } from "./protocol.js";
|
|
5
|
+
export type TuiThemeName = "forest" | "sage" | "dusk";
|
|
5
6
|
export interface ProfileConfiguration {
|
|
6
7
|
readonly provider?: LocalEndpointKind;
|
|
7
8
|
readonly baseUrl?: string;
|
|
@@ -13,6 +14,8 @@ export interface ProfileConfiguration {
|
|
|
13
14
|
/** Name of an environment variable containing a local endpoint token. */
|
|
14
15
|
readonly apiKeyEnv?: string;
|
|
15
16
|
readonly mcpServers?: McpServerConfigurations;
|
|
17
|
+
/** Visual preset used by the terminal UI. */
|
|
18
|
+
readonly tuiTheme?: TuiThemeName;
|
|
16
19
|
}
|
|
17
20
|
export interface HarnessConfiguration extends ProfileConfiguration {
|
|
18
21
|
readonly defaultProfile?: string;
|
|
@@ -26,6 +29,7 @@ export interface ConfigurationOverrides extends ProfileConfiguration {
|
|
|
26
29
|
export interface ResolvedConfiguration extends ClientConfiguration {
|
|
27
30
|
readonly profile?: string;
|
|
28
31
|
readonly permission: PermissionMode;
|
|
32
|
+
readonly tuiTheme?: TuiThemeName;
|
|
29
33
|
readonly paths: {
|
|
30
34
|
readonly user: string;
|
|
31
35
|
readonly workspace: string;
|
|
@@ -43,6 +47,8 @@ export declare function resolveConfiguration(options: {
|
|
|
43
47
|
readonly paths?: ConfigurationPaths;
|
|
44
48
|
}): Promise<ResolvedConfiguration>;
|
|
45
49
|
export declare function initializeWorkspaceConfiguration(workspaceRoot: string, paths?: ConfigurationPaths): Promise<string>;
|
|
50
|
+
/** Saves a selected local-model profile in the user configuration file. */
|
|
51
|
+
export declare function saveUserProfile(workspaceRoot: string, profileName: string, profile: ProfileConfiguration, paths?: ConfigurationPaths): Promise<string>;
|
|
46
52
|
export declare function parseConfigurationOverrides(arguments_: readonly string[]): {
|
|
47
53
|
readonly overrides: ConfigurationOverrides;
|
|
48
54
|
readonly rest: readonly string[];
|
package/dist/config.js
CHANGED
|
@@ -13,6 +13,9 @@ function validMode(value) {
|
|
|
13
13
|
function validPermission(value) {
|
|
14
14
|
return value === "ask" || value === "auto-read" || value === "auto-all" ? value : undefined;
|
|
15
15
|
}
|
|
16
|
+
function validTuiTheme(value) {
|
|
17
|
+
return value === "forest" || value === "sage" || value === "dusk" ? value : undefined;
|
|
18
|
+
}
|
|
16
19
|
function object(value) {
|
|
17
20
|
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
18
21
|
}
|
|
@@ -29,7 +32,8 @@ function parseProfile(value) {
|
|
|
29
32
|
internetAccess: typeof source.internetAccess === "boolean" ? source.internetAccess : undefined,
|
|
30
33
|
systemPrompt: typeof source.systemPrompt === "string" ? source.systemPrompt : undefined,
|
|
31
34
|
apiKeyEnv: typeof source.apiKeyEnv === "string" ? source.apiKeyEnv : undefined,
|
|
32
|
-
mcpServers: source.mcpServers === undefined ? undefined : parseMcpServerConfigurations(source.mcpServers)
|
|
35
|
+
mcpServers: source.mcpServers === undefined ? undefined : parseMcpServerConfigurations(source.mcpServers),
|
|
36
|
+
tuiTheme: validTuiTheme(source.tuiTheme)
|
|
33
37
|
};
|
|
34
38
|
}
|
|
35
39
|
function parseConfiguration(value) {
|
|
@@ -79,7 +83,8 @@ function environmentConfiguration(environment) {
|
|
|
79
83
|
: environment.TRUSS_HARNESS_INTERNET_ACCESS === "true" || environment.TRUSS_HARNESS_INTERNET_ACCESS === "1",
|
|
80
84
|
systemPrompt: environment.TRUSS_HARNESS_SYSTEM_PROMPT,
|
|
81
85
|
apiKeyEnv: environment.TRUSS_HARNESS_API_KEY ? "TRUSS_HARNESS_API_KEY" : undefined,
|
|
82
|
-
mcpServers
|
|
86
|
+
mcpServers,
|
|
87
|
+
tuiTheme: validTuiTheme(environment.TRUSS_HARNESS_TUI_THEME)
|
|
83
88
|
};
|
|
84
89
|
}
|
|
85
90
|
const profileKeys = [
|
|
@@ -91,7 +96,8 @@ const profileKeys = [
|
|
|
91
96
|
"internetAccess",
|
|
92
97
|
"systemPrompt",
|
|
93
98
|
"apiKeyEnv",
|
|
94
|
-
"mcpServers"
|
|
99
|
+
"mcpServers",
|
|
100
|
+
"tuiTheme"
|
|
95
101
|
];
|
|
96
102
|
function mergeProfiles(...sources) {
|
|
97
103
|
const merged = {};
|
|
@@ -165,6 +171,7 @@ export async function resolveConfiguration(options) {
|
|
|
165
171
|
systemPrompt: merged.systemPrompt,
|
|
166
172
|
mcpServers,
|
|
167
173
|
profile,
|
|
174
|
+
tuiTheme: merged.tuiTheme,
|
|
168
175
|
paths
|
|
169
176
|
};
|
|
170
177
|
}
|
|
@@ -208,6 +215,20 @@ export async function initializeWorkspaceConfiguration(workspaceRoot, paths = co
|
|
|
208
215
|
}, null, 2)}\n`, "utf8");
|
|
209
216
|
return paths.workspace;
|
|
210
217
|
}
|
|
218
|
+
/** Saves a selected local-model profile in the user configuration file. */
|
|
219
|
+
export async function saveUserProfile(workspaceRoot, profileName, profile, paths = configurationPaths(workspaceRoot)) {
|
|
220
|
+
const existing = await readConfiguration(paths.user);
|
|
221
|
+
await mkdir(dirname(paths.user), { recursive: true });
|
|
222
|
+
await writeFile(paths.user, JSON.stringify({
|
|
223
|
+
...existing,
|
|
224
|
+
defaultProfile: profileName,
|
|
225
|
+
profiles: {
|
|
226
|
+
...existing.profiles,
|
|
227
|
+
[profileName]: profile
|
|
228
|
+
}
|
|
229
|
+
}, null, 2) + "\n", "utf8");
|
|
230
|
+
return paths.user;
|
|
231
|
+
}
|
|
211
232
|
export function parseConfigurationOverrides(arguments_) {
|
|
212
233
|
const overrides = {};
|
|
213
234
|
const rest = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truss-harness/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Command-line client and runtime service for Truss.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
|
-
"truss-cli": "
|
|
17
|
+
"truss-cli": "dist/bin.js"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsc -b",
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -3,9 +3,8 @@ if (process.env.npm_config_global === "true" || process.env.npm_config_global ==
|
|
|
3
3
|
Truss CLI installed.
|
|
4
4
|
|
|
5
5
|
Next steps:
|
|
6
|
-
truss-cli
|
|
7
|
-
truss-cli
|
|
8
|
-
truss-cli chat "Explain this workspace"
|
|
6
|
+
truss-cli setup
|
|
7
|
+
truss-cli chat --mode edit
|
|
9
8
|
|
|
10
9
|
Run truss-cli help for modes, permissions, configuration, and examples.
|
|
11
10
|
`);
|