woopcode 0.1.0 → 0.2.0

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.
@@ -1,20 +1,13 @@
1
1
  import { Command } from "commander";
2
- import {
3
- buildRepositoryContext,
4
- getConfig,
5
- getConversation,
6
- saveConversation,
7
- } from "../config/config";
8
- import { createProviderClient } from "../config/client";
9
- import type { Message } from "../config/types";
2
+ import { getConfig } from "../config/config";
10
3
  import type { AgentCallbacks } from "../config/types";
11
- import { agentLoop } from "../config/runtime";
12
4
  import { App, store } from "../tui/src";
13
5
  import { render } from "ink";
14
6
  import { AgentController } from "./agentController";
15
7
  import { ACTIVE_PROVIDER_MODELS } from "../config/client";
16
8
  import type { HomeScreenData } from "../tui/src/components/HomeScreen";
17
9
  import { ensureProviderConfigured } from "../onboarding";
10
+ import { registerCommands } from "./slash";
18
11
 
19
12
  export const agentCommand = new Command("agent")
20
13
  .description("Runs the agent")
@@ -23,6 +16,9 @@ export const agentCommand = new Command("agent")
23
16
 
24
17
  /** Runs the interactive agent from either `woopcode` or `woopcode agent`. */
25
18
  export async function runAgent() {
19
+ // Register slash commands
20
+ registerCommands();
21
+
26
22
  // Ensure provider is configured (launches onboarding if needed)
27
23
  await ensureProviderConfigured();
28
24
 
@@ -32,48 +28,48 @@ export async function runAgent() {
32
28
  const apiKey = config.providers[provider].apiKey;
33
29
 
34
30
  const callbacks: AgentCallbacks = {
35
- onStatus(status) {
36
- if (cancelStatusTimeout) {
37
- clearTimeout(cancelStatusTimeout);
38
- cancelStatusTimeout = undefined;
39
- }
40
- store.setStatus(status);
41
- },
42
-
43
- onToolStart(tool) {
44
- store.finishAssistantMessage();
45
- store.startTool(tool);
46
- store.setStatus(`Running ${tool.name}...`);
47
- },
48
-
49
- onToolFinish(tool) {
50
- store.finishTool(tool.id);
51
- store.setStatus("Thinking...");
52
- },
53
-
54
- onText(text) {
55
- store.appendAssistantText(text);
56
- },
57
-
58
- onDone() {
59
- //console.log("onDone received");
60
- store.finishAssistantMessage();
31
+ onStatus(status) {
32
+ if (cancelStatusTimeout) {
33
+ clearTimeout(cancelStatusTimeout);
34
+ cancelStatusTimeout = undefined;
35
+ }
36
+ store.setStatus(status);
37
+ },
38
+
39
+ onToolStart(tool) {
40
+ store.finishAssistantMessage();
41
+ store.startTool(tool);
42
+ store.setStatus(`Running ${tool.name}...`);
43
+ },
44
+
45
+ onToolFinish(tool) {
46
+ store.finishTool(tool.id);
47
+ store.setStatus("Thinking...");
48
+ },
49
+
50
+ onText(text) {
51
+ store.appendAssistantText(text);
52
+ },
53
+
54
+ onDone() {
55
+ //console.log("onDone received");
56
+ store.finishAssistantMessage();
57
+ store.setStatus("Ready");
58
+ },
59
+
60
+ onError(error) {
61
+ store.finishAssistantMessage();
62
+ store.setStatus(`Error: ${error.message}`);
63
+ },
64
+
65
+ onCancel() {
66
+ store.finishAssistantMessage();
67
+ store.setStatus("Cancelled");
68
+
69
+ cancelStatusTimeout = setTimeout(() => {
61
70
  store.setStatus("Ready");
62
- },
63
-
64
- onError(error) {
65
- store.finishAssistantMessage();
66
- store.setStatus(`Error: ${error.message}`);
67
- },
68
-
69
- onCancel() {
70
- store.finishAssistantMessage();
71
- store.setStatus("Cancelled");
72
-
73
- cancelStatusTimeout = setTimeout(() => {
74
- store.setStatus("Ready");
75
- }, 1000);
76
- },
71
+ }, 1000);
72
+ },
77
73
  };
78
74
  const controller = new AgentController(provider, apiKey, callbacks);
79
75
  await controller.initialize();
@@ -102,7 +98,8 @@ export async function runAgent() {
102
98
  }
103
99
 
104
100
  async function buildHomeScreen(provider: string): Promise<HomeScreenData> {
105
- const repository = process.cwd().split("/").filter(Boolean).at(-1) ?? "workspace";
101
+ const repository =
102
+ process.cwd().split("/").filter(Boolean).at(-1) ?? "workspace";
106
103
  const branch = await getBranch();
107
104
  const providerLabel = provider === "google" ? "Gemini" : titleCase(provider);
108
105
 
@@ -117,7 +114,15 @@ async function buildHomeScreen(provider: string): Promise<HomeScreenData> {
117
114
  "Optimize performance",
118
115
  "Add authentication",
119
116
  ],
120
- capabilities: ["Build", "Review", "Explain", "Refactor", "Debug", "Test", "Document"],
117
+ capabilities: [
118
+ "Build",
119
+ "Review",
120
+ "Explain",
121
+ "Refactor",
122
+ "Debug",
123
+ "Test",
124
+ "Document",
125
+ ],
121
126
  repository,
122
127
  branch,
123
128
  providerName: providerLabel,
@@ -0,0 +1,104 @@
1
+ # Slash Commands
2
+
3
+ Local commands that execute instantly without invoking the LLM.
4
+
5
+ ## Available Commands
6
+
7
+ ### Session Commands
8
+
9
+ - **`/new`** (aliases: `clear`, `reset`) - Start a new conversation
10
+ - **`/exit`** (aliases: `quit`, `q`) - Exit Woopcode
11
+
12
+ ### Configuration Commands
13
+
14
+ - **`/provider [name]`** (alias: `p`) - Show current provider or switch to another
15
+ - Without arguments: Shows current provider and all available providers
16
+ - With provider name: Switches to the specified provider (if configured)
17
+
18
+ - **`/model`** (alias: `m`) - Show current model and available models
19
+ - Currently read-only; model switching will be added in future versions
20
+
21
+ ### Workspace Commands
22
+
23
+ - **`/workspace`** (alias: `ws`) - Show workspace information
24
+ - Displays: workspace name, path, git branch, file count
25
+
26
+ - **`/status`** (alias: `info`) - Show comprehensive system status
27
+ - Displays: workspace info, provider, model, conversation stats, tools, version
28
+
29
+ ### Other Commands
30
+
31
+ - **`/help`** (aliases: `h`, `?`) - Show all available commands
32
+ - **`/version`** (alias: `v`) - Show Woopcode version
33
+
34
+ ## Discovery Mode
35
+
36
+ Type `/` alone to see a quick list of all available commands.
37
+
38
+ ## Architecture
39
+
40
+ The slash command system consists of:
41
+
42
+ - **Parser** (`parser.ts`) - Parses user input into commands and arguments
43
+ - **Registry** (`registry.ts`) - Manages command registration and lookup
44
+ - **Handler** (`handler.ts`) - Executes commands and handles errors
45
+ - **Commands** (`commands.ts`) - All command implementations
46
+
47
+ ### Adding New Commands
48
+
49
+ 1. Define command in `commands.ts`:
50
+
51
+ ```typescript
52
+ const myCommand: SlashCommand = {
53
+ name: "mycommand",
54
+ aliases: ["mc", "mycmd"],
55
+ description: "Does something useful",
56
+ category: "other", // session | configuration | workspace | other
57
+
58
+ async execute(context, args) {
59
+ // Implementation
60
+ return "Command output";
61
+ }
62
+ };
63
+ ```
64
+
65
+ 2. Register in `registerCommands()`:
66
+
67
+ ```typescript
68
+ export function registerCommands() {
69
+ // ... existing commands
70
+ registry.register(myCommand);
71
+ }
72
+ ```
73
+
74
+ That's it! The command will automatically appear in `/help` and support tab completion.
75
+
76
+ ## Integration
77
+
78
+ Slash commands are intercepted in `tui/src/prompt.tsx` before being sent to the agent:
79
+
80
+ ```typescript
81
+ const result = await handleSlashCommand(prompt, context);
82
+
83
+ if (result.handled) {
84
+ // Command was executed, don't send to LLM
85
+ return;
86
+ }
87
+
88
+ // Otherwise continue to agent
89
+ await controller.run(prompt);
90
+ ```
91
+
92
+ ## Testing
93
+
94
+ Tests are located in `packages/tests/slash/`:
95
+
96
+ - `parser.test.ts` - Parser logic
97
+ - `registry.test.ts` - Command registry
98
+ - `handler.test.ts` - Command execution
99
+
100
+ Run tests:
101
+
102
+ ```bash
103
+ bun test packages/tests/slash/
104
+ ```
@@ -0,0 +1,239 @@
1
+ import type { SlashCommand, SlashCommandContext } from "./types";
2
+ import { registry } from "./registry";
3
+ import {
4
+ getConfig,
5
+ saveConfig,
6
+ getConversation,
7
+ saveConversation,
8
+ } from "../../config/config";
9
+
10
+ // Read version from package.json
11
+ const packageJsonPath = `${import.meta.dir}/../../package.json`;
12
+ const packageJson = await Bun.file(packageJsonPath).json();
13
+ const version = packageJson.version as string;
14
+
15
+ // Read models from models.json
16
+ const modelsJsonPath = `${import.meta.dir}/../../config/models.json`;
17
+ const modelsData = await Bun.file(modelsJsonPath).json();
18
+ const models = modelsData as Array<{
19
+ id: string;
20
+ provider: string;
21
+ name: string;
22
+ contextWindow: number | string;
23
+ }>;
24
+
25
+ // ==================== SESSION COMMANDS ====================
26
+
27
+ const helpCommand: SlashCommand = {
28
+ name: "help",
29
+ aliases: ["h", "?"],
30
+ description: "Show available commands",
31
+ category: "other",
32
+
33
+ async execute(context, args) {
34
+ return registry.generateHelp();
35
+ },
36
+ };
37
+
38
+ const newCommand: SlashCommand = {
39
+ name: "new",
40
+ aliases: ["clear", "reset"],
41
+ description: "Start a new conversation",
42
+ category: "session",
43
+
44
+ async execute(context, args) {
45
+ await saveConversation([]);
46
+ return "Started new conversation";
47
+ },
48
+ };
49
+
50
+ const exitCommand: SlashCommand = {
51
+ name: "exit",
52
+ aliases: ["quit", "q"],
53
+ description: "Exit Woopcode",
54
+ category: "session",
55
+
56
+ async execute(context, args) {
57
+ await context.onExit();
58
+ return "Exiting...";
59
+ },
60
+ };
61
+
62
+ // ==================== CONFIGURATION COMMANDS ====================
63
+
64
+ const providerCommand: SlashCommand = {
65
+ name: "provider",
66
+ aliases: ["p"],
67
+ description: "Show or switch provider",
68
+ category: "configuration",
69
+ usage: "/provider [provider-name]",
70
+
71
+ async execute(context, args) {
72
+ const config = await getConfig();
73
+
74
+ if (args.length === 0) {
75
+ const current = config.defaultProvider;
76
+ const providers = Object.entries(config.providers)
77
+ .map(([name, details]: [string, any]) => {
78
+ const status = details.apiKey ? "✓" : "✗";
79
+ const active = name === current ? "(active)" : "";
80
+ return ` ${status} ${name} ${active}`;
81
+ })
82
+ .join("\n");
83
+
84
+ return `Current Provider: ${current}\n\nAvailable:\n${providers}`;
85
+ }
86
+
87
+ const newProvider = args[0];
88
+ if (!newProvider) {
89
+ return `Provider name required.\nUsage: /provider <provider-name>`;
90
+ }
91
+
92
+ const available = Object.keys(config.providers);
93
+
94
+ if (!available.includes(newProvider)) {
95
+ return `Provider "${newProvider}" not found.\nAvailable: ${available.join(", ")}`;
96
+ }
97
+
98
+ const providerConfig = config.providers[newProvider];
99
+ if (!providerConfig || !providerConfig.apiKey) {
100
+ return `Provider "${newProvider}" not configured.\nRun: woopcode providers login -p ${newProvider}`;
101
+ }
102
+
103
+ config.defaultProvider = newProvider;
104
+ await saveConfig(config);
105
+
106
+ return `Switched to: ${newProvider}`;
107
+ },
108
+ };
109
+
110
+ const modelCommand: SlashCommand = {
111
+ name: "model",
112
+ aliases: ["m"],
113
+ description: "Show current model and available models",
114
+ category: "configuration",
115
+
116
+ async execute(context, args) {
117
+ const config = await getConfig();
118
+ const provider = config.defaultProvider;
119
+
120
+ // Show current model (read-only for now)
121
+ let output = `Current Model: gemini-3.5-flash-lite\n\n`;
122
+
123
+ // List available models for current provider
124
+ const providerModels = models.filter((m) => m.provider === provider);
125
+
126
+ if (providerModels.length > 0) {
127
+ output += `Available models for ${provider}:\n`;
128
+ providerModels.forEach((m) => {
129
+ output += ` ${m.id} - ${m.name}\n`;
130
+ });
131
+ }
132
+
133
+ return output.trim();
134
+ },
135
+ };
136
+
137
+ // ==================== WORKSPACE COMMANDS ====================
138
+
139
+ const workspaceCommand: SlashCommand = {
140
+ name: "workspace",
141
+ aliases: ["ws"],
142
+ description: "Show workspace information",
143
+ category: "workspace",
144
+
145
+ async execute(context, args) {
146
+ const cwd = process.cwd();
147
+ const parts = cwd.split("/").filter(Boolean);
148
+ const repoName = parts[parts.length - 1] ?? "unknown";
149
+
150
+ let branch = "not a git repository";
151
+ try {
152
+ branch =
153
+ (await Bun.$`git branch --show-current`.text()).trim() || "detached";
154
+ } catch {}
155
+
156
+ let fileCount = 0;
157
+ try {
158
+ for await (const entry of new Bun.Glob("**/*").scan(cwd)) {
159
+ if (
160
+ !entry.startsWith("node_modules") &&
161
+ !entry.startsWith(".git") &&
162
+ !entry.startsWith("dist")
163
+ ) {
164
+ fileCount++;
165
+ }
166
+ }
167
+ } catch {}
168
+
169
+ return [
170
+ `Workspace: ${repoName}`,
171
+ `Path: ${cwd}`,
172
+ `Branch: ${branch}`,
173
+ `Files: ${fileCount > 0 ? fileCount : "counting..."}`,
174
+ ].join("\n");
175
+ },
176
+ };
177
+
178
+ const statusCommand: SlashCommand = {
179
+ name: "status",
180
+ aliases: ["info"],
181
+ description: "Show comprehensive system status",
182
+ category: "other",
183
+
184
+ async execute(context, args) {
185
+ const config = await getConfig();
186
+ const conversation = await getConversation();
187
+ const cwd = process.cwd();
188
+ const parts = cwd.split("/").filter(Boolean);
189
+ const repoName = parts[parts.length - 1] ?? "workspace";
190
+
191
+ let branch = "not a git repository";
192
+ try {
193
+ branch =
194
+ (await Bun.$`git branch --show-current`.text()).trim() || "detached";
195
+ } catch {}
196
+
197
+ const provider = config.defaultProvider;
198
+ const providerLabel = provider === "google" ? "Google Gemini" : provider;
199
+
200
+ return [
201
+ `Workspace: ${repoName}`,
202
+ `Path: ${cwd}`,
203
+ `Branch: ${branch}`,
204
+ ``,
205
+ `Provider: ${providerLabel}`,
206
+ `Model: gemini-3.5-flash-lite`,
207
+ ``,
208
+ `Conversation: ${conversation.length} messages`,
209
+ `Tools: 9 registered`,
210
+ `Version: ${version}`,
211
+ ].join("\n");
212
+ },
213
+ };
214
+
215
+ // ==================== OTHER COMMANDS ====================
216
+
217
+ const versionCommand: SlashCommand = {
218
+ name: "version",
219
+ aliases: ["v"],
220
+ description: "Show Woopcode version",
221
+ category: "other",
222
+
223
+ async execute(context, args) {
224
+ return `Woopcode v${version}`;
225
+ },
226
+ };
227
+
228
+ // ==================== REGISTRATION ====================
229
+
230
+ export function registerCommands() {
231
+ registry.register(helpCommand);
232
+ registry.register(newCommand);
233
+ registry.register(exitCommand);
234
+ registry.register(providerCommand);
235
+ registry.register(modelCommand);
236
+ registry.register(workspaceCommand);
237
+ registry.register(statusCommand);
238
+ registry.register(versionCommand);
239
+ }
@@ -0,0 +1,40 @@
1
+ import { parseInput } from "./parser";
2
+ import { registry } from "./registry";
3
+ import type { SlashCommandContext } from "./types";
4
+
5
+ export async function handleSlashCommand(
6
+ input: string,
7
+ context: SlashCommandContext,
8
+ ): Promise<{ handled: boolean; output?: string }> {
9
+ const parsed = parseInput(input);
10
+
11
+ // Discovery mode: show available commands
12
+ if (parsed.type === "discovery") {
13
+ const output = registry.generateDiscoveryList();
14
+ context.onOutput(output);
15
+ return { handled: true, output };
16
+ }
17
+
18
+ if (parsed.type !== "command") {
19
+ return { handled: false };
20
+ }
21
+
22
+ const command = registry.get(parsed.command!);
23
+
24
+ if (!command) {
25
+ const output = `Unknown command "/${parsed.command}"\nRun /help to see available commands.`;
26
+ context.onOutput(output);
27
+ return { handled: true, output };
28
+ }
29
+
30
+ try {
31
+ const output = await command.execute(context, parsed.args!);
32
+ context.onOutput(output);
33
+ return { handled: true, output };
34
+ } catch (error) {
35
+ const message = error instanceof Error ? error.message : String(error);
36
+ const output = `Error: ${message}`;
37
+ context.onOutput(output);
38
+ return { handled: true, output };
39
+ }
40
+ }
@@ -0,0 +1,9 @@
1
+ export { handleSlashCommand } from "./handler";
2
+ export { registry } from "./registry";
3
+ export { registerCommands } from "./commands";
4
+ export { parseInput } from "./parser";
5
+ export type {
6
+ SlashCommand,
7
+ SlashCommandContext,
8
+ ParsedCommand,
9
+ } from "./types";
@@ -0,0 +1,25 @@
1
+ import type { ParsedCommand } from "./types";
2
+
3
+ export function parseInput(input: string): ParsedCommand {
4
+ const trimmed = input.trim();
5
+
6
+ //discovery mode
7
+ if (trimmed === "/") {
8
+ return { type: "discovery", originalInput: input };
9
+ }
10
+
11
+ if (!trimmed.startsWith("/")) {
12
+ return { type: "text", originalInput: input };
13
+ }
14
+
15
+ const parts = trimmed.slice(1).split(/\s+/);
16
+ const command = parts[0]?.toLowerCase() || "";
17
+ const args = parts.slice(1);
18
+
19
+ return {
20
+ type: "command",
21
+ command,
22
+ args,
23
+ originalInput: input,
24
+ };
25
+ }
@@ -0,0 +1,66 @@
1
+ import type { SlashCommand } from "./types";
2
+
3
+ export class SlashCommandRegistry {
4
+ private commands = new Map<string, SlashCommand>();
5
+ private aliases = new Map<string, string>();
6
+
7
+ register(command: SlashCommand): void {
8
+ this.commands.set(command.name, command);
9
+
10
+ if (command.aliases) {
11
+ command.aliases.forEach((alias) => {
12
+ this.aliases.set(alias, command.name);
13
+ });
14
+ }
15
+ }
16
+
17
+ get(nameOrAlias: string): SlashCommand | undefined {
18
+ const name = this.aliases.get(nameOrAlias) ?? nameOrAlias;
19
+ return this.commands.get(name);
20
+ }
21
+
22
+ getAll(): SlashCommand[] {
23
+ return Array.from(this.commands.values());
24
+ }
25
+
26
+ getByCategory(category: string): SlashCommand[] {
27
+ return this.getAll().filter((cmd) => cmd.category === category);
28
+ }
29
+
30
+ // Auto-generated help
31
+ generateHelp(): string {
32
+ const categories = {
33
+ session: "Session",
34
+ configuration: "Configuration",
35
+ workspace: "Workspace",
36
+ other: "Other",
37
+ };
38
+
39
+ let output = "Available Commands:\n\n";
40
+
41
+ for (const [key, label] of Object.entries(categories)) {
42
+ const commands = this.getByCategory(key);
43
+ if (commands.length === 0) continue;
44
+
45
+ output += `${label}:\n`;
46
+ commands.forEach((cmd) => {
47
+ const aliases = cmd.aliases?.length
48
+ ? ` (${cmd.aliases.join(", ")})`
49
+ : "";
50
+ output += ` /${cmd.name}${aliases} - ${cmd.description}\n`;
51
+ });
52
+ output += "\n";
53
+ }
54
+
55
+ return output.trim();
56
+ }
57
+
58
+ // 🔥 Discovery list
59
+ generateDiscoveryList(): string {
60
+ return this.getAll()
61
+ .map((cmd) => `/${cmd.name}`)
62
+ .join(" ");
63
+ }
64
+ }
65
+
66
+ export const registry = new SlashCommandRegistry();
@@ -0,0 +1,23 @@
1
+ import type { AgentController } from "../agentController";
2
+
3
+ export interface SlashCommandContext {
4
+ controller: AgentController;
5
+ onExit: () => Promise<void>;
6
+ onOutput: (message: string) => void;
7
+ }
8
+
9
+ export interface SlashCommand {
10
+ name: string;
11
+ aliases?: string[];
12
+ description: string;
13
+ category: "session" | "configuration" | "workspace" | "other";
14
+ usage?: string;
15
+ execute(context: SlashCommandContext, args: string[]): Promise<string>;
16
+ }
17
+
18
+ export interface ParsedCommand {
19
+ type: "command" | "text" | "discovery";
20
+ command?: string;
21
+ args?: string[];
22
+ originalInput?: string;
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "woopcode",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "An autonomous AI coding assistant built for the terminal",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -62,8 +62,7 @@
62
62
  "ink-spinner": "^5.0.0",
63
63
  "ink-text-input": "^6.0.0",
64
64
  "marked": "^18.0.7",
65
- "react": "^19.2.7",
66
- "woopcode": "file:woopcode-0.1.0.tgz"
65
+ "react": "^19.2.7"
67
66
  },
68
67
  "devDependencies": {
69
68
  "@stryker-mutator/core": "^9.6.1",
@@ -2,6 +2,8 @@ import { Box, Text, useInput } from "ink";
2
2
  import TextInput from "ink-text-input";
3
3
  import { useRef } from "react";
4
4
  import type { AgentController } from "../../commands/agentController";
5
+ import { handleSlashCommand } from "../../commands/slash";
6
+ import { store } from "./store/ui-store";
5
7
 
6
8
  interface PromptProps {
7
9
  controller: AgentController;
@@ -42,6 +44,24 @@ export function Prompt({
42
44
  if (!prompt || controller.isBusy()) {
43
45
  return;
44
46
  }
47
+
48
+ // 🔥 Slash command interception
49
+ const context = {
50
+ controller,
51
+ onExit,
52
+ onOutput: (message: string) => {
53
+ store.addSystemMessage(message);
54
+ },
55
+ };
56
+
57
+ const result = await handleSlashCommand(prompt, context);
58
+
59
+ if (result.handled) {
60
+ onValueChange("");
61
+ return;
62
+ }
63
+
64
+ // Original flow
45
65
  if (prompt === "/exit") {
46
66
  await onExit();
47
67
  return;
@@ -59,6 +59,22 @@ export class UIStore {
59
59
  this.emit();
60
60
  }
61
61
 
62
+ addSystemMessage(content: string) {
63
+ this.state = {
64
+ ...this.state,
65
+ timeline: [
66
+ ...this.state.timeline,
67
+ {
68
+ id: crypto.randomUUID(),
69
+ type: "system",
70
+ content,
71
+ },
72
+ ],
73
+ };
74
+
75
+ this.emit();
76
+ }
77
+
62
78
  startTool(tool: ToolCall) {
63
79
  this.state = {
64
80
  ...this.state,
@@ -47,6 +47,18 @@ function TimelineItem({ item }: { item: TimeLineItem }) {
47
47
  </Box>
48
48
  );
49
49
 
50
+ case "system":
51
+ return (
52
+ <Box flexDirection="column" marginBottom={1}>
53
+ <Text bold color="yellow">
54
+ System
55
+ </Text>
56
+ <Box paddingLeft={1}>
57
+ <Text>{item.content}</Text>
58
+ </Box>
59
+ </Box>
60
+ );
61
+
50
62
  case "tool": {
51
63
  const label = toolLabel(item.name);
52
64
  const target = formatToolArgument(item.arguments);
package/tui/src/types.ts CHANGED
@@ -10,6 +10,11 @@ export type TimeLineItem =
10
10
  content: string;
11
11
  streaming: boolean;
12
12
  }
13
+ | {
14
+ id: string;
15
+ type: "system";
16
+ content: string;
17
+ }
13
18
  | {
14
19
  id: string;
15
20
  type: "tool";