deepagentsdk 0.9.2
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 +21 -0
- package/README.md +159 -0
- package/package.json +95 -0
- package/src/agent.ts +1230 -0
- package/src/backends/composite.ts +273 -0
- package/src/backends/filesystem.ts +692 -0
- package/src/backends/index.ts +22 -0
- package/src/backends/local-sandbox.ts +175 -0
- package/src/backends/persistent.ts +593 -0
- package/src/backends/sandbox.ts +510 -0
- package/src/backends/state.ts +244 -0
- package/src/backends/utils.ts +287 -0
- package/src/checkpointer/file-saver.ts +98 -0
- package/src/checkpointer/index.ts +5 -0
- package/src/checkpointer/kv-saver.ts +82 -0
- package/src/checkpointer/memory-saver.ts +82 -0
- package/src/checkpointer/types.ts +125 -0
- package/src/cli/components/ApiKeyInput.tsx +300 -0
- package/src/cli/components/FilePreview.tsx +237 -0
- package/src/cli/components/Input.tsx +277 -0
- package/src/cli/components/Message.tsx +93 -0
- package/src/cli/components/ModelSelection.tsx +338 -0
- package/src/cli/components/SlashMenu.tsx +101 -0
- package/src/cli/components/StatusBar.tsx +89 -0
- package/src/cli/components/Subagent.tsx +91 -0
- package/src/cli/components/TodoList.tsx +133 -0
- package/src/cli/components/ToolApproval.tsx +70 -0
- package/src/cli/components/ToolCall.tsx +144 -0
- package/src/cli/components/ToolCallSummary.tsx +175 -0
- package/src/cli/components/Welcome.tsx +75 -0
- package/src/cli/components/index.ts +24 -0
- package/src/cli/hooks/index.ts +12 -0
- package/src/cli/hooks/useAgent.ts +933 -0
- package/src/cli/index.tsx +1066 -0
- package/src/cli/theme.ts +205 -0
- package/src/cli/utils/model-list.ts +365 -0
- package/src/constants/errors.ts +29 -0
- package/src/constants/limits.ts +195 -0
- package/src/index.ts +176 -0
- package/src/middleware/agent-memory.ts +330 -0
- package/src/prompts.ts +196 -0
- package/src/skills/index.ts +2 -0
- package/src/skills/load.ts +191 -0
- package/src/skills/types.ts +53 -0
- package/src/tools/execute.ts +167 -0
- package/src/tools/filesystem.ts +418 -0
- package/src/tools/index.ts +39 -0
- package/src/tools/subagent.ts +443 -0
- package/src/tools/todos.ts +101 -0
- package/src/tools/web.ts +567 -0
- package/src/types/backend.ts +177 -0
- package/src/types/core.ts +220 -0
- package/src/types/events.ts +429 -0
- package/src/types/index.ts +94 -0
- package/src/types/structured-output.ts +43 -0
- package/src/types/subagent.ts +96 -0
- package/src/types.ts +22 -0
- package/src/utils/approval.ts +213 -0
- package/src/utils/events.ts +416 -0
- package/src/utils/eviction.ts +181 -0
- package/src/utils/index.ts +34 -0
- package/src/utils/model-parser.ts +38 -0
- package/src/utils/patch-tool-calls.ts +233 -0
- package/src/utils/project-detection.ts +32 -0
- package/src/utils/summarization.ts +254 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Todo list panel component.
|
|
3
|
+
*/
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { Box, Text } from "ink";
|
|
6
|
+
import { Badge } from "@inkjs/ui";
|
|
7
|
+
import { emoji, colors } from "../theme.js";
|
|
8
|
+
import type { TodoItem } from "../../types.js";
|
|
9
|
+
|
|
10
|
+
interface TodoListProps {
|
|
11
|
+
todos: TodoItem[];
|
|
12
|
+
/** Whether to show as a full panel with border */
|
|
13
|
+
showPanel?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function TodoList({
|
|
17
|
+
todos,
|
|
18
|
+
showPanel = true,
|
|
19
|
+
}: TodoListProps): React.ReactElement {
|
|
20
|
+
const completed = todos.filter((t) => t.status === "completed").length;
|
|
21
|
+
const inProgress = todos.filter((t) => t.status === "in_progress").length;
|
|
22
|
+
const pending = todos.filter((t) => t.status === "pending").length;
|
|
23
|
+
const total = todos.length;
|
|
24
|
+
|
|
25
|
+
const content = (
|
|
26
|
+
<Box flexDirection="column">
|
|
27
|
+
<Box marginBottom={1}>
|
|
28
|
+
<Text bold color={colors.info}>
|
|
29
|
+
{emoji.todo} Todo List
|
|
30
|
+
</Text>
|
|
31
|
+
<Text dimColor>
|
|
32
|
+
{" "}
|
|
33
|
+
({completed}/{total} done)
|
|
34
|
+
</Text>
|
|
35
|
+
</Box>
|
|
36
|
+
|
|
37
|
+
{todos.length === 0 ? (
|
|
38
|
+
<Box paddingLeft={2}>
|
|
39
|
+
<Text dimColor>No todos yet.</Text>
|
|
40
|
+
</Box>
|
|
41
|
+
) : (
|
|
42
|
+
<Box flexDirection="column">
|
|
43
|
+
{todos.map((todo) => (
|
|
44
|
+
<TodoItemRow key={todo.id} todo={todo} />
|
|
45
|
+
))}
|
|
46
|
+
</Box>
|
|
47
|
+
)}
|
|
48
|
+
|
|
49
|
+
{todos.length > 0 && (
|
|
50
|
+
<Box marginTop={1} gap={2}>
|
|
51
|
+
{inProgress > 0 && (
|
|
52
|
+
<Text color={colors.warning}>{inProgress} in progress</Text>
|
|
53
|
+
)}
|
|
54
|
+
{pending > 0 && <Text dimColor>{pending} pending</Text>}
|
|
55
|
+
{completed > 0 && (
|
|
56
|
+
<Text color={colors.success}>{completed} completed</Text>
|
|
57
|
+
)}
|
|
58
|
+
</Box>
|
|
59
|
+
)}
|
|
60
|
+
</Box>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (!showPanel) {
|
|
64
|
+
return content;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Box
|
|
69
|
+
flexDirection="column"
|
|
70
|
+
borderStyle="single"
|
|
71
|
+
borderColor={colors.muted}
|
|
72
|
+
paddingX={2}
|
|
73
|
+
paddingY={1}
|
|
74
|
+
marginY={1}
|
|
75
|
+
>
|
|
76
|
+
{content}
|
|
77
|
+
</Box>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface TodoItemRowProps {
|
|
82
|
+
todo: TodoItem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function TodoItemRow({ todo }: TodoItemRowProps): React.ReactElement {
|
|
86
|
+
const statusEmoji = emoji[todo.status] || "•";
|
|
87
|
+
const isFinished = todo.status === "completed" || todo.status === "cancelled";
|
|
88
|
+
|
|
89
|
+
const badgeColor = {
|
|
90
|
+
pending: "gray" as const,
|
|
91
|
+
in_progress: "yellow" as const,
|
|
92
|
+
completed: "green" as const,
|
|
93
|
+
cancelled: "red" as const,
|
|
94
|
+
}[todo.status];
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<Box paddingLeft={2}>
|
|
98
|
+
<Text>{statusEmoji} </Text>
|
|
99
|
+
<Badge color={badgeColor}>
|
|
100
|
+
{todo.status.replace("_", " ")}
|
|
101
|
+
</Badge>
|
|
102
|
+
<Text> </Text>
|
|
103
|
+
{isFinished ? (
|
|
104
|
+
<Text strikethrough dimColor>
|
|
105
|
+
{todo.content}
|
|
106
|
+
</Text>
|
|
107
|
+
) : (
|
|
108
|
+
<Text>{todo.content}</Text>
|
|
109
|
+
)}
|
|
110
|
+
</Box>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Compact todo change notification.
|
|
116
|
+
*/
|
|
117
|
+
interface TodosChangedProps {
|
|
118
|
+
todos: TodoItem[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function TodosChanged({ todos }: TodosChangedProps): React.ReactElement {
|
|
122
|
+
const completed = todos.filter((t) => t.status === "completed").length;
|
|
123
|
+
const total = todos.length;
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<Box marginY={1}>
|
|
127
|
+
<Text color={colors.info}>
|
|
128
|
+
{emoji.todo} Todos updated ({completed}/{total} done)
|
|
129
|
+
</Text>
|
|
130
|
+
</Box>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool approval component for interactive approval flow.
|
|
3
|
+
*/
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { Box, Text, useInput } from "ink";
|
|
6
|
+
|
|
7
|
+
interface ToolApprovalProps {
|
|
8
|
+
toolName: string;
|
|
9
|
+
args: unknown;
|
|
10
|
+
onApprove: () => void;
|
|
11
|
+
onDeny: () => void;
|
|
12
|
+
onApproveAll?: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function ToolApproval({
|
|
16
|
+
toolName,
|
|
17
|
+
args,
|
|
18
|
+
onApprove,
|
|
19
|
+
onDeny,
|
|
20
|
+
onApproveAll,
|
|
21
|
+
}: ToolApprovalProps): React.ReactElement {
|
|
22
|
+
useInput((input, key) => {
|
|
23
|
+
if (input === "y" || input === "Y") {
|
|
24
|
+
onApprove();
|
|
25
|
+
} else if (input === "n" || input === "N" || key.escape) {
|
|
26
|
+
onDeny();
|
|
27
|
+
} else if ((input === "a" || input === "A") && onApproveAll) {
|
|
28
|
+
onApproveAll();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Format args for display (truncate if too long)
|
|
33
|
+
const argsDisplay = JSON.stringify(args, null, 2);
|
|
34
|
+
const truncatedArgs =
|
|
35
|
+
argsDisplay.length > 500
|
|
36
|
+
? argsDisplay.slice(0, 500) + "\n... (truncated)"
|
|
37
|
+
: argsDisplay;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Box
|
|
41
|
+
flexDirection="column"
|
|
42
|
+
borderStyle="round"
|
|
43
|
+
borderColor="yellow"
|
|
44
|
+
paddingX={1}
|
|
45
|
+
marginY={1}
|
|
46
|
+
>
|
|
47
|
+
<Text bold color="yellow">
|
|
48
|
+
🛑 Tool Approval Required
|
|
49
|
+
</Text>
|
|
50
|
+
<Text>
|
|
51
|
+
Tool: <Text bold>{toolName}</Text>
|
|
52
|
+
</Text>
|
|
53
|
+
<Box marginTop={1}>
|
|
54
|
+
<Text dimColor>Arguments:</Text>
|
|
55
|
+
</Box>
|
|
56
|
+
<Text>{truncatedArgs}</Text>
|
|
57
|
+
<Box marginTop={1}>
|
|
58
|
+
<Text>
|
|
59
|
+
Press <Text bold color="green">[Y]</Text> to approve,{" "}
|
|
60
|
+
<Text bold color="red">[N]</Text> to deny
|
|
61
|
+
{onApproveAll && (
|
|
62
|
+
<>
|
|
63
|
+
, <Text bold color="blue">[A]</Text> to approve all (enable auto-approve)
|
|
64
|
+
</>
|
|
65
|
+
)}
|
|
66
|
+
</Text>
|
|
67
|
+
</Box>
|
|
68
|
+
</Box>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool call display component with spinner.
|
|
3
|
+
* Clean, minimal design.
|
|
4
|
+
*/
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { Box, Text } from "ink";
|
|
7
|
+
import { Spinner, StatusMessage } from "@inkjs/ui";
|
|
8
|
+
import { colors } from "../theme.js";
|
|
9
|
+
|
|
10
|
+
interface ToolCallProps {
|
|
11
|
+
/** Tool name being called */
|
|
12
|
+
toolName: string;
|
|
13
|
+
/** Whether the tool is currently executing */
|
|
14
|
+
isExecuting?: boolean;
|
|
15
|
+
/** Tool arguments (optional, for display) */
|
|
16
|
+
args?: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function ToolCall({
|
|
20
|
+
toolName,
|
|
21
|
+
isExecuting = true,
|
|
22
|
+
}: ToolCallProps): React.ReactElement {
|
|
23
|
+
if (isExecuting) {
|
|
24
|
+
return (
|
|
25
|
+
<Box>
|
|
26
|
+
<Spinner label={toolName} />
|
|
27
|
+
</Box>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Box>
|
|
33
|
+
<Text color={colors.success}>✓</Text>
|
|
34
|
+
<Text> {toolName}</Text>
|
|
35
|
+
</Box>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Tool result display.
|
|
41
|
+
*/
|
|
42
|
+
interface ToolResultProps {
|
|
43
|
+
toolName: string;
|
|
44
|
+
result: unknown;
|
|
45
|
+
maxLength?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function ToolResult({
|
|
49
|
+
toolName,
|
|
50
|
+
result,
|
|
51
|
+
maxLength = 100,
|
|
52
|
+
}: ToolResultProps): React.ReactElement {
|
|
53
|
+
let resultStr = String(result);
|
|
54
|
+
if (resultStr.length > maxLength) {
|
|
55
|
+
resultStr = resultStr.substring(0, maxLength) + "...";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<Box>
|
|
60
|
+
<Text color={colors.success}>✓ </Text>
|
|
61
|
+
<Text color={colors.tool}>{toolName}</Text>
|
|
62
|
+
<Text dimColor> → {resultStr}</Text>
|
|
63
|
+
</Box>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Step indicator for multi-step operations.
|
|
69
|
+
*/
|
|
70
|
+
interface StepIndicatorProps {
|
|
71
|
+
stepNumber: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function StepIndicator({
|
|
75
|
+
stepNumber,
|
|
76
|
+
}: StepIndicatorProps): React.ReactElement {
|
|
77
|
+
return (
|
|
78
|
+
<Box>
|
|
79
|
+
<Text dimColor>step {stepNumber}</Text>
|
|
80
|
+
</Box>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Thinking indicator with animated spinner.
|
|
86
|
+
*/
|
|
87
|
+
export function ThinkingIndicator(): React.ReactElement {
|
|
88
|
+
return (
|
|
89
|
+
<Box>
|
|
90
|
+
<Text color={colors.warning}>● </Text>
|
|
91
|
+
<Spinner label="" />
|
|
92
|
+
</Box>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Done indicator.
|
|
98
|
+
*/
|
|
99
|
+
interface DoneIndicatorProps {
|
|
100
|
+
todosCompleted: number;
|
|
101
|
+
todosTotal: number;
|
|
102
|
+
filesCount: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function DoneIndicator({
|
|
106
|
+
todosCompleted,
|
|
107
|
+
todosTotal,
|
|
108
|
+
filesCount,
|
|
109
|
+
}: DoneIndicatorProps): React.ReactElement {
|
|
110
|
+
const hasTodos = todosTotal > 0;
|
|
111
|
+
const hasFiles = filesCount > 0;
|
|
112
|
+
|
|
113
|
+
if (!hasTodos && !hasFiles) {
|
|
114
|
+
return <></>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<Box>
|
|
119
|
+
<Text dimColor>
|
|
120
|
+
{hasTodos && `${todosCompleted}/${todosTotal} tasks`}
|
|
121
|
+
{hasTodos && hasFiles && " · "}
|
|
122
|
+
{hasFiles && `${filesCount} files`}
|
|
123
|
+
</Text>
|
|
124
|
+
</Box>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Error display.
|
|
130
|
+
*/
|
|
131
|
+
interface ErrorDisplayProps {
|
|
132
|
+
error: Error | string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function ErrorDisplay({ error }: ErrorDisplayProps): React.ReactElement {
|
|
136
|
+
const message = error instanceof Error ? error.message : error;
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<Box>
|
|
140
|
+
<Text color={colors.error}>✗ {message}</Text>
|
|
141
|
+
</Box>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collapsible tool call summary component.
|
|
3
|
+
* Shows a collapsed summary of tool calls that can be expanded to see details.
|
|
4
|
+
*/
|
|
5
|
+
import React, { useState } from "react";
|
|
6
|
+
import { Box, Text, useInput } from "ink";
|
|
7
|
+
import { colors, emoji } from "../theme.js";
|
|
8
|
+
import type { ToolCallData } from "./Message.js";
|
|
9
|
+
|
|
10
|
+
interface ToolCallSummaryProps {
|
|
11
|
+
/** Array of tool calls to display */
|
|
12
|
+
toolCalls: ToolCallData[];
|
|
13
|
+
/** Whether this summary is interactive (can be expanded) */
|
|
14
|
+
interactive?: boolean;
|
|
15
|
+
/** Initial expanded state */
|
|
16
|
+
defaultExpanded?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Format a tool result for display.
|
|
21
|
+
*/
|
|
22
|
+
function formatResult(result: unknown, maxLength = 60): string {
|
|
23
|
+
if (result === undefined || result === null) {
|
|
24
|
+
return "done";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let str: string;
|
|
28
|
+
if (typeof result === "string") {
|
|
29
|
+
str = result;
|
|
30
|
+
} else if (typeof result === "object") {
|
|
31
|
+
try {
|
|
32
|
+
str = JSON.stringify(result);
|
|
33
|
+
} catch {
|
|
34
|
+
str = String(result);
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
str = String(result);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Remove newlines and extra whitespace
|
|
41
|
+
str = str.replace(/\s+/g, " ").trim();
|
|
42
|
+
|
|
43
|
+
if (str.length > maxLength) {
|
|
44
|
+
return str.slice(0, maxLength - 3) + "...";
|
|
45
|
+
}
|
|
46
|
+
return str;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Format tool arguments for display.
|
|
51
|
+
*/
|
|
52
|
+
function formatArgs(args: unknown): string {
|
|
53
|
+
if (!args) return "";
|
|
54
|
+
|
|
55
|
+
if (typeof args === "object") {
|
|
56
|
+
const obj = args as Record<string, unknown>;
|
|
57
|
+
const keys = Object.keys(obj);
|
|
58
|
+
if (keys.length === 0) return "";
|
|
59
|
+
|
|
60
|
+
// Show first 2 key-value pairs
|
|
61
|
+
const preview = keys.slice(0, 2).map(k => {
|
|
62
|
+
const v = obj[k];
|
|
63
|
+
const valueStr = typeof v === "string"
|
|
64
|
+
? (v.length > 20 ? v.slice(0, 17) + "..." : v)
|
|
65
|
+
: String(v).slice(0, 20);
|
|
66
|
+
return `${k}: ${valueStr}`;
|
|
67
|
+
}).join(", ");
|
|
68
|
+
|
|
69
|
+
if (keys.length > 2) {
|
|
70
|
+
return `(${preview}, +${keys.length - 2} more)`;
|
|
71
|
+
}
|
|
72
|
+
return `(${preview})`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return String(args).slice(0, 30);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function ToolCallSummary({
|
|
79
|
+
toolCalls,
|
|
80
|
+
interactive = false,
|
|
81
|
+
defaultExpanded = false,
|
|
82
|
+
}: ToolCallSummaryProps): React.ReactElement {
|
|
83
|
+
const [expanded, setExpanded] = useState(defaultExpanded);
|
|
84
|
+
|
|
85
|
+
// Allow toggling with Enter key when interactive
|
|
86
|
+
useInput(
|
|
87
|
+
(input, key) => {
|
|
88
|
+
if (key.return && interactive) {
|
|
89
|
+
setExpanded((prev) => !prev);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{ isActive: interactive }
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (toolCalls.length === 0) {
|
|
96
|
+
return <></>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const successCount = toolCalls.filter((tc) => tc.status === "success").length;
|
|
100
|
+
const errorCount = toolCalls.filter((tc) => tc.status === "error").length;
|
|
101
|
+
|
|
102
|
+
// Collapsed view
|
|
103
|
+
if (!expanded) {
|
|
104
|
+
return (
|
|
105
|
+
<Box paddingLeft={2}>
|
|
106
|
+
<Text dimColor>
|
|
107
|
+
{emoji.tool} {toolCalls.length} tool call{toolCalls.length !== 1 ? "s" : ""}
|
|
108
|
+
{errorCount > 0 && (
|
|
109
|
+
<Text color={colors.error}> ({errorCount} failed)</Text>
|
|
110
|
+
)}
|
|
111
|
+
{interactive && <Text dimColor> [press enter to expand]</Text>}
|
|
112
|
+
</Text>
|
|
113
|
+
</Box>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Expanded view
|
|
118
|
+
return (
|
|
119
|
+
<Box flexDirection="column" paddingLeft={2} marginTop={1}>
|
|
120
|
+
<Box marginBottom={1}>
|
|
121
|
+
<Text dimColor>
|
|
122
|
+
{emoji.tool} {toolCalls.length} tool call{toolCalls.length !== 1 ? "s" : ""}
|
|
123
|
+
{interactive && <Text dimColor> [press enter to collapse]</Text>}
|
|
124
|
+
</Text>
|
|
125
|
+
</Box>
|
|
126
|
+
|
|
127
|
+
{toolCalls.map((tc, index) => (
|
|
128
|
+
<Box key={index} flexDirection="column" marginBottom={1}>
|
|
129
|
+
<Box>
|
|
130
|
+
<Text color={tc.status === "success" ? colors.success : colors.error}>
|
|
131
|
+
{tc.status === "success" ? "✓" : "✗"}
|
|
132
|
+
</Text>
|
|
133
|
+
<Text> </Text>
|
|
134
|
+
<Text color={colors.tool} bold>
|
|
135
|
+
{tc.toolName}
|
|
136
|
+
</Text>
|
|
137
|
+
{tc.args !== undefined && (
|
|
138
|
+
<Text dimColor> {String(formatArgs(tc.args))}</Text>
|
|
139
|
+
)}
|
|
140
|
+
</Box>
|
|
141
|
+
{tc.result !== undefined && (
|
|
142
|
+
<Box paddingLeft={2}>
|
|
143
|
+
<Text dimColor>→ {formatResult(tc.result)}</Text>
|
|
144
|
+
</Box>
|
|
145
|
+
)}
|
|
146
|
+
</Box>
|
|
147
|
+
))}
|
|
148
|
+
</Box>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Inline tool call display for showing during generation.
|
|
154
|
+
*/
|
|
155
|
+
interface InlineToolCallProps {
|
|
156
|
+
toolName: string;
|
|
157
|
+
args?: unknown;
|
|
158
|
+
isExecuting?: boolean;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function InlineToolCall({
|
|
162
|
+
toolName,
|
|
163
|
+
args,
|
|
164
|
+
isExecuting = false,
|
|
165
|
+
}: InlineToolCallProps): React.ReactElement {
|
|
166
|
+
return (
|
|
167
|
+
<Box>
|
|
168
|
+
<Text color={colors.tool}>
|
|
169
|
+
{isExecuting ? "⏳" : "✓"} {toolName}
|
|
170
|
+
</Text>
|
|
171
|
+
{args !== undefined && <Text dimColor> {String(formatArgs(args))}</Text>}
|
|
172
|
+
</Box>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Welcome banner component for the CLI.
|
|
3
|
+
* Clean, minimal design inspired by Claude Code and OpenAI Codex.
|
|
4
|
+
*/
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { Box, Text } from "ink";
|
|
7
|
+
import { colors } from "../theme.js";
|
|
8
|
+
|
|
9
|
+
interface WelcomeProps {
|
|
10
|
+
/** Model name to display */
|
|
11
|
+
model?: string;
|
|
12
|
+
/** Working directory */
|
|
13
|
+
workDir?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function Welcome({ model, workDir }: WelcomeProps): React.ReactElement {
|
|
17
|
+
const displayDir = workDir
|
|
18
|
+
? workDir.replace(process.env.HOME || "", "~")
|
|
19
|
+
: process.cwd().replace(process.env.HOME || "", "~");
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Box
|
|
23
|
+
flexDirection="column"
|
|
24
|
+
borderStyle="single"
|
|
25
|
+
borderColor={colors.muted}
|
|
26
|
+
paddingX={2}
|
|
27
|
+
paddingY={1}
|
|
28
|
+
marginBottom={1}
|
|
29
|
+
>
|
|
30
|
+
<Box>
|
|
31
|
+
<Text bold color={colors.primary}>{">"}_</Text>
|
|
32
|
+
<Text bold> Deep Agent</Text>
|
|
33
|
+
</Box>
|
|
34
|
+
<Box height={1} />
|
|
35
|
+
<Box>
|
|
36
|
+
<Text dimColor>model:</Text>
|
|
37
|
+
<Text> {model || "anthropic/claude-haiku-4-5-20251001"}</Text>
|
|
38
|
+
<Text dimColor> /model to change</Text>
|
|
39
|
+
</Box>
|
|
40
|
+
<Box>
|
|
41
|
+
<Text dimColor>directory:</Text>
|
|
42
|
+
<Text> {displayDir}</Text>
|
|
43
|
+
</Box>
|
|
44
|
+
</Box>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Compact help text shown below the welcome banner.
|
|
50
|
+
*/
|
|
51
|
+
export function WelcomeHint(): React.ReactElement {
|
|
52
|
+
return (
|
|
53
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
54
|
+
<Text dimColor>
|
|
55
|
+
To get started, describe a task or try one of these commands:
|
|
56
|
+
</Text>
|
|
57
|
+
<Box height={1} />
|
|
58
|
+
<Box flexDirection="column">
|
|
59
|
+
<Text>
|
|
60
|
+
<Text color={colors.info}>/help</Text>
|
|
61
|
+
<Text dimColor> - show available commands</Text>
|
|
62
|
+
</Text>
|
|
63
|
+
<Text>
|
|
64
|
+
<Text color={colors.info}>/features</Text>
|
|
65
|
+
<Text dimColor> - show enabled features</Text>
|
|
66
|
+
</Text>
|
|
67
|
+
<Text>
|
|
68
|
+
<Text color={colors.info}>/model</Text>
|
|
69
|
+
<Text dimColor> - change the model</Text>
|
|
70
|
+
</Text>
|
|
71
|
+
</Box>
|
|
72
|
+
</Box>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Export all CLI components.
|
|
3
|
+
*/
|
|
4
|
+
export { Welcome, WelcomeHint } from "./Welcome.js";
|
|
5
|
+
export { Input } from "./Input.js";
|
|
6
|
+
export { SlashMenu, SlashMenuPanel } from "./SlashMenu.js";
|
|
7
|
+
export { Message, StreamingMessage, type MessageData, type MessageRole, type ToolCallData } from "./Message.js";
|
|
8
|
+
export { TodoList, TodosChanged } from "./TodoList.js";
|
|
9
|
+
export { FilePreview, FileWritten, FileEdited, FileRead, LsResult, GlobResult, GrepResult, FileList } from "./FilePreview.js";
|
|
10
|
+
export {
|
|
11
|
+
ToolCall,
|
|
12
|
+
ToolResult,
|
|
13
|
+
StepIndicator,
|
|
14
|
+
ThinkingIndicator,
|
|
15
|
+
DoneIndicator,
|
|
16
|
+
ErrorDisplay,
|
|
17
|
+
} from "./ToolCall.js";
|
|
18
|
+
export { SubagentStart, SubagentFinish, SubagentRunning } from "./Subagent.js";
|
|
19
|
+
export { StatusBar } from "./StatusBar.js";
|
|
20
|
+
export { ToolCallSummary, InlineToolCall } from "./ToolCallSummary.js";
|
|
21
|
+
export { ModelSelectionPanel } from "./ModelSelection.js";
|
|
22
|
+
export { ApiKeyInputPanel, ApiKeyStatus } from "./ApiKeyInput.js";
|
|
23
|
+
export { ToolApproval } from "./ToolApproval.js";
|
|
24
|
+
|