@supatest/cli 0.0.4 → 0.0.5
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/dist/commands/login.js +392 -0
- package/dist/commands/setup.js +234 -0
- package/dist/config.js +29 -0
- package/dist/core/agent.js +259 -0
- package/dist/index.js +154 -6586
- package/dist/modes/headless.js +117 -0
- package/dist/modes/interactive.js +418 -0
- package/dist/presenters/composite.js +32 -0
- package/dist/presenters/console.js +163 -0
- package/dist/presenters/react.js +217 -0
- package/dist/presenters/types.js +1 -0
- package/dist/presenters/web.js +78 -0
- package/dist/prompts/builder.js +181 -0
- package/dist/prompts/fixer.js +148 -0
- package/dist/prompts/index.js +3 -0
- package/dist/prompts/planner.js +70 -0
- package/dist/services/api-client.js +244 -0
- package/dist/services/event-streamer.js +130 -0
- package/dist/types.js +1 -0
- package/dist/ui/App.js +322 -0
- package/dist/ui/components/AuthBanner.js +24 -0
- package/dist/ui/components/AuthDialog.js +32 -0
- package/dist/ui/components/Banner.js +12 -0
- package/dist/ui/components/ExpandableSection.js +17 -0
- package/dist/ui/components/Header.js +51 -0
- package/dist/ui/components/HelpMenu.js +89 -0
- package/dist/ui/components/InputPrompt.js +286 -0
- package/dist/ui/components/MessageList.js +42 -0
- package/dist/ui/components/QueuedMessageDisplay.js +31 -0
- package/dist/ui/components/Scrollable.js +103 -0
- package/dist/ui/components/SessionSelector.js +196 -0
- package/dist/ui/components/StatusBar.js +34 -0
- package/dist/ui/components/messages/AssistantMessage.js +20 -0
- package/dist/ui/components/messages/ErrorMessage.js +26 -0
- package/dist/ui/components/messages/LoadingMessage.js +28 -0
- package/dist/ui/components/messages/ThinkingMessage.js +17 -0
- package/dist/ui/components/messages/TodoMessage.js +44 -0
- package/dist/ui/components/messages/ToolMessage.js +218 -0
- package/dist/ui/components/messages/UserMessage.js +14 -0
- package/dist/ui/contexts/KeypressContext.js +527 -0
- package/dist/ui/contexts/MouseContext.js +98 -0
- package/dist/ui/contexts/SessionContext.js +129 -0
- package/dist/ui/hooks/useAnimatedScrollbar.js +83 -0
- package/dist/ui/hooks/useBatchedScroll.js +22 -0
- package/dist/ui/hooks/useBracketedPaste.js +31 -0
- package/dist/ui/hooks/useFocus.js +50 -0
- package/dist/ui/hooks/useKeypress.js +26 -0
- package/dist/ui/hooks/useModeToggle.js +25 -0
- package/dist/ui/types/auth.js +13 -0
- package/dist/ui/utils/file-completion.js +56 -0
- package/dist/ui/utils/input.js +50 -0
- package/dist/ui/utils/markdown.js +376 -0
- package/dist/ui/utils/mouse.js +189 -0
- package/dist/ui/utils/theme.js +59 -0
- package/dist/utils/banner.js +9 -0
- package/dist/utils/encryption.js +71 -0
- package/dist/utils/events.js +36 -0
- package/dist/utils/keychain-storage.js +120 -0
- package/dist/utils/logger.js +209 -0
- package/dist/utils/node-version.js +89 -0
- package/dist/utils/plan-file.js +75 -0
- package/dist/utils/project-instructions.js +23 -0
- package/dist/utils/rich-logger.js +208 -0
- package/dist/utils/stdin.js +25 -0
- package/dist/utils/stdio.js +80 -0
- package/dist/utils/summary.js +94 -0
- package/dist/utils/token-storage.js +242 -0
- package/dist/version.js +6 -0
- package/package.json +3 -4
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Bar Component
|
|
3
|
+
* Displays session stats, web URL, and agent status
|
|
4
|
+
*/
|
|
5
|
+
import { Box, Text } from "ink";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { useSession } from "../contexts/SessionContext.js";
|
|
8
|
+
import { theme } from "../utils/theme.js";
|
|
9
|
+
export const StatusBar = () => {
|
|
10
|
+
const { stats, webUrl, isAgentRunning } = useSession();
|
|
11
|
+
const elapsedSeconds = Math.floor((Date.now() - stats.startTime) / 1000);
|
|
12
|
+
const minutes = Math.floor(elapsedSeconds / 60);
|
|
13
|
+
const seconds = elapsedSeconds % 60;
|
|
14
|
+
const timeStr = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
|
15
|
+
return (React.createElement(Box, { borderColor: theme.border.default, borderStyle: "round", flexDirection: "column", paddingX: 1 },
|
|
16
|
+
React.createElement(Box, { flexDirection: "row", justifyContent: "space-between" },
|
|
17
|
+
React.createElement(Box, null,
|
|
18
|
+
React.createElement(Text, { color: theme.text.dim },
|
|
19
|
+
"Files: ",
|
|
20
|
+
React.createElement(Text, { color: theme.text.accent }, stats.filesModified.size)),
|
|
21
|
+
React.createElement(Text, { color: theme.text.dim }, " \u2022 "),
|
|
22
|
+
React.createElement(Text, { color: theme.text.dim },
|
|
23
|
+
"Commands: ",
|
|
24
|
+
React.createElement(Text, { color: theme.text.accent }, stats.commandsRun.length)),
|
|
25
|
+
React.createElement(Text, { color: theme.text.dim }, " \u2022 "),
|
|
26
|
+
React.createElement(Text, { color: theme.text.dim },
|
|
27
|
+
"Time: ",
|
|
28
|
+
React.createElement(Text, { color: theme.text.accent }, timeStr))),
|
|
29
|
+
React.createElement(Box, null, isAgentRunning ? (React.createElement(Text, { color: theme.status.inProgress }, "\u25CF Running")) : (React.createElement(Text, { color: theme.status.completed }, "\u25CF Ready")))),
|
|
30
|
+
webUrl && (React.createElement(Box, { marginTop: 0 },
|
|
31
|
+
React.createElement(Text, { color: theme.text.dim },
|
|
32
|
+
"Web: ",
|
|
33
|
+
React.createElement(Text, { color: theme.text.info, underline: true }, webUrl))))));
|
|
34
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assistant message component
|
|
3
|
+
* Displays AI responses with markdown rendering
|
|
4
|
+
*/
|
|
5
|
+
import { Box, Text } from "ink";
|
|
6
|
+
import Spinner from "ink-spinner";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { MarkdownDisplay } from "../../utils/markdown.js";
|
|
9
|
+
import { theme } from "../../utils/theme.js";
|
|
10
|
+
export const AssistantMessage = ({ text, isPending = false, terminalWidth = 80, }) => {
|
|
11
|
+
// Use "dots" spinner to match the screenshot look (animating dots)
|
|
12
|
+
// When pending, show the spinner. When done, show the static accent icon.
|
|
13
|
+
const prefix = isPending ? React.createElement(Spinner, { type: "dots" }) : "✦ ";
|
|
14
|
+
const prefixWidth = 2;
|
|
15
|
+
return (React.createElement(Box, { flexDirection: "row", marginTop: 1 },
|
|
16
|
+
React.createElement(Box, { width: prefixWidth },
|
|
17
|
+
React.createElement(Text, { color: isPending ? theme.text.dim : theme.text.accent }, prefix)),
|
|
18
|
+
React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
|
|
19
|
+
React.createElement(MarkdownDisplay, { isPending: isPending, terminalWidth: terminalWidth - prefixWidth, text: text }))));
|
|
20
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error message component
|
|
3
|
+
* Displays errors and warnings
|
|
4
|
+
*/
|
|
5
|
+
import { Box, Text } from "ink";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { theme } from "../../utils/theme.js";
|
|
8
|
+
export const ErrorMessage = ({ message, type = "error", }) => {
|
|
9
|
+
const { icon, color } = getErrorStyle(type);
|
|
10
|
+
return (React.createElement(Box, { flexDirection: "row", marginTop: 1 },
|
|
11
|
+
React.createElement(Text, { color: color },
|
|
12
|
+
icon,
|
|
13
|
+
" ",
|
|
14
|
+
message)));
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Get icon and color based on error type
|
|
18
|
+
*/
|
|
19
|
+
function getErrorStyle(type) {
|
|
20
|
+
const styles = {
|
|
21
|
+
error: { icon: "✗", color: theme.text.error },
|
|
22
|
+
warning: { icon: "⚠", color: theme.text.warning },
|
|
23
|
+
info: { icon: "ℹ", color: theme.text.info },
|
|
24
|
+
};
|
|
25
|
+
return styles[type];
|
|
26
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading message component
|
|
3
|
+
* Displays a spinner with static text while waiting for assistant response
|
|
4
|
+
* Note: We avoid text rotation to prevent layout shifts and flickering
|
|
5
|
+
*/
|
|
6
|
+
import { Box, Text } from "ink";
|
|
7
|
+
import Spinner from "ink-spinner";
|
|
8
|
+
import React, { useMemo } from "react";
|
|
9
|
+
import { theme } from "../../utils/theme.js";
|
|
10
|
+
// Fun loading messages - pick one randomly on mount (no rotation to avoid flicker)
|
|
11
|
+
const LOADING_MESSAGES = [
|
|
12
|
+
"Thinking...",
|
|
13
|
+
"Working...",
|
|
14
|
+
"Processing...",
|
|
15
|
+
];
|
|
16
|
+
export const LoadingMessage = () => {
|
|
17
|
+
// Pick a random message once on mount - no rotation to prevent layout shifts
|
|
18
|
+
const message = useMemo(() => LOADING_MESSAGES[Math.floor(Math.random() * LOADING_MESSAGES.length)], []);
|
|
19
|
+
return (React.createElement(Box, { flexDirection: "row", marginTop: 1 },
|
|
20
|
+
React.createElement(Box, { width: 2 },
|
|
21
|
+
React.createElement(Text, { color: theme.text.accent },
|
|
22
|
+
React.createElement(Spinner, { type: "dots" }))),
|
|
23
|
+
React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
|
|
24
|
+
React.createElement(Text, { color: theme.text.dim },
|
|
25
|
+
message,
|
|
26
|
+
" ",
|
|
27
|
+
React.createElement(Text, { color: theme.text.dim }, "(esc to interrupt)")))));
|
|
28
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thinking message component
|
|
3
|
+
* Displays Claude's thinking process (collapsible by default)
|
|
4
|
+
*/
|
|
5
|
+
import { Box, Text } from "ink";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { theme } from "../../utils/theme.js";
|
|
8
|
+
export const ThinkingMessage = ({ id, content, isExpanded = false, onToggle, }) => {
|
|
9
|
+
return (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
10
|
+
React.createElement(Box, { flexDirection: "row" },
|
|
11
|
+
React.createElement(Text, { color: theme.text.dim }, "\u25CF Thinking"),
|
|
12
|
+
content && (React.createElement(Text, { color: theme.text.dim },
|
|
13
|
+
" ",
|
|
14
|
+
isExpanded ? "▼" : "▶"))),
|
|
15
|
+
isExpanded && content && (React.createElement(Box, { flexDirection: "column", marginLeft: 2 },
|
|
16
|
+
React.createElement(Text, { color: theme.text.dim }, content)))));
|
|
17
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Todo message component
|
|
3
|
+
* Displays todo list with progress
|
|
4
|
+
*/
|
|
5
|
+
import { Box, Text } from "ink";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { theme } from "../../utils/theme.js";
|
|
8
|
+
export const TodoMessage = ({ todos }) => {
|
|
9
|
+
const completed = todos.filter((t) => t.status === "completed");
|
|
10
|
+
const inProgress = todos.filter((t) => t.status === "in_progress");
|
|
11
|
+
const pending = todos.filter((t) => t.status === "pending");
|
|
12
|
+
const total = todos.length;
|
|
13
|
+
const completedCount = completed.length;
|
|
14
|
+
const progress = total > 0 ? Math.round((completedCount / total) * 100) : 0;
|
|
15
|
+
// Progress bar
|
|
16
|
+
const barLength = 20;
|
|
17
|
+
const filledLength = Math.round((barLength * completedCount) / total);
|
|
18
|
+
const bar = "█".repeat(filledLength) + "░".repeat(barLength - filledLength);
|
|
19
|
+
return (React.createElement(Box, { flexDirection: "column", marginY: 0 },
|
|
20
|
+
React.createElement(Box, { flexDirection: "row" },
|
|
21
|
+
React.createElement(Text, { color: theme.text.info }, "\uD83D\uDCDD "),
|
|
22
|
+
React.createElement(Text, { color: theme.text.dim }, "Todo Progress: "),
|
|
23
|
+
React.createElement(Text, { color: theme.text.accent }, bar),
|
|
24
|
+
React.createElement(Text, { color: theme.text.primary },
|
|
25
|
+
" ",
|
|
26
|
+
progress,
|
|
27
|
+
"%"),
|
|
28
|
+
React.createElement(Text, { color: theme.text.dim },
|
|
29
|
+
" ",
|
|
30
|
+
"(",
|
|
31
|
+
completedCount,
|
|
32
|
+
"/",
|
|
33
|
+
total,
|
|
34
|
+
")")),
|
|
35
|
+
inProgress.length > 0 && (React.createElement(Box, { flexDirection: "column", marginLeft: 2 }, inProgress.map((todo, idx) => (React.createElement(Box, { flexDirection: "row", key: `in-progress-${idx}` },
|
|
36
|
+
React.createElement(Text, { color: theme.status.inProgress }, "\u2192 "),
|
|
37
|
+
React.createElement(Text, { color: theme.text.primary }, todo.activeForm || todo.content)))))),
|
|
38
|
+
completed.length > 0 && completed.length <= 2 && (React.createElement(Box, { flexDirection: "column", marginLeft: 2 }, completed.map((todo, idx) => (React.createElement(Box, { flexDirection: "row", key: `completed-${idx}` },
|
|
39
|
+
React.createElement(Text, { color: theme.status.completed }, "\u2713 "),
|
|
40
|
+
React.createElement(Text, { color: theme.text.dim }, todo.content)))))),
|
|
41
|
+
pending.length > 0 && pending.length <= 3 && (React.createElement(Box, { flexDirection: "column", marginLeft: 2 }, pending.map((todo, idx) => (React.createElement(Box, { flexDirection: "row", key: `pending-${idx}` },
|
|
42
|
+
React.createElement(Text, { color: theme.status.pending }, "\u23F3 "),
|
|
43
|
+
React.createElement(Text, { color: theme.text.secondary }, todo.content))))))));
|
|
44
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool message component
|
|
3
|
+
* Displays tool calls (Read, Write, Edit, Bash, etc.)
|
|
4
|
+
*/
|
|
5
|
+
import { structuredPatch } from "diff";
|
|
6
|
+
import { Box, Text } from "ink";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { getToolDisplayName } from "shared";
|
|
9
|
+
import { theme } from "../../utils/theme.js";
|
|
10
|
+
export const ToolMessage = ({ toolName, description, input, result, isExpanded = true, }) => {
|
|
11
|
+
const displayName = getToolDisplayName(toolName);
|
|
12
|
+
const { icon, color } = getToolStyle(toolName);
|
|
13
|
+
const resultSummary = result ? getResultSummary(toolName, result) : null;
|
|
14
|
+
const hasExpandableContent = !!result;
|
|
15
|
+
const isRunning = !result;
|
|
16
|
+
// Get the actual command/content to display
|
|
17
|
+
const commandDisplay = getCommandDisplay(toolName, input);
|
|
18
|
+
// Check if this is an Edit operation - show diff by default
|
|
19
|
+
const isEditOperation = toolName === "Edit";
|
|
20
|
+
const editDiff = isEditOperation ? getEditDiff(input) : null;
|
|
21
|
+
// Tools that support expand/collapse (Bash has expandable output)
|
|
22
|
+
const isExpandableTool = toolName === "Bash" || toolName === "BashOutput" || toolName === "Command Output";
|
|
23
|
+
return (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
24
|
+
React.createElement(Box, { flexDirection: "row" },
|
|
25
|
+
hasExpandableContent && isExpandableTool ? (React.createElement(Text, { color: theme.text.dim }, isExpanded ? "▼ " : "▶ ")) : (React.createElement(Text, { color: isRunning ? "yellow" : color }, "\u25CF ")),
|
|
26
|
+
React.createElement(Text, { bold: true, color: color }, displayName),
|
|
27
|
+
React.createElement(Text, { color: theme.text.dim },
|
|
28
|
+
"(",
|
|
29
|
+
description,
|
|
30
|
+
")"),
|
|
31
|
+
editDiff && (React.createElement(Text, { color: theme.text.dim },
|
|
32
|
+
" with ",
|
|
33
|
+
editDiff.additions,
|
|
34
|
+
" addition",
|
|
35
|
+
editDiff.additions !== 1 ? "s" : "",
|
|
36
|
+
" and ",
|
|
37
|
+
editDiff.removals,
|
|
38
|
+
" removal",
|
|
39
|
+
editDiff.removals !== 1 ? "s" : "")),
|
|
40
|
+
hasExpandableContent && !isExpanded && isExpandableTool && (React.createElement(Text, { color: theme.text.dim }, " (ctrl+o to expand)"))),
|
|
41
|
+
!isEditOperation && resultSummary && (React.createElement(Box, { marginLeft: 2 },
|
|
42
|
+
React.createElement(Text, { color: theme.text.dim },
|
|
43
|
+
"\u2514 ",
|
|
44
|
+
resultSummary))),
|
|
45
|
+
editDiff && (React.createElement(Box, { flexDirection: "column", marginLeft: 2 }, editDiff.lines.map((line, i) => {
|
|
46
|
+
if (line.type === "separator") {
|
|
47
|
+
return (React.createElement(Box, { key: i, flexDirection: "row" },
|
|
48
|
+
React.createElement(Text, { color: theme.text.dim }, "...")));
|
|
49
|
+
}
|
|
50
|
+
const isRemove = line.type === "remove";
|
|
51
|
+
const isAdd = line.type === "add";
|
|
52
|
+
const bgColor = isRemove ? "#5c1b1b" : isAdd ? "#1b3d1b" : undefined;
|
|
53
|
+
const prefix = isRemove ? "- " : isAdd ? "+ " : " ";
|
|
54
|
+
const prefixColor = isRemove ? "red" : isAdd ? "green" : theme.text.dim;
|
|
55
|
+
return (React.createElement(Box, { key: i, flexDirection: "row" },
|
|
56
|
+
React.createElement(Text, { color: theme.text.dim }, line.lineNum.toString().padStart(4, " ")),
|
|
57
|
+
React.createElement(Text, { color: prefixColor },
|
|
58
|
+
" ",
|
|
59
|
+
prefix),
|
|
60
|
+
React.createElement(Text, { backgroundColor: bgColor }, line.content)));
|
|
61
|
+
}))),
|
|
62
|
+
isExpanded && result && isExpandableTool && (React.createElement(Box, { flexDirection: "column", marginLeft: 4, marginTop: 1 },
|
|
63
|
+
React.createElement(Text, { color: theme.text.secondary }, truncate(result, 2000))))));
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Generate diff display for Edit operations using jsdiff
|
|
67
|
+
* Shows context lines before/after changes like Claude Code
|
|
68
|
+
*/
|
|
69
|
+
function getEditDiff(input) {
|
|
70
|
+
if (!input?.old_string || !input?.new_string)
|
|
71
|
+
return null;
|
|
72
|
+
// Use jsdiff to compute structured patch with 3 lines of context
|
|
73
|
+
const patch = structuredPatch("file", "file", input.old_string, input.new_string, "", "", { context: 3 });
|
|
74
|
+
const lines = [];
|
|
75
|
+
let additions = 0;
|
|
76
|
+
let removals = 0;
|
|
77
|
+
// Process each hunk
|
|
78
|
+
for (let hunkIndex = 0; hunkIndex < patch.hunks.length; hunkIndex++) {
|
|
79
|
+
const hunk = patch.hunks[hunkIndex];
|
|
80
|
+
// Add separator between hunks (except for first)
|
|
81
|
+
if (hunkIndex > 0) {
|
|
82
|
+
lines.push({
|
|
83
|
+
lineNum: "...",
|
|
84
|
+
type: "separator",
|
|
85
|
+
content: "",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
let oldLineNum = hunk.oldStart;
|
|
89
|
+
let newLineNum = hunk.newStart;
|
|
90
|
+
for (const line of hunk.lines) {
|
|
91
|
+
const content = line.substring(1); // Remove the +/- prefix
|
|
92
|
+
if (line.startsWith("+")) {
|
|
93
|
+
lines.push({
|
|
94
|
+
lineNum: newLineNum,
|
|
95
|
+
type: "add",
|
|
96
|
+
content,
|
|
97
|
+
});
|
|
98
|
+
newLineNum++;
|
|
99
|
+
additions++;
|
|
100
|
+
}
|
|
101
|
+
else if (line.startsWith("-")) {
|
|
102
|
+
lines.push({
|
|
103
|
+
lineNum: oldLineNum,
|
|
104
|
+
type: "remove",
|
|
105
|
+
content,
|
|
106
|
+
});
|
|
107
|
+
oldLineNum++;
|
|
108
|
+
removals++;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Context line (starts with space)
|
|
112
|
+
lines.push({
|
|
113
|
+
lineNum: newLineNum,
|
|
114
|
+
type: "context",
|
|
115
|
+
content,
|
|
116
|
+
});
|
|
117
|
+
oldLineNum++;
|
|
118
|
+
newLineNum++;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { lines, additions, removals };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get command/content to display based on tool type
|
|
126
|
+
*/
|
|
127
|
+
function getCommandDisplay(toolName, input) {
|
|
128
|
+
if (!input)
|
|
129
|
+
return null;
|
|
130
|
+
switch (toolName) {
|
|
131
|
+
case "Bash":
|
|
132
|
+
case "BashOutput":
|
|
133
|
+
case "Command Output": {
|
|
134
|
+
const command = input.command || input.bash_id;
|
|
135
|
+
if (!command)
|
|
136
|
+
return null;
|
|
137
|
+
// Split long commands into multiple lines
|
|
138
|
+
return [command];
|
|
139
|
+
}
|
|
140
|
+
case "Read":
|
|
141
|
+
case "Write":
|
|
142
|
+
case "Edit":
|
|
143
|
+
return input.file_path ? [input.file_path] : null;
|
|
144
|
+
case "Grep":
|
|
145
|
+
return input.pattern ? [`"${input.pattern}"${input.path ? ` in ${input.path}` : ""}`] : null;
|
|
146
|
+
case "Glob":
|
|
147
|
+
return input.pattern ? [input.pattern] : null;
|
|
148
|
+
default:
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get icon and color for tool based on name
|
|
154
|
+
*/
|
|
155
|
+
function getToolStyle(toolName) {
|
|
156
|
+
const styles = {
|
|
157
|
+
Read: { icon: "📖", color: theme.tool.read },
|
|
158
|
+
Write: { icon: "✏️", color: theme.tool.write },
|
|
159
|
+
Edit: { icon: "✏️", color: theme.tool.edit },
|
|
160
|
+
Bash: { icon: "🔨", color: theme.tool.bash },
|
|
161
|
+
BashOutput: { icon: "📄", color: theme.tool.bash },
|
|
162
|
+
"Command Output": { icon: "📄", color: theme.tool.bash },
|
|
163
|
+
Glob: { icon: "🔍", color: theme.tool.search },
|
|
164
|
+
Grep: { icon: "🔍", color: theme.tool.search },
|
|
165
|
+
Task: { icon: "🤖", color: theme.tool.agent },
|
|
166
|
+
TodoWrite: { icon: "📝", color: theme.text.info },
|
|
167
|
+
};
|
|
168
|
+
return styles[toolName] || { icon: "🔧", color: theme.text.secondary };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Truncate string to max length
|
|
172
|
+
*/
|
|
173
|
+
function truncate(str, maxLength) {
|
|
174
|
+
if (str.length <= maxLength) {
|
|
175
|
+
return str;
|
|
176
|
+
}
|
|
177
|
+
return str.slice(0, maxLength - 3) + "...";
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get result summary for tool output
|
|
181
|
+
*/
|
|
182
|
+
function getResultSummary(toolName, result) {
|
|
183
|
+
switch (toolName) {
|
|
184
|
+
case "Glob": {
|
|
185
|
+
// Count lines that look like file paths
|
|
186
|
+
const lines = result.split("\n").filter((line) => line.trim());
|
|
187
|
+
const count = lines.filter((line) => !line.startsWith("Found")).length;
|
|
188
|
+
return count > 0 ? `Found ${count} files` : "No files found";
|
|
189
|
+
}
|
|
190
|
+
case "Grep": {
|
|
191
|
+
// Count lines of output
|
|
192
|
+
const lines = result.split("\n").filter((line) => line.trim());
|
|
193
|
+
return lines.length > 0
|
|
194
|
+
? `${lines.length} lines of output`
|
|
195
|
+
: "No matches found";
|
|
196
|
+
}
|
|
197
|
+
case "Read": {
|
|
198
|
+
// Count lines
|
|
199
|
+
const lines = result.split("\n").length;
|
|
200
|
+
return `Read ${lines} lines`;
|
|
201
|
+
}
|
|
202
|
+
case "Bash": {
|
|
203
|
+
// Show if there was output
|
|
204
|
+
const hasOutput = result.trim().length > 0;
|
|
205
|
+
return hasOutput ? `${result.split("\n").length} lines of output` : null;
|
|
206
|
+
}
|
|
207
|
+
case "BashOutput":
|
|
208
|
+
case "Command Output": {
|
|
209
|
+
// Show lines of shell output
|
|
210
|
+
const lines = result.split("\n").filter((line) => line.trim());
|
|
211
|
+
return lines.length > 0
|
|
212
|
+
? `${lines.length} lines of output`
|
|
213
|
+
: "No output";
|
|
214
|
+
}
|
|
215
|
+
default:
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User message component
|
|
3
|
+
* Displays user input/prompts
|
|
4
|
+
*/
|
|
5
|
+
import { Box, Text } from "ink";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { theme } from "../../utils/theme.js";
|
|
8
|
+
export const UserMessage = ({ text }) => {
|
|
9
|
+
return (React.createElement(Box, { flexDirection: "row", marginTop: 1 },
|
|
10
|
+
React.createElement(Box, { width: 2 },
|
|
11
|
+
React.createElement(Text, { color: theme.text.info }, "\u276F ")),
|
|
12
|
+
React.createElement(Box, { flexGrow: 1 },
|
|
13
|
+
React.createElement(Text, { color: theme.text.primary }, text))));
|
|
14
|
+
};
|