neeter 0.6.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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +361 -0
  3. package/dist/react/AgentProvider.d.ts +15 -0
  4. package/dist/react/AgentProvider.js +30 -0
  5. package/dist/react/ApprovalButtons.d.ts +5 -0
  6. package/dist/react/ApprovalButtons.js +30 -0
  7. package/dist/react/ChatInput.d.ts +6 -0
  8. package/dist/react/ChatInput.js +41 -0
  9. package/dist/react/CollapsibleCard.d.ts +9 -0
  10. package/dist/react/CollapsibleCard.js +9 -0
  11. package/dist/react/MessageList.d.ts +3 -0
  12. package/dist/react/MessageList.js +32 -0
  13. package/dist/react/PendingPermissions.d.ts +3 -0
  14. package/dist/react/PendingPermissions.js +35 -0
  15. package/dist/react/StatusDot.d.ts +8 -0
  16. package/dist/react/StatusDot.js +15 -0
  17. package/dist/react/TextMessage.d.ts +5 -0
  18. package/dist/react/TextMessage.js +8 -0
  19. package/dist/react/ThinkingBlock.d.ts +5 -0
  20. package/dist/react/ThinkingBlock.js +38 -0
  21. package/dist/react/ThinkingIndicator.d.ts +3 -0
  22. package/dist/react/ThinkingIndicator.js +5 -0
  23. package/dist/react/ToolApprovalCard.d.ts +7 -0
  24. package/dist/react/ToolApprovalCard.js +11 -0
  25. package/dist/react/ToolCallCard.d.ts +5 -0
  26. package/dist/react/ToolCallCard.js +59 -0
  27. package/dist/react/UserQuestionCard.d.ts +6 -0
  28. package/dist/react/UserQuestionCard.js +120 -0
  29. package/dist/react/approval-matching.d.ts +13 -0
  30. package/dist/react/approval-matching.js +30 -0
  31. package/dist/react/cn.d.ts +2 -0
  32. package/dist/react/cn.js +5 -0
  33. package/dist/react/icons.d.ts +7 -0
  34. package/dist/react/icons.js +8 -0
  35. package/dist/react/index.d.ts +28 -0
  36. package/dist/react/index.js +28 -0
  37. package/dist/react/markdown-overrides.d.ts +2 -0
  38. package/dist/react/markdown-overrides.js +8 -0
  39. package/dist/react/registry.d.ts +4 -0
  40. package/dist/react/registry.js +10 -0
  41. package/dist/react/store.d.ts +34 -0
  42. package/dist/react/store.js +141 -0
  43. package/dist/react/use-agent.d.ts +12 -0
  44. package/dist/react/use-agent.js +119 -0
  45. package/dist/react/widgets/AskUserQuestionWidget.d.ts +1 -0
  46. package/dist/react/widgets/AskUserQuestionWidget.js +42 -0
  47. package/dist/react/widgets/BashWidget.d.ts +1 -0
  48. package/dist/react/widgets/BashWidget.js +33 -0
  49. package/dist/react/widgets/EditWidget.d.ts +1 -0
  50. package/dist/react/widgets/EditWidget.js +36 -0
  51. package/dist/react/widgets/GlobWidget.d.ts +1 -0
  52. package/dist/react/widgets/GlobWidget.js +31 -0
  53. package/dist/react/widgets/GrepWidget.d.ts +1 -0
  54. package/dist/react/widgets/GrepWidget.js +36 -0
  55. package/dist/react/widgets/NotebookEditWidget.d.ts +1 -0
  56. package/dist/react/widgets/NotebookEditWidget.js +47 -0
  57. package/dist/react/widgets/ReadWidget.d.ts +1 -0
  58. package/dist/react/widgets/ReadWidget.js +46 -0
  59. package/dist/react/widgets/TodoWriteWidget.d.ts +1 -0
  60. package/dist/react/widgets/TodoWriteWidget.js +40 -0
  61. package/dist/react/widgets/WebFetchWidget.d.ts +1 -0
  62. package/dist/react/widgets/WebFetchWidget.js +48 -0
  63. package/dist/react/widgets/WebSearchWidget.d.ts +1 -0
  64. package/dist/react/widgets/WebSearchWidget.js +85 -0
  65. package/dist/react/widgets/WriteWidget.d.ts +1 -0
  66. package/dist/react/widgets/WriteWidget.js +30 -0
  67. package/dist/server/index.d.ts +6 -0
  68. package/dist/server/index.js +5 -0
  69. package/dist/server/permission-gate.d.ts +12 -0
  70. package/dist/server/permission-gate.js +41 -0
  71. package/dist/server/push-channel.d.ts +8 -0
  72. package/dist/server/push-channel.js +40 -0
  73. package/dist/server/router.d.ts +8 -0
  74. package/dist/server/router.js +67 -0
  75. package/dist/server/session.d.ts +40 -0
  76. package/dist/server/session.js +117 -0
  77. package/dist/server/translator.d.ts +15 -0
  78. package/dist/server/translator.js +236 -0
  79. package/dist/types.d.ts +79 -0
  80. package/dist/types.js +1 -0
  81. package/package.json +72 -0
  82. package/src/theme.css +170 -0
@@ -0,0 +1,119 @@
1
+ import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
2
+ export function useAgent(store, config) {
3
+ const endpoint = config?.endpoint ?? "/api";
4
+ const onCustomEvent = config?.onCustomEvent;
5
+ const eventSourceRef = useRef(null);
6
+ const sessionId = useSyncExternalStore(store.subscribe, () => store.getState().sessionId);
7
+ useEffect(() => {
8
+ let cancelled = false;
9
+ async function init() {
10
+ const res = await fetch(`${endpoint}/sessions`, { method: "POST" });
11
+ const data = await res.json();
12
+ if (!cancelled)
13
+ store.getState().setSessionId(data.sessionId);
14
+ }
15
+ init();
16
+ return () => {
17
+ cancelled = true;
18
+ };
19
+ }, [endpoint, store]);
20
+ useEffect(() => {
21
+ if (!sessionId)
22
+ return;
23
+ const es = new EventSource(`${endpoint}/sessions/${sessionId}/events`);
24
+ eventSourceRef.current = es;
25
+ es.addEventListener("message_start", () => {
26
+ store.getState().setThinking(true);
27
+ });
28
+ es.addEventListener("thinking_delta", (e) => {
29
+ const { text } = JSON.parse(e.data);
30
+ store.getState().appendStreamingThinking(text);
31
+ });
32
+ es.addEventListener("text_delta", (e) => {
33
+ store.getState().setThinking(false);
34
+ store.getState().flushStreamingThinking();
35
+ const { text } = JSON.parse(e.data);
36
+ store.getState().appendStreamingText(text);
37
+ });
38
+ es.addEventListener("tool_start", (e) => {
39
+ store.getState().setThinking(false);
40
+ store.getState().flushStreamingThinking();
41
+ store.getState().flushStreamingText();
42
+ const { id, name } = JSON.parse(e.data);
43
+ store.getState().startToolCall(id, name);
44
+ });
45
+ es.addEventListener("tool_input_delta", (e) => {
46
+ const { id, partialJson } = JSON.parse(e.data);
47
+ store.getState().appendToolInput(id, partialJson);
48
+ });
49
+ es.addEventListener("tool_call", (e) => {
50
+ store.getState().flushStreamingText();
51
+ const { id, name, input } = JSON.parse(e.data);
52
+ store.getState().finalizeToolCall(id, name, input);
53
+ });
54
+ es.addEventListener("tool_result", (e) => {
55
+ const { toolUseId, result } = JSON.parse(e.data);
56
+ store.getState().completeToolCall(toolUseId, result);
57
+ if (store.getState().isStreaming) {
58
+ store.getState().setThinking(true);
59
+ }
60
+ });
61
+ es.addEventListener("permission_request", (e) => {
62
+ const request = JSON.parse(e.data);
63
+ store.getState().addPermissionRequest(request);
64
+ });
65
+ es.addEventListener("session_error", (e) => {
66
+ store.getState().flushStreamingThinking();
67
+ store.getState().flushStreamingText();
68
+ store.getState().setThinking(false);
69
+ const { subtype } = JSON.parse(e.data);
70
+ store.getState().addSystemMessage(`Session ended: ${subtype}`);
71
+ store.getState().setStreaming(false);
72
+ });
73
+ es.addEventListener("turn_complete", () => {
74
+ store.getState().flushStreamingThinking();
75
+ store.getState().flushStreamingText();
76
+ store.getState().setThinking(false);
77
+ store.getState().setStreaming(false);
78
+ });
79
+ es.addEventListener("error", () => {
80
+ if (es.readyState === EventSource.CLOSED) {
81
+ store.getState().flushStreamingThinking();
82
+ store.getState().flushStreamingText();
83
+ store.getState().setStreaming(false);
84
+ }
85
+ });
86
+ if (onCustomEvent) {
87
+ es.addEventListener("custom", (e) => {
88
+ onCustomEvent(JSON.parse(e.data));
89
+ });
90
+ }
91
+ return () => {
92
+ es.close();
93
+ eventSourceRef.current = null;
94
+ };
95
+ }, [sessionId, endpoint, store, onCustomEvent]);
96
+ const sendMessage = useCallback(async (text) => {
97
+ if (!sessionId)
98
+ return;
99
+ store.getState().addUserMessage(text);
100
+ store.getState().setStreaming(true);
101
+ store.getState().setThinking(true);
102
+ await fetch(`${endpoint}/sessions/${sessionId}/messages`, {
103
+ method: "POST",
104
+ headers: { "Content-Type": "application/json" },
105
+ body: JSON.stringify({ text }),
106
+ });
107
+ }, [sessionId, endpoint, store]);
108
+ const respondToPermission = useCallback(async (response) => {
109
+ if (!sessionId)
110
+ return;
111
+ store.getState().removePermissionRequest(response.requestId);
112
+ await fetch(`${endpoint}/sessions/${sessionId}/permissions`, {
113
+ method: "POST",
114
+ headers: { "Content-Type": "application/json" },
115
+ body: JSON.stringify(response),
116
+ });
117
+ }, [sessionId, endpoint, store]);
118
+ return { sessionId, sendMessage, respondToPermission };
119
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { registerWidget } from "../registry.js";
3
+ function parseAnswers(result) {
4
+ if (typeof result !== "string")
5
+ return null;
6
+ const answers = {};
7
+ // Format: "question"="answer"
8
+ const re = /"(.+?)"="(.+?)"/g;
9
+ for (let match = re.exec(result); match !== null; match = re.exec(result)) {
10
+ answers[match[1]] = match[2];
11
+ }
12
+ if (Object.keys(answers).length === 0)
13
+ return null;
14
+ return answers;
15
+ }
16
+ function AskUserQuestionWidget({ input, result }) {
17
+ const questions = (input.questions ?? []);
18
+ const answers = parseAnswers(result);
19
+ if (!questions.length || !answers)
20
+ return null;
21
+ return (_jsx("div", { className: "space-y-1.5 py-1 text-xs", children: questions.map((q) => {
22
+ const answer = answers[q.question];
23
+ return (_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: q.question }), answer && _jsx("span", { className: "ml-1.5 font-medium text-foreground", children: answer })] }, q.question));
24
+ }) }));
25
+ }
26
+ registerWidget({
27
+ toolName: "AskUserQuestion",
28
+ label: "Question",
29
+ richLabel: (result, input) => {
30
+ const questions = (input.questions ?? []);
31
+ const answers = parseAnswers(result);
32
+ if (!questions.length || !answers)
33
+ return null;
34
+ const first = questions[0];
35
+ const answer = answers[first.question];
36
+ if (!answer)
37
+ return null;
38
+ const label = first.header ?? "Answer";
39
+ return `${label}: ${answer}`;
40
+ },
41
+ component: AskUserQuestionWidget,
42
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { registerWidget } from "../registry.js";
3
+ function BashInputRenderer({ input }) {
4
+ const command = typeof input.command === "string" ? input.command : null;
5
+ const description = typeof input.description === "string" ? input.description : null;
6
+ if (!command)
7
+ return null;
8
+ return (_jsxs("div", { className: "mt-1.5 space-y-1", children: [description && _jsx("div", { className: "text-xs text-muted-foreground", children: description }), _jsx("pre", { className: "text-[11px] leading-snug text-muted-foreground bg-accent rounded px-2 py-1 overflow-x-auto whitespace-pre-wrap break-all", children: _jsx("code", { children: command }) })] }));
9
+ }
10
+ function BashWidget({ result, input, phase }) {
11
+ const command = typeof input.command === "string" ? input.command : null;
12
+ if (phase === "running" || phase === "pending") {
13
+ return (_jsxs("div", { className: "py-1 space-y-1 text-xs", children: [command && (_jsx("pre", { className: "leading-snug text-muted-foreground bg-accent rounded px-2 py-1 overflow-x-auto whitespace-pre-wrap break-all", children: _jsxs("code", { children: ["$ ", command] }) })), _jsx("span", { className: "animate-pulse text-muted-foreground", children: "Running\u2026" })] }));
14
+ }
15
+ if (typeof result !== "string" || !result)
16
+ return null;
17
+ return (_jsx("div", { className: "py-1 text-xs", children: _jsx("pre", { className: "leading-snug text-foreground bg-accent rounded px-2 py-1.5 overflow-x-auto whitespace-pre-wrap break-all max-h-[300px] overflow-y-auto", children: _jsxs("code", { children: [command && (_jsxs("span", { className: "text-muted-foreground", children: ["$ ", command, "\n"] })), result] }) }) }));
18
+ }
19
+ registerWidget({
20
+ toolName: "Bash",
21
+ label: "Bash",
22
+ richLabel: (_r, input) => {
23
+ const desc = typeof input.description === "string" ? input.description : null;
24
+ const cmd = typeof input.command === "string" ? input.command : null;
25
+ if (desc)
26
+ return desc;
27
+ if (cmd)
28
+ return cmd.length > 60 ? `${cmd.slice(0, 57)}...` : cmd;
29
+ return null;
30
+ },
31
+ inputRenderer: BashInputRenderer,
32
+ component: BashWidget,
33
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { registerWidget } from "../registry.js";
3
+ function basename(filePath) {
4
+ return filePath.split("/").pop() ?? filePath;
5
+ }
6
+ function EditInputRenderer({ input }) {
7
+ const filePath = typeof input.file_path === "string" ? input.file_path : null;
8
+ const oldStr = typeof input.old_string === "string" ? input.old_string : null;
9
+ const newStr = typeof input.new_string === "string" ? input.new_string : null;
10
+ if (!filePath || oldStr == null || newStr == null)
11
+ return null;
12
+ return (_jsxs("div", { className: "mt-1.5 space-y-1.5", children: [_jsx("div", { className: "text-xs text-muted-foreground font-mono truncate", children: filePath }), _jsxs("div", { className: "text-[11px] leading-snug rounded overflow-hidden border border-border", children: [oldStr && (_jsx("pre", { className: "bg-red-500/10 text-red-700 dark:text-red-400 px-2 py-1 overflow-x-auto whitespace-pre-wrap break-all", children: _jsx("code", { children: oldStr.split("\n").map((line, i) => (
13
+ // biome-ignore lint/suspicious/noArrayIndexKey: static split lines
14
+ _jsxs("span", { children: [i > 0 && "\n", "- ", line] }, i))) }) })), newStr && (_jsx("pre", { className: "bg-green-500/10 text-green-700 dark:text-green-400 px-2 py-1 overflow-x-auto whitespace-pre-wrap break-all", children: _jsx("code", { children: newStr.split("\n").map((line, i) => (
15
+ // biome-ignore lint/suspicious/noArrayIndexKey: static split lines
16
+ _jsxs("span", { children: [i > 0 && "\n", "+ ", line] }, i))) }) }))] })] }));
17
+ }
18
+ function EditWidget({ result, input, phase }) {
19
+ const filePath = typeof input.file_path === "string" ? input.file_path : null;
20
+ if (phase === "running" || phase === "pending") {
21
+ return (_jsx("div", { className: "flex items-center gap-2 text-xs text-muted-foreground py-1", children: _jsxs("span", { className: "animate-pulse", children: ["Editing ", filePath ? basename(filePath) : "file", "\u2026"] }) }));
22
+ }
23
+ if (typeof result !== "string" || !result)
24
+ return null;
25
+ return _jsx("div", { className: "py-1 text-xs text-muted-foreground", children: result });
26
+ }
27
+ registerWidget({
28
+ toolName: "Edit",
29
+ label: "Edit",
30
+ richLabel: (_r, input) => {
31
+ const filePath = typeof input.file_path === "string" ? input.file_path : null;
32
+ return filePath ? basename(filePath) : null;
33
+ },
34
+ inputRenderer: EditInputRenderer,
35
+ component: EditWidget,
36
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { registerWidget } from "../registry.js";
3
+ function GlobInputRenderer({ input }) {
4
+ const pattern = typeof input.pattern === "string" ? input.pattern : null;
5
+ const path = typeof input.path === "string" ? input.path : null;
6
+ if (!pattern)
7
+ return null;
8
+ return (_jsx("div", { className: "mt-1.5 space-y-1", children: _jsx("pre", { className: "text-[11px] leading-snug text-muted-foreground bg-accent rounded px-2 py-1 overflow-x-auto whitespace-pre-wrap break-all", children: _jsxs("code", { children: [pattern, path ? ` in ${path}` : ""] }) }) }));
9
+ }
10
+ function GlobWidget({ result, input, phase }) {
11
+ const pattern = typeof input.pattern === "string" ? input.pattern : null;
12
+ if (phase === "running" || phase === "pending") {
13
+ return (_jsx("div", { className: "flex items-center gap-2 text-xs text-muted-foreground py-1", children: _jsxs("span", { className: "animate-pulse", children: ["Matching ", pattern ? pattern : "files", "\u2026"] }) }));
14
+ }
15
+ if (typeof result !== "string" || !result)
16
+ return null;
17
+ const lines = result.split("\n").filter(Boolean);
18
+ return (_jsxs("div", { className: "py-1 text-xs", children: [_jsxs("div", { className: "text-muted-foreground mb-1", children: [lines.length, " ", lines.length === 1 ? "match" : "matches"] }), _jsx("pre", { className: "leading-snug text-foreground bg-accent rounded px-2 py-1.5 overflow-x-auto whitespace-pre-wrap break-all max-h-[300px] overflow-y-auto", children: _jsx("code", { children: lines.join("\n") }) })] }));
19
+ }
20
+ registerWidget({
21
+ toolName: "Glob",
22
+ label: "Glob",
23
+ richLabel: (_r, input) => {
24
+ const pattern = typeof input.pattern === "string" ? input.pattern : null;
25
+ if (pattern)
26
+ return pattern.length > 50 ? `${pattern.slice(0, 47)}...` : pattern;
27
+ return null;
28
+ },
29
+ inputRenderer: GlobInputRenderer,
30
+ component: GlobWidget,
31
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { registerWidget } from "../registry.js";
3
+ function GrepInputRenderer({ input }) {
4
+ const pattern = typeof input.pattern === "string" ? input.pattern : null;
5
+ const path = typeof input.path === "string" ? input.path : null;
6
+ const glob = typeof input.glob === "string" ? input.glob : null;
7
+ const outputMode = typeof input.output_mode === "string" ? input.output_mode : null;
8
+ if (!pattern)
9
+ return null;
10
+ return (_jsx("div", { className: "mt-1.5 space-y-1", children: _jsx("pre", { className: "text-[11px] leading-snug text-muted-foreground bg-accent rounded px-2 py-1 overflow-x-auto whitespace-pre-wrap break-all", children: _jsxs("code", { children: ["/", pattern, "/", path ? ` in ${path}` : "", glob ? ` --glob ${glob}` : "", outputMode && outputMode !== "files_with_matches" ? ` (${outputMode})` : ""] }) }) }));
11
+ }
12
+ function GrepWidget({ result, input, phase }) {
13
+ const pattern = typeof input.pattern === "string" ? input.pattern : null;
14
+ if (phase === "running" || phase === "pending") {
15
+ return (_jsx("div", { className: "flex items-center gap-2 text-xs text-muted-foreground py-1", children: _jsxs("span", { className: "animate-pulse", children: ["Searching ", pattern ? `/${pattern}/` : "", "\u2026"] }) }));
16
+ }
17
+ if (typeof result !== "string" || !result)
18
+ return null;
19
+ return (_jsx("div", { className: "py-1 text-xs", children: _jsx("pre", { className: "leading-snug text-foreground bg-accent rounded px-2 py-1.5 overflow-x-auto whitespace-pre-wrap break-all max-h-[300px] overflow-y-auto", children: _jsx("code", { children: result }) }) }));
20
+ }
21
+ registerWidget({
22
+ toolName: "Grep",
23
+ label: "Grep",
24
+ richLabel: (_r, input) => {
25
+ const pattern = typeof input.pattern === "string" ? input.pattern : null;
26
+ if (!pattern)
27
+ return null;
28
+ const type = typeof input.type === "string" ? input.type : null;
29
+ const glob = typeof input.glob === "string" ? input.glob : null;
30
+ const scope = type ? ` in ${type} files` : glob ? ` in ${glob}` : "";
31
+ const pat = pattern.length > 40 ? `${pattern.slice(0, 37)}…` : pattern;
32
+ return `${pat}${scope}`;
33
+ },
34
+ inputRenderer: GrepInputRenderer,
35
+ component: GrepWidget,
36
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { registerWidget } from "../registry.js";
3
+ function basename(filePath) {
4
+ return filePath.split("/").pop() ?? filePath;
5
+ }
6
+ const modeLabel = {
7
+ replace: "Replace cell",
8
+ insert: "Insert cell",
9
+ delete: "Delete cell",
10
+ };
11
+ function NotebookEditInputRenderer({ input }) {
12
+ const notebookPath = typeof input.notebook_path === "string" ? input.notebook_path : null;
13
+ const editMode = typeof input.edit_mode === "string" ? input.edit_mode : "replace";
14
+ const cellType = typeof input.cell_type === "string" ? input.cell_type : null;
15
+ const cellId = typeof input.cell_id === "string" ? input.cell_id : null;
16
+ const cellNumber = typeof input.cell_number === "number" ? input.cell_number : null;
17
+ const newSource = typeof input.new_source === "string" ? input.new_source : null;
18
+ if (!notebookPath)
19
+ return null;
20
+ const cellRef = cellId ?? (cellNumber != null ? `cell ${cellNumber}` : null);
21
+ const cellLabel = cellRef && editMode === "insert" ? `after ${cellRef}` : cellRef;
22
+ return (_jsxs("div", { className: "mt-1.5 space-y-1", children: [_jsxs("div", { className: "text-xs text-muted-foreground", children: [modeLabel[editMode] ?? editMode, cellType && _jsxs("span", { className: "ml-1 opacity-70", children: ["[", cellType, "]"] }), cellLabel && _jsx("span", { className: "ml-1 font-mono opacity-70", children: cellLabel }), _jsx("span", { className: "ml-1.5 font-mono opacity-70", children: basename(notebookPath) })] }), newSource && editMode !== "delete" && (_jsx("pre", { className: "text-[11px] leading-snug text-muted-foreground bg-accent rounded px-2 py-1 overflow-x-auto whitespace-pre-wrap break-all max-h-[120px] overflow-y-auto", children: _jsx("code", { children: newSource }) }))] }));
23
+ }
24
+ function NotebookEditWidget({ input, phase }) {
25
+ const notebookPath = typeof input.notebook_path === "string" ? input.notebook_path : null;
26
+ const editMode = typeof input.edit_mode === "string" ? input.edit_mode : "replace";
27
+ const cellType = typeof input.cell_type === "string" ? input.cell_type : null;
28
+ const newSource = typeof input.new_source === "string" ? input.new_source : null;
29
+ if (phase === "running" || phase === "pending") {
30
+ return (_jsx("div", { className: "flex items-center gap-2 text-xs text-muted-foreground py-1", children: _jsxs("span", { className: "animate-pulse", children: ["Editing ", notebookPath ? basename(notebookPath) : "notebook", "\u2026"] }) }));
31
+ }
32
+ return (_jsxs("div", { className: "py-1 space-y-1.5", children: [_jsxs("div", { className: "text-xs text-muted-foreground", children: [editMode === "insert" ? "Inserted" : editMode === "delete" ? "Deleted" : "Replaced", " cell", cellType && _jsxs("span", { className: "ml-1 opacity-70", children: ["[", cellType, "]"] })] }), newSource && editMode !== "delete" && (_jsx("pre", { className: "text-[11px] leading-snug text-foreground bg-accent rounded px-2 py-1.5 overflow-x-auto whitespace-pre-wrap break-all max-h-[200px] overflow-y-auto", children: _jsx("code", { children: newSource }) }))] }));
33
+ }
34
+ registerWidget({
35
+ toolName: "NotebookEdit",
36
+ label: "Notebook Edit",
37
+ richLabel: (_r, input) => {
38
+ const notebookPath = typeof input.notebook_path === "string" ? input.notebook_path : null;
39
+ const editMode = typeof input.edit_mode === "string" ? input.edit_mode : null;
40
+ const name = notebookPath ? basename(notebookPath) : null;
41
+ if (!name)
42
+ return null;
43
+ return editMode && editMode !== "replace" ? `${editMode} ${name}` : name;
44
+ },
45
+ inputRenderer: NotebookEditInputRenderer,
46
+ component: NotebookEditWidget,
47
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { registerWidget } from "../registry.js";
3
+ /** Show path relative to likely project root, falling back to last 3 segments. */
4
+ function shortPath(filePath) {
5
+ const parts = filePath.split("/");
6
+ // Heuristic: strip up to the first segment after common prefixes
7
+ // e.g. /Users/dan/Documents/my-project/src/server/index.ts → src/server/index.ts
8
+ // Look for common root markers and take everything after
9
+ const markers = ["Documents", "Projects", "repos", "src", "home"];
10
+ for (let i = 0; i < parts.length - 1; i++) {
11
+ if (markers.includes(parts[i])) {
12
+ // Take from the segment AFTER the marker's child (the project dir)
13
+ const projectStart = i + 2; // marker + project-name
14
+ if (projectStart < parts.length) {
15
+ return parts.slice(projectStart).join("/");
16
+ }
17
+ }
18
+ }
19
+ // Fallback: last 3 segments
20
+ return parts.slice(-3).join("/");
21
+ }
22
+ function ReadInputRenderer({ input }) {
23
+ const filePath = typeof input.file_path === "string" ? input.file_path : null;
24
+ if (!filePath)
25
+ return null;
26
+ return _jsx("div", { className: "mt-1.5 text-xs text-muted-foreground font-mono truncate", children: filePath });
27
+ }
28
+ function ReadWidget({ result, input, phase }) {
29
+ const filePath = typeof input.file_path === "string" ? input.file_path : null;
30
+ if (phase === "running" || phase === "pending") {
31
+ return (_jsx("div", { className: "flex items-center gap-2 text-xs text-muted-foreground py-1", children: _jsxs("span", { className: "animate-pulse", children: ["Reading ", filePath ? shortPath(filePath) : "file", "\u2026"] }) }));
32
+ }
33
+ if (typeof result !== "string" || !result)
34
+ return null;
35
+ return (_jsx("div", { className: "py-1 text-xs", children: _jsx("pre", { className: "leading-snug text-foreground bg-accent rounded px-2 py-1.5 overflow-x-auto whitespace-pre-wrap break-all max-h-[300px] overflow-y-auto", children: _jsx("code", { children: result }) }) }));
36
+ }
37
+ registerWidget({
38
+ toolName: "Read",
39
+ label: "Read",
40
+ richLabel: (_r, input) => {
41
+ const filePath = typeof input.file_path === "string" ? input.file_path : null;
42
+ return filePath ? shortPath(filePath) : null;
43
+ },
44
+ inputRenderer: ReadInputRenderer,
45
+ component: ReadWidget,
46
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { registerWidget } from "../registry.js";
3
+ function parseTodos(input) {
4
+ if (!Array.isArray(input.todos))
5
+ return [];
6
+ return input.todos.filter((t) => t != null &&
7
+ typeof t === "object" &&
8
+ typeof t.content === "string" &&
9
+ typeof t.status === "string");
10
+ }
11
+ function TodoList({ todos }) {
12
+ return (_jsx("div", { className: "space-y-0.5 text-xs", children: todos.map((todo, i) => (
13
+ // biome-ignore lint/suspicious/noArrayIndexKey: todo items lack stable IDs
14
+ _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "shrink-0 w-4 text-center", children: todo.status === "completed" ? (_jsx("span", { className: "text-green-600 dark:text-green-400", children: "\u2713" })) : todo.status === "in_progress" ? (_jsx("span", { className: "text-blue-600 dark:text-blue-400", children: "\u25CB" })) : (_jsx("span", { className: "text-muted-foreground", children: "\u25CB" })) }), _jsx("span", { className: todo.status === "completed"
15
+ ? "text-muted-foreground line-through"
16
+ : todo.status === "in_progress"
17
+ ? "text-foreground font-medium"
18
+ : "text-muted-foreground", children: todo.content })] }, i))) }));
19
+ }
20
+ function TodoWriteWidget({ input, phase }) {
21
+ const todos = parseTodos(input);
22
+ if (phase === "running" || phase === "pending") {
23
+ return (_jsx("div", { className: "flex items-center gap-2 text-xs text-muted-foreground py-1", children: _jsx("span", { className: "animate-pulse", children: "Updating tasks\u2026" }) }));
24
+ }
25
+ if (!todos.length)
26
+ return null;
27
+ return (_jsx("div", { className: "py-1", children: _jsx(TodoList, { todos: todos }) }));
28
+ }
29
+ registerWidget({
30
+ toolName: "TodoWrite",
31
+ label: "Todo",
32
+ richLabel: (_r, input) => {
33
+ const todos = parseTodos(input);
34
+ if (!todos.length)
35
+ return null;
36
+ const done = todos.filter((t) => t.status === "completed").length;
37
+ return `${done}/${todos.length} done`;
38
+ },
39
+ component: TodoWriteWidget,
40
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Markdown from "react-markdown";
3
+ import { markdownComponents } from "../markdown-overrides.js";
4
+ import { registerWidget } from "../registry.js";
5
+ function domain(url) {
6
+ try {
7
+ return new URL(url).hostname.replace(/^www\./, "");
8
+ }
9
+ catch {
10
+ return url;
11
+ }
12
+ }
13
+ function faviconUrl(url) {
14
+ try {
15
+ const host = new URL(url).hostname;
16
+ return `https://www.google.com/s2/favicons?sz=32&domain=${host}`;
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
22
+ function WebFetchInputRenderer({ input }) {
23
+ const url = typeof input.url === "string" ? input.url : null;
24
+ const prompt = typeof input.prompt === "string" ? input.prompt : null;
25
+ if (!url)
26
+ return null;
27
+ return (_jsxs("div", { className: "mt-1.5 space-y-1", children: [_jsxs("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1.5 rounded-full bg-accent px-2.5 py-1 text-[11px] leading-none whitespace-nowrap max-w-[280px] text-muted-foreground no-underline hover:bg-accent/80 hover:text-primary transition-colors", children: [_jsx("img", { src: faviconUrl(url), alt: "", width: 14, height: 14, className: "rounded-full shrink-0" }), _jsx("span", { className: "truncate", children: domain(url) })] }), prompt && _jsxs("div", { className: "text-xs italic text-muted-foreground", children: ["\u201C", prompt, "\u201D"] })] }));
28
+ }
29
+ function WebFetchWidget({ result, input, phase }) {
30
+ if (phase === "running" || phase === "pending") {
31
+ const url = typeof input.url === "string" ? input.url : null;
32
+ return (_jsx("div", { className: "flex items-center gap-2 text-xs text-muted-foreground py-1", children: _jsxs("span", { className: "animate-pulse", children: ["Fetching ", url ? domain(url) : "page", "\u2026"] }) }));
33
+ }
34
+ if (typeof result !== "string" || !result)
35
+ return null;
36
+ const url = typeof input.url === "string" ? input.url : null;
37
+ return (_jsxs("div", { className: "py-1 space-y-1.5 text-xs", children: [url && (_jsxs("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1.5 rounded-full bg-accent px-2.5 py-1 text-[11px] leading-none whitespace-nowrap max-w-[280px] text-muted-foreground no-underline hover:bg-accent/80 hover:text-primary transition-colors", children: [_jsx("img", { src: faviconUrl(url), alt: "", width: 14, height: 14, className: "rounded-full shrink-0" }), _jsx("span", { className: "truncate", children: domain(url) })] })), _jsx("div", { className: "text-foreground leading-relaxed [&>*:first-child]:mt-0 [&>*:last-child]:mb-0", children: _jsx(Markdown, { components: markdownComponents, children: result }) })] }));
38
+ }
39
+ registerWidget({
40
+ toolName: "WebFetch",
41
+ label: "Web Fetch",
42
+ richLabel: (_r, input) => {
43
+ const url = typeof input.url === "string" ? input.url : null;
44
+ return url ? domain(url) : null;
45
+ },
46
+ inputRenderer: WebFetchInputRenderer,
47
+ component: WebFetchWidget,
48
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,85 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { registerWidget } from "../registry.js";
4
+ function extractJsonArray(str, start) {
5
+ if (str[start] !== "[")
6
+ return null;
7
+ let depth = 0;
8
+ for (let i = start; i < str.length; i++) {
9
+ if (str[i] === "[")
10
+ depth++;
11
+ else if (str[i] === "]") {
12
+ depth--;
13
+ if (depth === 0)
14
+ return str.slice(start, i + 1);
15
+ }
16
+ }
17
+ return null;
18
+ }
19
+ function parseSearchResult(raw) {
20
+ if (typeof raw !== "string")
21
+ return null;
22
+ const queryMatch = raw.match(/^Web search results for query: "(.+?)"\n/);
23
+ if (!queryMatch)
24
+ return null;
25
+ const linksIdx = raw.indexOf("Links: [");
26
+ if (linksIdx === -1)
27
+ return null;
28
+ const jsonStr = extractJsonArray(raw, linksIdx + 7);
29
+ if (!jsonStr)
30
+ return null;
31
+ try {
32
+ const links = JSON.parse(jsonStr);
33
+ return { query: queryMatch[1], links };
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ function faviconUrl(url) {
40
+ try {
41
+ const host = new URL(url).hostname;
42
+ return `https://www.google.com/s2/favicons?sz=32&domain=${host}`;
43
+ }
44
+ catch {
45
+ return undefined;
46
+ }
47
+ }
48
+ function domain(url) {
49
+ try {
50
+ return new URL(url).hostname.replace(/^www\./, "");
51
+ }
52
+ catch {
53
+ return url;
54
+ }
55
+ }
56
+ function WebSearchInputRenderer({ input }) {
57
+ const query = typeof input.query === "string" ? input.query : null;
58
+ if (!query)
59
+ return null;
60
+ return _jsxs("div", { className: "mt-1.5 text-xs italic text-muted-foreground", children: ["\u201C", query, "\u201D"] });
61
+ }
62
+ function WebSearchWidget({ result, phase }) {
63
+ const [expanded, setExpanded] = useState(false);
64
+ if (phase === "running" || phase === "pending") {
65
+ return (_jsx("div", { className: "flex items-center gap-2 text-xs text-muted-foreground py-1", children: _jsx("span", { className: "animate-pulse", children: "Searching the web\u2026" }) }));
66
+ }
67
+ const parsed = parseSearchResult(result);
68
+ if (!parsed)
69
+ return null;
70
+ const pills = parsed.links.map((link) => (_jsxs("a", { href: link.url, target: "_blank", rel: "noopener noreferrer", title: link.title, className: "inline-flex items-center gap-1.5 rounded-full bg-accent px-2.5 py-1 text-[11px] leading-none whitespace-nowrap max-w-[180px] text-muted-foreground no-underline hover:bg-accent/80 hover:text-primary transition-colors shrink-0", children: [_jsx("img", { src: faviconUrl(link.url), alt: "", width: 14, height: 14, className: "rounded-full shrink-0" }), _jsx("span", { className: "truncate", children: domain(link.url) })] }, link.url)));
71
+ if (expanded) {
72
+ return (_jsxs("div", { className: "py-1 space-y-1.5", children: [_jsx("div", { className: "flex flex-wrap gap-1.5", children: pills }), _jsx("button", { type: "button", onClick: () => setExpanded(false), className: "text-[10px] text-muted-foreground/60 hover:text-muted-foreground transition-colors", children: "Show less" })] }));
73
+ }
74
+ return (_jsxs("div", { className: "py-1 space-y-0.5", children: [_jsxs("div", { className: "relative", children: [_jsx("div", { className: "flex flex-nowrap gap-1.5 overflow-hidden max-h-[22px]", children: pills }), _jsx("div", { className: "pointer-events-none absolute inset-y-0 right-0 w-12 bg-gradient-to-l from-accent/50 to-transparent" })] }), _jsxs("button", { type: "button", onClick: () => setExpanded(true), className: "text-[10px] text-muted-foreground/60 hover:text-muted-foreground transition-colors pt-0.5", children: ["Show all ", parsed.links.length, " sources"] })] }));
75
+ }
76
+ registerWidget({
77
+ toolName: "WebSearch",
78
+ label: "Web Search",
79
+ richLabel: (r) => {
80
+ const p = parseSearchResult(r);
81
+ return p ? `"${p.query}" · ${p.links.length} sources` : null;
82
+ },
83
+ inputRenderer: WebSearchInputRenderer,
84
+ component: WebSearchWidget,
85
+ });
@@ -0,0 +1 @@
1
+ export {};