@radnine/storybook-addon-claude 0.3.3 → 0.5.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.
package/dist/manager.js CHANGED
@@ -1,33 +1,1827 @@
1
- "use strict";
2
-
3
- var _managerApi = require("storybook/manager-api");
4
- var _constants = require("./constants");
5
- var _Panel = require("./Panel");
6
- var _GlobalPanel = require("./GlobalPanel");
7
- var _GlobalChatButton = require("./components/GlobalChatButton");
8
- _managerApi.addons.register(_constants.ADDON_ID, api => {
9
- // Component-scoped chat panel (addon area below canvas)
10
- _managerApi.addons.add(_constants.PANEL_ID, {
11
- type: _managerApi.types.PANEL,
12
- title: 'Claude',
13
- match: ({
14
- viewMode
15
- }) => viewMode === 'story',
16
- render: _Panel.ClaudePanel
1
+ // src/manager.js
2
+ import { addons, types } from "storybook/manager-api";
3
+
4
+ // src/constants.js
5
+ var ADDON_ID = "storybook-addon-claude";
6
+ var PANEL_ID = `${ADDON_ID}/panel`;
7
+ var PAGE_ID = `${ADDON_ID}/page`;
8
+ var TOOL_ID = `${ADDON_ID}/tool`;
9
+ var DEFAULT_PORT = 3001;
10
+ var DEFAULT_HOST = "localhost";
11
+ var GLOBAL_SESSION_KEY = "claude-global";
12
+ var CONNECTION_STATES = {
13
+ DISCONNECTED: "disconnected",
14
+ CONNECTING: "connecting",
15
+ CONNECTED: "connected",
16
+ RECONNECTING: "reconnecting"
17
+ };
18
+
19
+ // src/Panel.jsx
20
+ import React7, { useCallback as useCallback3 } from "react";
21
+ import { useAddonState } from "storybook/manager-api";
22
+
23
+ // src/components/ConnectionStatus.jsx
24
+ import React from "react";
25
+ var STATUS_CONFIG = {
26
+ [CONNECTION_STATES.CONNECTED]: {
27
+ label: "Connected",
28
+ color: "#4caf50"
29
+ },
30
+ [CONNECTION_STATES.CONNECTING]: {
31
+ label: "Connecting...",
32
+ color: "#ff9800"
33
+ },
34
+ [CONNECTION_STATES.RECONNECTING]: {
35
+ label: "Reconnecting...",
36
+ color: "#ff9800"
37
+ },
38
+ [CONNECTION_STATES.DISCONNECTED]: {
39
+ label: "Disconnected",
40
+ color: "#f44336"
41
+ }
42
+ };
43
+ function ConnectionStatus({ state, sessionId, onDisconnect, onNewChat, isProcessing }) {
44
+ const config = STATUS_CONFIG[state] || STATUS_CONFIG[CONNECTION_STATES.DISCONNECTED];
45
+ const isConnected = state === CONNECTION_STATES.CONNECTED;
46
+ return /* @__PURE__ */ React.createElement("div", { style: styles.container }, /* @__PURE__ */ React.createElement("div", { style: styles.indicator }, /* @__PURE__ */ React.createElement("span", { style: { ...styles.dot, backgroundColor: config.color } }), /* @__PURE__ */ React.createElement("span", { style: styles.label }, config.label)), /* @__PURE__ */ React.createElement("div", { style: styles.right }, sessionId && /* @__PURE__ */ React.createElement("span", { style: styles.sessionId, title: sessionId }, sessionId.slice(0, 8), "..."), onNewChat && isConnected && /* @__PURE__ */ React.createElement(
47
+ "button",
48
+ {
49
+ onClick: onNewChat,
50
+ disabled: isProcessing,
51
+ style: {
52
+ ...styles.actionButton,
53
+ ...isProcessing ? styles.actionButtonDisabled : {}
54
+ },
55
+ title: "Clear chat and start fresh session",
56
+ "aria-label": "New Chat"
57
+ },
58
+ "New Chat"
59
+ ), onDisconnect && /* @__PURE__ */ React.createElement(
60
+ "button",
61
+ {
62
+ onClick: onDisconnect,
63
+ style: styles.actionButton,
64
+ title: isConnected ? "Disconnect" : "Change Token",
65
+ "aria-label": isConnected ? "Disconnect" : "Change Token"
66
+ },
67
+ isConnected ? "Disconnect" : "Change Token"
68
+ )));
69
+ }
70
+ var styles = {
71
+ container: {
72
+ display: "flex",
73
+ alignItems: "center",
74
+ justifyContent: "space-between",
75
+ padding: "6px 12px",
76
+ borderBottom: "1px solid rgba(0,0,0,0.1)",
77
+ fontSize: "12px",
78
+ fontFamily: "inherit"
79
+ },
80
+ indicator: {
81
+ display: "flex",
82
+ alignItems: "center",
83
+ gap: "6px"
84
+ },
85
+ dot: {
86
+ width: "8px",
87
+ height: "8px",
88
+ borderRadius: "50%",
89
+ display: "inline-block"
90
+ },
91
+ label: {
92
+ color: "#666"
93
+ },
94
+ right: {
95
+ display: "flex",
96
+ alignItems: "center",
97
+ gap: "8px"
98
+ },
99
+ sessionId: {
100
+ color: "#999",
101
+ fontFamily: "monospace",
102
+ fontSize: "11px"
103
+ },
104
+ actionButton: {
105
+ padding: "2px 8px",
106
+ border: "1px solid #ccc",
107
+ borderRadius: "4px",
108
+ backgroundColor: "transparent",
109
+ color: "#666",
110
+ fontSize: "11px",
111
+ cursor: "pointer",
112
+ lineHeight: "1.4"
113
+ },
114
+ actionButtonDisabled: {
115
+ opacity: 0.4,
116
+ cursor: "default"
117
+ }
118
+ };
119
+
120
+ // src/components/MessageList.jsx
121
+ import React3, { useEffect, useRef } from "react";
122
+
123
+ // src/components/MarkdownContent.jsx
124
+ import React2, { useMemo } from "react";
125
+ import { marked } from "marked";
126
+ var renderer = new marked.Renderer();
127
+ renderer.link = function({ href, title, text }) {
128
+ const titleAttr = title ? ` title="${title}"` : "";
129
+ return `<a href="${href}" target="_blank" rel="noopener noreferrer"${titleAttr}>${text}</a>`;
130
+ };
131
+ marked.setOptions({
132
+ renderer,
133
+ gfm: true,
134
+ breaks: true
135
+ });
136
+ var MARKDOWN_STYLES = `
137
+ .claude-md { line-height: 1.5; word-break: break-word; }
138
+ .claude-md h1 { font-size: 1.1em; font-weight: 600; margin: 8px 0 4px; }
139
+ .claude-md h2 { font-size: 1.05em; font-weight: 600; margin: 8px 0 4px; }
140
+ .claude-md h3 { font-size: 1em; font-weight: 600; margin: 6px 0 3px; }
141
+ .claude-md h4, .claude-md h5, .claude-md h6 { font-size: 1em; font-weight: 600; margin: 4px 0 2px; }
142
+ .claude-md p { margin: 4px 0; }
143
+ .claude-md ul, .claude-md ol { margin: 4px 0; padding-left: 20px; }
144
+ .claude-md li { margin: 2px 0; }
145
+ .claude-md code { background: rgba(0,0,0,0.06); padding: 1px 4px; border-radius: 3px; font-size: 0.9em; }
146
+ .claude-md pre { background: rgba(0,0,0,0.04); padding: 8px; border-radius: 4px; overflow-x: auto; margin: 4px 0; }
147
+ .claude-md pre code { background: none; padding: 0; }
148
+ .claude-md table { border-collapse: collapse; margin: 4px 0; font-size: 0.95em; }
149
+ .claude-md th, .claude-md td { border: 1px solid rgba(0,0,0,0.15); padding: 4px 8px; }
150
+ .claude-md th { background: rgba(0,0,0,0.04); font-weight: 600; }
151
+ .claude-md blockquote { border-left: 3px solid rgba(0,0,0,0.15); margin: 4px 0; padding: 2px 8px; color: #666; }
152
+ .claude-md a { color: #0366d6; text-decoration: none; }
153
+ .claude-md a:hover { text-decoration: underline; }
154
+ .claude-md hr { border: none; border-top: 1px solid rgba(0,0,0,0.1); margin: 8px 0; }
155
+ .claude-md > *:first-child { margin-top: 0; }
156
+ .claude-md > *:last-child { margin-bottom: 0; }
157
+ `;
158
+ var stylesInjected = false;
159
+ function ensureStyles() {
160
+ if (stylesInjected) return;
161
+ try {
162
+ const style = document.createElement("style");
163
+ style.textContent = MARKDOWN_STYLES;
164
+ document.head.appendChild(style);
165
+ stylesInjected = true;
166
+ } catch (_e) {
167
+ }
168
+ }
169
+ function MarkdownContent({ text }) {
170
+ ensureStyles();
171
+ const html = useMemo(() => {
172
+ if (!text) return "";
173
+ return marked.parse(text);
174
+ }, [text]);
175
+ return /* @__PURE__ */ React2.createElement(
176
+ "div",
177
+ {
178
+ className: "claude-md",
179
+ dangerouslySetInnerHTML: { __html: html }
180
+ }
181
+ );
182
+ }
183
+
184
+ // src/components/MessageList.jsx
185
+ function MessageList({ messages }) {
186
+ const bottomRef = useRef(null);
187
+ useEffect(() => {
188
+ var _a;
189
+ (_a = bottomRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
190
+ }, [messages]);
191
+ if (messages.length === 0) {
192
+ return /* @__PURE__ */ React3.createElement("div", { style: styles2.empty }, "No messages yet. Send a message to start chatting with Claude.");
193
+ }
194
+ return /* @__PURE__ */ React3.createElement("div", { style: styles2.container }, messages.map((msg) => /* @__PURE__ */ React3.createElement(MessageItem, { key: msg.id, message: msg })), /* @__PURE__ */ React3.createElement("div", { ref: bottomRef }));
195
+ }
196
+ function MessageItem({ message }) {
197
+ switch (message.type) {
198
+ case "user_input":
199
+ return /* @__PURE__ */ React3.createElement(UserMessage, { text: message.text });
200
+ case "skill_invocation":
201
+ return /* @__PURE__ */ React3.createElement(SkillInvocationMessage, { skill: message.skill });
202
+ case "output":
203
+ return /* @__PURE__ */ React3.createElement(OutputMessage, { data: message.data, replay: message.replay });
204
+ case "complete":
205
+ return /* @__PURE__ */ React3.createElement(CompleteMessage, { message });
206
+ case "error":
207
+ return /* @__PURE__ */ React3.createElement(ErrorMessage, { message });
208
+ default:
209
+ return null;
210
+ }
211
+ }
212
+ function UserMessage({ text }) {
213
+ return /* @__PURE__ */ React3.createElement("div", { style: styles2.userRow }, /* @__PURE__ */ React3.createElement("div", { style: styles2.userBubble }, text));
214
+ }
215
+ function OutputMessage({ data, replay }) {
216
+ if (!data) return null;
217
+ const classifiedType = classifyOutputType(data);
218
+ switch (classifiedType) {
219
+ case "assistant":
220
+ return /* @__PURE__ */ React3.createElement(AssistantMessage, { data, replay });
221
+ case "tool_use":
222
+ return /* @__PURE__ */ React3.createElement(ToolUseMessage, { data });
223
+ case "tool_result":
224
+ return /* @__PURE__ */ React3.createElement(ToolResultMessage, { data });
225
+ case "result":
226
+ return /* @__PURE__ */ React3.createElement(ResultMessage, { data });
227
+ case "system":
228
+ return /* @__PURE__ */ React3.createElement(SystemMessage, { data });
229
+ case "rate_limit_event":
230
+ return /* @__PURE__ */ React3.createElement(RateLimitMessage, { data });
231
+ case "_skip":
232
+ return null;
233
+ default:
234
+ return /* @__PURE__ */ React3.createElement(GenericOutputMessage, { data, replay });
235
+ }
236
+ }
237
+ function classifyOutputType(data) {
238
+ var _a;
239
+ if (!data || typeof data !== "object") return void 0;
240
+ const rawType = data.type;
241
+ const content = ((_a = data == null ? void 0 : data.message) == null ? void 0 : _a.content) || (data == null ? void 0 : data.content);
242
+ const contentArray = Array.isArray(content) ? content : [];
243
+ switch (rawType) {
244
+ case "assistant": {
245
+ const hasToolUse = contentArray.some((b) => (b == null ? void 0 : b.type) === "tool_use");
246
+ const hasText = contentArray.some((b) => {
247
+ var _a2;
248
+ return (b == null ? void 0 : b.type) === "text" && ((_a2 = b.text) == null ? void 0 : _a2.trim());
249
+ });
250
+ const hasOnlyThinking = contentArray.length > 0 && contentArray.every((b) => (b == null ? void 0 : b.type) === "thinking");
251
+ if (hasOnlyThinking) return "_skip";
252
+ if (hasToolUse && !hasText) return "tool_use";
253
+ return "assistant";
254
+ }
255
+ case "user": {
256
+ const hasToolResult = contentArray.some((b) => (b == null ? void 0 : b.type) === "tool_result");
257
+ if (hasToolResult) return "tool_result";
258
+ return "_skip";
259
+ }
260
+ case "system": {
261
+ if (data.subtype === "init") return "_skip";
262
+ return "system";
263
+ }
264
+ default:
265
+ return rawType;
266
+ }
267
+ }
268
+ function AssistantMessage({ data, replay }) {
269
+ const text = extractAssistantText(data);
270
+ return /* @__PURE__ */ React3.createElement("div", { style: styles2.assistantRow }, /* @__PURE__ */ React3.createElement("div", { style: { ...styles2.assistantBubble, ...replay ? styles2.replay : {} } }, text ? /* @__PURE__ */ React3.createElement(MarkdownContent, { text }) : "(empty response)"));
271
+ }
272
+ function ToolUseMessage({ data }) {
273
+ const toolName = extractToolName(data);
274
+ const summary = extractToolSummary(data);
275
+ return /* @__PURE__ */ React3.createElement("div", { style: styles2.toolRow }, /* @__PURE__ */ React3.createElement("details", { style: styles2.toolDetails }, /* @__PURE__ */ React3.createElement("summary", { style: styles2.toolSummary }, /* @__PURE__ */ React3.createElement("span", { style: styles2.toolIcon }, "\u2699"), " ", toolName, summary ? `: ${summary}` : ""), /* @__PURE__ */ React3.createElement("pre", { style: styles2.toolContent }, JSON.stringify(data, null, 2))));
276
+ }
277
+ function ToolResultMessage({ data }) {
278
+ const isError = extractToolResultError(data);
279
+ const summary = extractToolResultSummary(data);
280
+ return /* @__PURE__ */ React3.createElement("div", { style: styles2.toolRow }, /* @__PURE__ */ React3.createElement("details", { style: styles2.toolDetails }, /* @__PURE__ */ React3.createElement("summary", { style: { ...styles2.toolSummary, color: isError ? "#f44336" : "#4caf50" } }, isError ? "Error" : "Done", summary ? `: ${summary}` : ""), /* @__PURE__ */ React3.createElement("pre", { style: styles2.toolContent }, JSON.stringify(data, null, 2))));
281
+ }
282
+ function ResultMessage({ data }) {
283
+ const subtype = (data == null ? void 0 : data.subtype) || "unknown";
284
+ return /* @__PURE__ */ React3.createElement("div", { style: styles2.resultRow }, /* @__PURE__ */ React3.createElement("div", { style: styles2.resultBubble }, /* @__PURE__ */ React3.createElement("span", { style: styles2.resultLabel }, subtype === "success" ? "Completed" : `Result: ${subtype}`)));
285
+ }
286
+ function SystemMessage({ data }) {
287
+ if (typeof (data == null ? void 0 : data.message) === "string") {
288
+ return /* @__PURE__ */ React3.createElement("div", { style: styles2.systemRow }, /* @__PURE__ */ React3.createElement("div", { style: styles2.systemBubble }, data.message));
289
+ }
290
+ const label = (data == null ? void 0 : data.subtype) ? `System: ${data.subtype}` : "System event";
291
+ return /* @__PURE__ */ React3.createElement("div", { style: styles2.toolRow }, /* @__PURE__ */ React3.createElement("details", { style: styles2.toolDetails }, /* @__PURE__ */ React3.createElement("summary", { style: styles2.toolSummary }, label), /* @__PURE__ */ React3.createElement("pre", { style: styles2.toolContent }, JSON.stringify(data, null, 2))));
292
+ }
293
+ function RateLimitMessage({ data }) {
294
+ return /* @__PURE__ */ React3.createElement("div", { style: styles2.toolRow }, /* @__PURE__ */ React3.createElement("details", { style: styles2.toolDetails }, /* @__PURE__ */ React3.createElement("summary", { style: { ...styles2.toolSummary, color: "#ff9800" } }, "Rate limited (waiting...)"), /* @__PURE__ */ React3.createElement("pre", { style: styles2.toolContent }, JSON.stringify(data, null, 2))));
295
+ }
296
+ function GenericOutputMessage({ data, replay }) {
297
+ const text = extractAssistantText(data);
298
+ if (text && text.startsWith("[Context from Storybook]")) {
299
+ return null;
300
+ }
301
+ if (text) {
302
+ return /* @__PURE__ */ React3.createElement("div", { style: styles2.assistantRow }, /* @__PURE__ */ React3.createElement("div", { style: { ...styles2.assistantBubble, ...replay ? styles2.replay : {} } }, /* @__PURE__ */ React3.createElement(MarkdownContent, { text })));
303
+ }
304
+ const summaryLabel = typeof data === "object" && data.type ? `System: ${data.type}` : "System message";
305
+ return /* @__PURE__ */ React3.createElement("div", { style: styles2.toolRow }, /* @__PURE__ */ React3.createElement("details", { style: styles2.toolDetails }, /* @__PURE__ */ React3.createElement("summary", { style: styles2.toolSummary }, summaryLabel), /* @__PURE__ */ React3.createElement("pre", { style: styles2.toolContent }, JSON.stringify(data, null, 2))));
306
+ }
307
+ function CompleteMessage({ message }) {
308
+ return /* @__PURE__ */ React3.createElement("div", { style: styles2.systemRow }, /* @__PURE__ */ React3.createElement("div", { style: styles2.completeBubble }, "Command completed (exit ", message.exitCode ?? "?", ")", message.durationMs != null && ` in ${(message.durationMs / 1e3).toFixed(1)}s`));
309
+ }
310
+ function SkillInvocationMessage({ skill }) {
311
+ return /* @__PURE__ */ React3.createElement("div", { style: styles2.skillRow }, /* @__PURE__ */ React3.createElement("div", { style: styles2.skillBubble }, /* @__PURE__ */ React3.createElement("span", { style: styles2.skillIcon }, skill.icon), /* @__PURE__ */ React3.createElement("span", { style: styles2.skillLabel }, "Skill: ", skill.label)));
312
+ }
313
+ function ErrorMessage({ message }) {
314
+ return /* @__PURE__ */ React3.createElement("div", { style: styles2.errorRow }, /* @__PURE__ */ React3.createElement("div", { style: styles2.errorBubble }, message.code && /* @__PURE__ */ React3.createElement("span", { style: styles2.errorCode }, message.code, ": "), message.message || "Unknown error"));
315
+ }
316
+ function extractAssistantText(data) {
317
+ var _a;
318
+ const content = ((_a = data == null ? void 0 : data.message) == null ? void 0 : _a.content) || (data == null ? void 0 : data.content);
319
+ if (typeof content === "string") return content;
320
+ if (Array.isArray(content)) {
321
+ const textBlocks = content.filter((b) => b.type === "text");
322
+ return textBlocks.map((b) => b.text).join("\n");
323
+ }
324
+ if (content && content.type === "text") return content.text;
325
+ return null;
326
+ }
327
+ function extractToolName(data) {
328
+ var _a;
329
+ const content = ((_a = data == null ? void 0 : data.message) == null ? void 0 : _a.content) || (data == null ? void 0 : data.content);
330
+ if (Array.isArray(content)) {
331
+ const toolBlock = content.find((b) => b.type === "tool_use");
332
+ return (toolBlock == null ? void 0 : toolBlock.name) || "Tool";
333
+ }
334
+ if ((content == null ? void 0 : content.type) === "tool_use") return content.name || "Tool";
335
+ return "Tool";
336
+ }
337
+ function extractToolSummary(data) {
338
+ var _a;
339
+ const content = ((_a = data == null ? void 0 : data.message) == null ? void 0 : _a.content) || (data == null ? void 0 : data.content);
340
+ if (Array.isArray(content)) {
341
+ const toolBlock = content.find((b) => b.type === "tool_use");
342
+ if (toolBlock == null ? void 0 : toolBlock.input) {
343
+ const input = toolBlock.input;
344
+ if (input.file_path) return input.file_path;
345
+ if (input.command) return input.command.slice(0, 60);
346
+ if (input.path) return input.path;
347
+ }
348
+ }
349
+ return null;
350
+ }
351
+ function extractToolResultError(data) {
352
+ var _a;
353
+ const content = ((_a = data == null ? void 0 : data.message) == null ? void 0 : _a.content) || (data == null ? void 0 : data.content);
354
+ if (Array.isArray(content)) {
355
+ const resultBlock = content.find((b) => b.type === "tool_result");
356
+ return (resultBlock == null ? void 0 : resultBlock.is_error) === true;
357
+ }
358
+ return false;
359
+ }
360
+ function extractToolResultSummary(data) {
361
+ var _a;
362
+ const content = ((_a = data == null ? void 0 : data.message) == null ? void 0 : _a.content) || (data == null ? void 0 : data.content);
363
+ if (Array.isArray(content)) {
364
+ const resultBlock = content.find((b) => b.type === "tool_result");
365
+ if (typeof (resultBlock == null ? void 0 : resultBlock.content) === "string") {
366
+ return resultBlock.content.split("\n")[0].slice(0, 100);
367
+ }
368
+ }
369
+ return null;
370
+ }
371
+ var styles2 = {
372
+ container: {
373
+ flex: 1,
374
+ overflowY: "auto",
375
+ padding: "12px",
376
+ display: "flex",
377
+ flexDirection: "column",
378
+ gap: "8px"
379
+ },
380
+ empty: {
381
+ flex: 1,
382
+ display: "flex",
383
+ alignItems: "center",
384
+ justifyContent: "center",
385
+ color: "#999",
386
+ fontSize: "13px",
387
+ padding: "24px",
388
+ textAlign: "center"
389
+ },
390
+ // User messages (right-aligned)
391
+ userRow: {
392
+ display: "flex",
393
+ justifyContent: "flex-end"
394
+ },
395
+ userBubble: {
396
+ maxWidth: "80%",
397
+ padding: "8px 12px",
398
+ borderRadius: "12px 12px 2px 12px",
399
+ backgroundColor: "#1ea7fd",
400
+ color: "#fff",
401
+ fontSize: "13px",
402
+ lineHeight: "1.4",
403
+ whiteSpace: "pre-wrap",
404
+ wordBreak: "break-word"
405
+ },
406
+ // Assistant messages (left-aligned)
407
+ assistantRow: {
408
+ display: "flex",
409
+ justifyContent: "flex-start"
410
+ },
411
+ assistantBubble: {
412
+ maxWidth: "80%",
413
+ padding: "8px 12px",
414
+ borderRadius: "12px 12px 12px 2px",
415
+ backgroundColor: "#f0f0f0",
416
+ color: "#333",
417
+ fontSize: "13px",
418
+ lineHeight: "1.4",
419
+ wordBreak: "break-word"
420
+ },
421
+ replay: {
422
+ opacity: 0.7
423
+ },
424
+ // Tool messages (collapsed)
425
+ toolRow: {
426
+ display: "flex",
427
+ justifyContent: "flex-start",
428
+ maxWidth: "90%"
429
+ },
430
+ toolDetails: {
431
+ fontSize: "12px",
432
+ color: "#666",
433
+ cursor: "pointer",
434
+ width: "100%"
435
+ },
436
+ toolSummary: {
437
+ padding: "4px 8px",
438
+ borderRadius: "6px",
439
+ backgroundColor: "#f5f5f5",
440
+ border: "1px solid #e0e0e0",
441
+ listStyle: "none",
442
+ userSelect: "none"
443
+ },
444
+ toolIcon: {
445
+ fontSize: "11px"
446
+ },
447
+ toolContent: {
448
+ margin: "4px 0 0 0",
449
+ padding: "8px",
450
+ backgroundColor: "#fafafa",
451
+ border: "1px solid #e0e0e0",
452
+ borderRadius: "4px",
453
+ fontSize: "11px",
454
+ overflow: "auto",
455
+ maxHeight: "200px"
456
+ },
457
+ // Result / system / complete
458
+ resultRow: {
459
+ display: "flex",
460
+ justifyContent: "center"
461
+ },
462
+ resultBubble: {
463
+ padding: "6px 12px",
464
+ borderRadius: "8px",
465
+ backgroundColor: "#e8f5e9",
466
+ fontSize: "12px",
467
+ color: "#2e7d32",
468
+ textAlign: "center"
469
+ },
470
+ resultLabel: {
471
+ fontWeight: 600
472
+ },
473
+ resultText: {
474
+ marginTop: "4px",
475
+ fontSize: "12px",
476
+ color: "#555"
477
+ },
478
+ systemRow: {
479
+ display: "flex",
480
+ justifyContent: "center"
481
+ },
482
+ systemBubble: {
483
+ padding: "4px 10px",
484
+ borderRadius: "6px",
485
+ backgroundColor: "#f5f5f5",
486
+ fontSize: "11px",
487
+ color: "#999",
488
+ fontStyle: "italic"
489
+ },
490
+ completeBubble: {
491
+ padding: "4px 10px",
492
+ borderRadius: "6px",
493
+ backgroundColor: "#f0f4f8",
494
+ fontSize: "11px",
495
+ color: "#777"
496
+ },
497
+ genericContent: {
498
+ margin: 0,
499
+ padding: "8px",
500
+ backgroundColor: "#fafafa",
501
+ border: "1px solid #e0e0e0",
502
+ borderRadius: "4px",
503
+ fontSize: "11px",
504
+ overflow: "auto",
505
+ maxHeight: "200px",
506
+ maxWidth: "90%"
507
+ },
508
+ // Skill invocation
509
+ skillRow: {
510
+ display: "flex",
511
+ justifyContent: "center"
512
+ },
513
+ skillBubble: {
514
+ display: "inline-flex",
515
+ alignItems: "center",
516
+ gap: "6px",
517
+ padding: "6px 14px",
518
+ borderRadius: "8px",
519
+ backgroundColor: "#e8eaf6",
520
+ fontSize: "12px",
521
+ color: "#3949ab",
522
+ fontWeight: 500
523
+ },
524
+ skillIcon: {
525
+ fontSize: "14px"
526
+ },
527
+ skillLabel: {
528
+ fontWeight: 600
529
+ },
530
+ // Error messages
531
+ errorRow: {
532
+ display: "flex",
533
+ justifyContent: "center"
534
+ },
535
+ errorBubble: {
536
+ padding: "6px 12px",
537
+ borderRadius: "8px",
538
+ backgroundColor: "#ffebee",
539
+ fontSize: "12px",
540
+ color: "#c62828"
541
+ },
542
+ errorCode: {
543
+ fontWeight: 600,
544
+ fontFamily: "monospace"
545
+ }
546
+ };
547
+
548
+ // src/components/MessageInput.jsx
549
+ import React4, { useState, useRef as useRef2 } from "react";
550
+ function MessageInput({ onSend, disabled, isProcessing, placeholder }) {
551
+ const [text, setText] = useState("");
552
+ const textareaRef = useRef2(null);
553
+ const handleSend = () => {
554
+ const trimmed = text.trim();
555
+ if (!trimmed || disabled) return;
556
+ onSend(trimmed);
557
+ setText("");
558
+ if (textareaRef.current) {
559
+ textareaRef.current.style.height = "auto";
560
+ }
561
+ };
562
+ const handleKeyDown = (e) => {
563
+ if (e.key === "Enter" && !e.shiftKey) {
564
+ e.preventDefault();
565
+ handleSend();
566
+ }
567
+ };
568
+ const handleInput = (e) => {
569
+ setText(e.target.value);
570
+ const el = e.target;
571
+ el.style.height = "auto";
572
+ el.style.height = Math.min(el.scrollHeight, 120) + "px";
573
+ };
574
+ return /* @__PURE__ */ React4.createElement("div", { style: styles3.container }, /* @__PURE__ */ React4.createElement(
575
+ "textarea",
576
+ {
577
+ ref: textareaRef,
578
+ style: styles3.textarea,
579
+ value: text,
580
+ onChange: handleInput,
581
+ onKeyDown: handleKeyDown,
582
+ placeholder: disabled ? "Connect to daemon first..." : placeholder || "Ask Claude...",
583
+ disabled,
584
+ rows: 1
585
+ }
586
+ ), /* @__PURE__ */ React4.createElement(
587
+ "button",
588
+ {
589
+ style: {
590
+ ...styles3.button,
591
+ ...disabled || !text.trim() ? styles3.buttonDisabled : {}
592
+ },
593
+ onClick: handleSend,
594
+ disabled: disabled || !text.trim(),
595
+ title: "Send message (Enter)"
596
+ },
597
+ isProcessing ? "..." : "Send"
598
+ ));
599
+ }
600
+ var styles3 = {
601
+ container: {
602
+ display: "flex",
603
+ alignItems: "flex-end",
604
+ gap: "8px",
605
+ padding: "8px 12px",
606
+ borderTop: "1px solid rgba(0,0,0,0.1)"
607
+ },
608
+ textarea: {
609
+ flex: 1,
610
+ padding: "8px 10px",
611
+ border: "1px solid #ddd",
612
+ borderRadius: "8px",
613
+ fontSize: "13px",
614
+ fontFamily: "inherit",
615
+ lineHeight: "1.4",
616
+ resize: "none",
617
+ outline: "none",
618
+ minHeight: "36px",
619
+ maxHeight: "120px"
620
+ },
621
+ button: {
622
+ padding: "8px 16px",
623
+ border: "none",
624
+ borderRadius: "8px",
625
+ backgroundColor: "#1ea7fd",
626
+ color: "#fff",
627
+ fontSize: "13px",
628
+ fontWeight: 600,
629
+ cursor: "pointer",
630
+ whiteSpace: "nowrap",
631
+ minHeight: "36px"
632
+ },
633
+ buttonDisabled: {
634
+ backgroundColor: "#ccc",
635
+ cursor: "not-allowed"
636
+ }
637
+ };
638
+
639
+ // src/components/TokenInput.jsx
640
+ import React5, { useState as useState2 } from "react";
641
+ function TokenInput({ onSubmit, defaultPort = 3001 }) {
642
+ const [token, setToken] = useState2("");
643
+ const [port, setPort] = useState2(String(defaultPort));
644
+ const handleSubmit = (e) => {
645
+ e.preventDefault();
646
+ const trimmed = token.trim();
647
+ if (trimmed) {
648
+ const portNum = parseInt(port, 10) || defaultPort;
649
+ onSubmit(trimmed, portNum);
650
+ }
651
+ };
652
+ return /* @__PURE__ */ React5.createElement("div", { style: styles4.container }, /* @__PURE__ */ React5.createElement("div", { style: styles4.title }, "Connect to Claude Daemon"), /* @__PURE__ */ React5.createElement("p", { style: styles4.description }, "Enter the auth token from the daemon's startup output, or set the ", /* @__PURE__ */ React5.createElement("code", { style: styles4.code }, "CLAUDE_DAEMON_TOKEN"), " environment variable."), /* @__PURE__ */ React5.createElement("form", { onSubmit: handleSubmit, style: styles4.form }, /* @__PURE__ */ React5.createElement("div", { style: styles4.inputRow }, /* @__PURE__ */ React5.createElement(
653
+ "input",
654
+ {
655
+ type: "text",
656
+ value: token,
657
+ onChange: (e) => setToken(e.target.value),
658
+ placeholder: "Paste daemon auth token...",
659
+ style: styles4.input,
660
+ autoFocus: true
661
+ }
662
+ ), /* @__PURE__ */ React5.createElement(
663
+ "input",
664
+ {
665
+ type: "number",
666
+ value: port,
667
+ onChange: (e) => setPort(e.target.value),
668
+ placeholder: "Port",
669
+ style: styles4.portInput,
670
+ min: "1",
671
+ max: "65535",
672
+ "aria-label": "Port"
673
+ }
674
+ )), /* @__PURE__ */ React5.createElement(
675
+ "button",
676
+ {
677
+ type: "submit",
678
+ disabled: !token.trim(),
679
+ style: {
680
+ ...styles4.button,
681
+ ...!token.trim() ? styles4.buttonDisabled : {}
682
+ }
683
+ },
684
+ "Connect"
685
+ )));
686
+ }
687
+ var styles4 = {
688
+ container: {
689
+ display: "flex",
690
+ flexDirection: "column",
691
+ alignItems: "center",
692
+ justifyContent: "center",
693
+ padding: "24px",
694
+ height: "100%"
695
+ },
696
+ title: {
697
+ fontSize: "16px",
698
+ fontWeight: 600,
699
+ marginBottom: "8px",
700
+ color: "#333"
701
+ },
702
+ description: {
703
+ fontSize: "13px",
704
+ color: "#666",
705
+ textAlign: "center",
706
+ maxWidth: "320px",
707
+ lineHeight: "1.5",
708
+ marginBottom: "16px"
709
+ },
710
+ code: {
711
+ fontFamily: "monospace",
712
+ backgroundColor: "#f5f5f5",
713
+ padding: "2px 4px",
714
+ borderRadius: "3px",
715
+ fontSize: "12px"
716
+ },
717
+ form: {
718
+ display: "flex",
719
+ gap: "8px",
720
+ width: "100%",
721
+ maxWidth: "400px",
722
+ alignItems: "center"
723
+ },
724
+ inputRow: {
725
+ display: "flex",
726
+ gap: "8px",
727
+ flex: 1
728
+ },
729
+ input: {
730
+ flex: 1,
731
+ padding: "8px 12px",
732
+ border: "1px solid #ddd",
733
+ borderRadius: "8px",
734
+ fontSize: "13px",
735
+ fontFamily: "monospace",
736
+ outline: "none"
737
+ },
738
+ portInput: {
739
+ width: "72px",
740
+ padding: "8px 8px",
741
+ border: "1px solid #ddd",
742
+ borderRadius: "8px",
743
+ fontSize: "13px",
744
+ fontFamily: "monospace",
745
+ outline: "none",
746
+ textAlign: "center"
747
+ },
748
+ button: {
749
+ padding: "8px 16px",
750
+ border: "none",
751
+ borderRadius: "8px",
752
+ backgroundColor: "#1ea7fd",
753
+ color: "#fff",
754
+ fontSize: "13px",
755
+ fontWeight: 600,
756
+ cursor: "pointer"
757
+ },
758
+ buttonDisabled: {
759
+ backgroundColor: "#ccc",
760
+ cursor: "not-allowed"
761
+ }
762
+ };
763
+
764
+ // src/components/GitContextBar.jsx
765
+ import React6 from "react";
766
+ function GitContextBar({ branch, pr, loading }) {
767
+ if (loading || !branch) return null;
768
+ return /* @__PURE__ */ React6.createElement("div", { style: styles5.container }, /* @__PURE__ */ React6.createElement("div", { style: styles5.left }, /* @__PURE__ */ React6.createElement("span", { style: styles5.branchIcon, title: "Git branch" }, "\u2387"), /* @__PURE__ */ React6.createElement("span", { style: styles5.branchName }, branch)), pr && /* @__PURE__ */ React6.createElement("div", { style: styles5.right }, /* @__PURE__ */ React6.createElement(
769
+ "a",
770
+ {
771
+ href: pr.url,
772
+ target: "_blank",
773
+ rel: "noopener noreferrer",
774
+ style: styles5.prLink,
775
+ title: pr.title
776
+ },
777
+ "PR #",
778
+ pr.number
779
+ )));
780
+ }
781
+ var styles5 = {
782
+ container: {
783
+ display: "flex",
784
+ alignItems: "center",
785
+ justifyContent: "space-between",
786
+ padding: "3px 12px",
787
+ borderBottom: "1px solid rgba(0,0,0,0.1)",
788
+ backgroundColor: "#f5f5f5",
789
+ fontSize: "11px",
790
+ fontFamily: "inherit"
791
+ },
792
+ left: {
793
+ display: "flex",
794
+ alignItems: "center",
795
+ gap: "4px"
796
+ },
797
+ branchIcon: {
798
+ fontSize: "12px",
799
+ color: "#888"
800
+ },
801
+ branchName: {
802
+ fontFamily: "monospace",
803
+ fontSize: "11px",
804
+ color: "#555",
805
+ backgroundColor: "#eee",
806
+ padding: "1px 5px",
807
+ borderRadius: "3px"
808
+ },
809
+ right: {
810
+ display: "flex",
811
+ alignItems: "center"
812
+ },
813
+ prLink: {
814
+ color: "#0366d6",
815
+ textDecoration: "none",
816
+ fontFamily: "monospace",
817
+ fontSize: "11px"
818
+ }
819
+ };
820
+
821
+ // src/useClaudeSession.js
822
+ import { useState as useState3, useEffect as useEffect2, useRef as useRef3, useCallback } from "react";
823
+
824
+ // src/WebSocketClient.js
825
+ var WebSocketClient = class {
826
+ constructor(options = {}) {
827
+ this._host = options.host || DEFAULT_HOST;
828
+ this._port = options.port || DEFAULT_PORT;
829
+ this._token = options.token || null;
830
+ this._ws = null;
831
+ this._state = CONNECTION_STATES.DISCONNECTED;
832
+ this._listeners = /* @__PURE__ */ new Map();
833
+ this._reconnectAttempt = 0;
834
+ this._maxReconnectAttempts = options.maxReconnectAttempts || 10;
835
+ this._baseDelay = options.baseDelay || 1e3;
836
+ this._maxDelay = options.maxDelay || 3e4;
837
+ this._reconnectTimer = null;
838
+ this._pingInterval = null;
839
+ this._pingIntervalMs = options.pingIntervalMs || 3e4;
840
+ }
841
+ get state() {
842
+ return this._state;
843
+ }
844
+ get isConnected() {
845
+ return this._state === CONNECTION_STATES.CONNECTED;
846
+ }
847
+ /**
848
+ * Register an event listener.
849
+ * Events: 'state_change', 'session_activated', 'output', 'complete', 'error', 'pong'
850
+ */
851
+ on(event, callback) {
852
+ if (!this._listeners.has(event)) {
853
+ this._listeners.set(event, /* @__PURE__ */ new Set());
854
+ }
855
+ this._listeners.get(event).add(callback);
856
+ return () => {
857
+ var _a;
858
+ return (_a = this._listeners.get(event)) == null ? void 0 : _a.delete(callback);
859
+ };
860
+ }
861
+ /**
862
+ * Connect to the standalone daemon.
863
+ */
864
+ connect() {
865
+ if (this._ws) {
866
+ this._ws.close();
867
+ }
868
+ if (this._state !== CONNECTION_STATES.RECONNECTING) {
869
+ this._setState(CONNECTION_STATES.CONNECTING);
870
+ }
871
+ const params = this._token ? `?token=${encodeURIComponent(this._token)}` : "";
872
+ const url = `ws://${this._host}:${this._port}/${params}`;
873
+ try {
874
+ this._ws = new WebSocket(url);
875
+ } catch (err) {
876
+ this._setState(CONNECTION_STATES.DISCONNECTED);
877
+ this._emit("error", { code: "CONNECT_FAILED", message: err.message });
878
+ return;
879
+ }
880
+ this._ws.onopen = () => {
881
+ this._setState(CONNECTION_STATES.CONNECTED);
882
+ this._reconnectAttempt = 0;
883
+ this._startPing();
884
+ };
885
+ this._ws.onmessage = (event) => {
886
+ this._handleMessage(event.data);
887
+ };
888
+ this._ws.onclose = (event) => {
889
+ this._stopPing();
890
+ const wasConnected = this._state === CONNECTION_STATES.CONNECTED;
891
+ this._ws = null;
892
+ if (event.code === 4001) {
893
+ this._setState(CONNECTION_STATES.DISCONNECTED);
894
+ this._emit("error", { code: "AUTH_FAILED", message: "Authentication failed" });
895
+ return;
896
+ }
897
+ if (wasConnected || this._state === CONNECTION_STATES.RECONNECTING) {
898
+ this._scheduleReconnect();
899
+ } else {
900
+ this._setState(CONNECTION_STATES.DISCONNECTED);
901
+ }
902
+ };
903
+ this._ws.onerror = () => {
904
+ };
905
+ }
906
+ /**
907
+ * Disconnect and stop reconnection attempts.
908
+ */
909
+ disconnect() {
910
+ this._clearReconnect();
911
+ this._stopPing();
912
+ if (this._ws) {
913
+ this._ws.close(1e3, "Client disconnecting");
914
+ this._ws = null;
915
+ }
916
+ this._setState(CONNECTION_STATES.DISCONNECTED);
917
+ }
918
+ /**
919
+ * Update the auth token (e.g., when user pastes it in UI).
920
+ */
921
+ setToken(token) {
922
+ this._token = token;
923
+ }
924
+ /**
925
+ * Activate a session. Must be called after connecting.
926
+ */
927
+ activate(sessionId, workingDir) {
928
+ this._send({
929
+ type: "activate",
930
+ sessionId,
931
+ workingDir
932
+ });
933
+ }
934
+ /**
935
+ * Send a command (user message) to Claude.
936
+ */
937
+ sendCommand(sessionId, text, commandId) {
938
+ this._send({
939
+ type: "command",
940
+ sessionId,
941
+ commandId: commandId || crypto.randomUUID(),
942
+ text
943
+ });
944
+ }
945
+ /**
946
+ * Clear (tear down) a session on the daemon.
947
+ */
948
+ clearSession(sessionId) {
949
+ this._send({
950
+ type: "clear_session",
951
+ sessionId
952
+ });
953
+ }
954
+ /**
955
+ * Send a query to the daemon.
956
+ */
957
+ sendQuery(queryType) {
958
+ this._send({
959
+ type: "query",
960
+ queryType
961
+ });
962
+ }
963
+ // ---------------------------------------------------------------------------
964
+ // Internal
965
+ // ---------------------------------------------------------------------------
966
+ _send(msg) {
967
+ if (this._ws && this._ws.readyState === WebSocket.OPEN) {
968
+ this._ws.send(JSON.stringify(msg));
969
+ }
970
+ }
971
+ _handleMessage(raw) {
972
+ let msg;
973
+ try {
974
+ msg = JSON.parse(raw);
975
+ } catch {
976
+ return;
977
+ }
978
+ switch (msg.type) {
979
+ case "session_activated":
980
+ case "session_cleared":
981
+ case "output":
982
+ case "complete":
983
+ case "error":
984
+ case "user_input":
985
+ case "pong":
986
+ case "query_result":
987
+ this._emit(msg.type, msg);
988
+ break;
989
+ default:
990
+ this._emit("message", msg);
991
+ }
992
+ }
993
+ _setState(newState) {
994
+ if (this._state !== newState) {
995
+ const oldState = this._state;
996
+ this._state = newState;
997
+ this._emit("state_change", { oldState, newState });
998
+ }
999
+ }
1000
+ _emit(event, data) {
1001
+ const listeners = this._listeners.get(event);
1002
+ if (listeners) {
1003
+ for (const cb of listeners) {
1004
+ try {
1005
+ cb(data);
1006
+ } catch {
1007
+ }
1008
+ }
1009
+ }
1010
+ }
1011
+ _scheduleReconnect() {
1012
+ if (this._reconnectAttempt >= this._maxReconnectAttempts) {
1013
+ this._setState(CONNECTION_STATES.DISCONNECTED);
1014
+ this._emit("error", { code: "MAX_RECONNECT", message: "Max reconnection attempts reached" });
1015
+ return;
1016
+ }
1017
+ this._setState(CONNECTION_STATES.RECONNECTING);
1018
+ const delay = Math.min(
1019
+ this._baseDelay * Math.pow(2, this._reconnectAttempt),
1020
+ this._maxDelay
1021
+ );
1022
+ this._reconnectAttempt++;
1023
+ this._reconnectTimer = setTimeout(() => {
1024
+ this.connect();
1025
+ }, delay);
1026
+ }
1027
+ _clearReconnect() {
1028
+ if (this._reconnectTimer) {
1029
+ clearTimeout(this._reconnectTimer);
1030
+ this._reconnectTimer = null;
1031
+ }
1032
+ this._reconnectAttempt = 0;
1033
+ }
1034
+ _startPing() {
1035
+ this._stopPing();
1036
+ this._pingInterval = setInterval(() => {
1037
+ this._send({ type: "ping" });
1038
+ }, this._pingIntervalMs);
1039
+ }
1040
+ _stopPing() {
1041
+ if (this._pingInterval) {
1042
+ clearInterval(this._pingInterval);
1043
+ this._pingInterval = null;
1044
+ }
1045
+ }
1046
+ };
1047
+
1048
+ // src/useClaudeSession.js
1049
+ var DEFAULT_STORAGE_KEY = `${ADDON_ID}:sessionId`;
1050
+ function loadPersistedSessionId(storageKey) {
1051
+ try {
1052
+ return localStorage.getItem(storageKey) || null;
1053
+ } catch {
1054
+ return null;
1055
+ }
1056
+ }
1057
+ function persistSessionId(storageKey, id) {
1058
+ try {
1059
+ if (id) {
1060
+ localStorage.setItem(storageKey, id);
1061
+ } else {
1062
+ localStorage.removeItem(storageKey);
1063
+ }
1064
+ } catch {
1065
+ }
1066
+ }
1067
+ function useClaudeSession(options = {}) {
1068
+ const { host, port, token, sessionId: initialSessionId, storageKey } = options;
1069
+ const _storageKey = storageKey || DEFAULT_STORAGE_KEY;
1070
+ const [connectionState, setConnectionState] = useState3(CONNECTION_STATES.DISCONNECTED);
1071
+ const [messages, setMessages] = useState3([]);
1072
+ const [sessionId, setSessionId] = useState3(initialSessionId || loadPersistedSessionId(_storageKey));
1073
+ const [isProcessing, setIsProcessing] = useState3(false);
1074
+ const [authFailed, setAuthFailed] = useState3(false);
1075
+ const clientRef = useRef3(null);
1076
+ const sessionIdRef = useRef3(sessionId);
1077
+ sessionIdRef.current = sessionId;
1078
+ useEffect2(() => {
1079
+ const client = new WebSocketClient({ host, port, token });
1080
+ clientRef.current = client;
1081
+ const unsubs = [
1082
+ client.on("state_change", ({ newState }) => {
1083
+ setConnectionState(newState);
1084
+ }),
1085
+ client.on("session_activated", (msg) => {
1086
+ setSessionId(msg.sessionId);
1087
+ persistSessionId(_storageKey, msg.sessionId);
1088
+ }),
1089
+ client.on("session_cleared", () => {
1090
+ const newSid = crypto.randomUUID();
1091
+ sessionIdRef.current = newSid;
1092
+ setSessionId(newSid);
1093
+ persistSessionId(_storageKey, newSid);
1094
+ setMessages([]);
1095
+ setIsProcessing(false);
1096
+ client.activate(newSid);
1097
+ }),
1098
+ client.on("output", (msg) => {
1099
+ if (msg.sessionId !== sessionIdRef.current) return;
1100
+ setMessages((prev) => [...prev, { ...msg, id: crypto.randomUUID() }]);
1101
+ if (msg.data && msg.data.type === "result") {
1102
+ setIsProcessing(false);
1103
+ }
1104
+ }),
1105
+ client.on("user_input", (msg) => {
1106
+ if (msg.sessionId !== sessionIdRef.current) return;
1107
+ let text = msg.text || "";
1108
+ const ctxMarker = "[Context from Storybook]";
1109
+ if (text.startsWith(ctxMarker)) {
1110
+ const idx = text.indexOf("\n\n");
1111
+ text = idx >= 0 ? text.slice(idx + 2) : text;
1112
+ }
1113
+ setMessages((prev) => [...prev, {
1114
+ type: "user_input",
1115
+ text,
1116
+ id: crypto.randomUUID(),
1117
+ timestamp: msg.timestamp,
1118
+ replay: true
1119
+ }]);
1120
+ }),
1121
+ client.on("complete", (msg) => {
1122
+ if (msg.sessionId !== sessionIdRef.current) return;
1123
+ setMessages((prev) => [...prev, { ...msg, id: crypto.randomUUID() }]);
1124
+ setIsProcessing(false);
1125
+ }),
1126
+ client.on("error", (msg) => {
1127
+ if (msg.code === "AUTH_FAILED") {
1128
+ setAuthFailed(true);
1129
+ return;
1130
+ }
1131
+ setMessages((prev) => [
1132
+ ...prev,
1133
+ { type: "error", ...msg, id: crypto.randomUUID(), timestamp: Date.now() }
1134
+ ]);
1135
+ setIsProcessing(false);
1136
+ })
1137
+ ];
1138
+ return () => {
1139
+ unsubs.forEach((unsub) => unsub());
1140
+ client.disconnect();
1141
+ };
1142
+ }, [host, port, token]);
1143
+ useEffect2(() => {
1144
+ const client = clientRef.current;
1145
+ if (!client) return;
1146
+ if (token) {
1147
+ client.setToken(token);
1148
+ }
1149
+ setAuthFailed(false);
1150
+ client.connect();
1151
+ return () => client.disconnect();
1152
+ }, [token]);
1153
+ useEffect2(() => {
1154
+ const client = clientRef.current;
1155
+ if (!client || connectionState !== CONNECTION_STATES.CONNECTED || !sessionId) return;
1156
+ client.activate(sessionId);
1157
+ }, [connectionState, sessionId]);
1158
+ const sendMessage = useCallback(
1159
+ (text, contextPrefix) => {
1160
+ const client = clientRef.current;
1161
+ if (!client || !client.isConnected || !sessionIdRef.current) return;
1162
+ const fullText = contextPrefix ? `${contextPrefix}
1163
+
1164
+ ${text}` : text;
1165
+ setMessages((prev) => [
1166
+ ...prev,
1167
+ {
1168
+ type: "user_input",
1169
+ text,
1170
+ id: crypto.randomUUID(),
1171
+ timestamp: Date.now()
1172
+ }
1173
+ ]);
1174
+ setIsProcessing(true);
1175
+ client.sendCommand(sessionIdRef.current, fullText);
1176
+ },
1177
+ []
1178
+ );
1179
+ const startSession = useCallback(
1180
+ (newSessionId, workingDir) => {
1181
+ const client = clientRef.current;
1182
+ const sid = newSessionId || crypto.randomUUID();
1183
+ sessionIdRef.current = sid;
1184
+ setSessionId(sid);
1185
+ persistSessionId(_storageKey, sid);
1186
+ setMessages([]);
1187
+ if (client && client.isConnected) {
1188
+ client.activate(sid, workingDir);
1189
+ }
1190
+ return sid;
1191
+ },
1192
+ []
1193
+ );
1194
+ const clearMessages = useCallback(() => {
1195
+ setMessages([]);
1196
+ }, []);
1197
+ const disconnect = useCallback(() => {
1198
+ const client = clientRef.current;
1199
+ if (client) {
1200
+ client.disconnect();
1201
+ }
1202
+ sessionIdRef.current = null;
1203
+ setSessionId(null);
1204
+ persistSessionId(_storageKey, null);
1205
+ setMessages([]);
1206
+ setIsProcessing(false);
1207
+ }, []);
1208
+ const newChat = useCallback(() => {
1209
+ const client = clientRef.current;
1210
+ const currentSid = sessionIdRef.current;
1211
+ if (client && client.isConnected && currentSid) {
1212
+ client.clearSession(currentSid);
1213
+ } else {
1214
+ startSession();
1215
+ }
1216
+ }, [startSession]);
1217
+ return {
1218
+ connectionState,
1219
+ messages,
1220
+ sessionId,
1221
+ isProcessing,
1222
+ authFailed,
1223
+ sendMessage,
1224
+ startSession,
1225
+ clearMessages,
1226
+ setMessages,
1227
+ disconnect,
1228
+ newChat,
1229
+ isConnected: connectionState === CONNECTION_STATES.CONNECTED,
1230
+ client: clientRef.current
1231
+ };
1232
+ }
1233
+
1234
+ // src/useGitContext.js
1235
+ import { useState as useState4, useEffect as useEffect3, useRef as useRef4, useCallback as useCallback2 } from "react";
1236
+ function useGitContext(client, connectionState) {
1237
+ const [gitData, setGitData] = useState4(null);
1238
+ const [loading, setLoading] = useState4(false);
1239
+ const intervalRef = useRef4(null);
1240
+ const unsubRef = useRef4(null);
1241
+ const fetchContext = useCallback2(() => {
1242
+ if (!client || !client.isConnected) return;
1243
+ setLoading(true);
1244
+ client.sendQuery("git_context");
1245
+ }, [client]);
1246
+ useEffect3(() => {
1247
+ if (!client) return;
1248
+ const unsub = client.on("query_result", (msg) => {
1249
+ if (msg.queryType === "git_context") {
1250
+ setGitData(msg.data || null);
1251
+ setLoading(false);
1252
+ }
1253
+ });
1254
+ const unsubErr = client.on("error", (msg) => {
1255
+ if (msg.code === "UNKNOWN_TYPE" || msg.code === "UNKNOWN_QUERY") {
1256
+ setLoading(false);
1257
+ }
1258
+ });
1259
+ unsubRef.current = () => {
1260
+ unsub();
1261
+ unsubErr();
1262
+ };
1263
+ return () => {
1264
+ if (unsubRef.current) unsubRef.current();
1265
+ };
1266
+ }, [client]);
1267
+ useEffect3(() => {
1268
+ if (connectionState !== CONNECTION_STATES.CONNECTED) {
1269
+ if (intervalRef.current) {
1270
+ clearInterval(intervalRef.current);
1271
+ intervalRef.current = null;
1272
+ }
1273
+ return;
1274
+ }
1275
+ fetchContext();
1276
+ intervalRef.current = setInterval(fetchContext, 6e4);
1277
+ return () => {
1278
+ if (intervalRef.current) {
1279
+ clearInterval(intervalRef.current);
1280
+ intervalRef.current = null;
1281
+ }
1282
+ };
1283
+ }, [connectionState, fetchContext]);
1284
+ return {
1285
+ branch: (gitData == null ? void 0 : gitData.branch) || null,
1286
+ pr: (gitData == null ? void 0 : gitData.pr) || null,
1287
+ loading
1288
+ };
1289
+ }
1290
+
1291
+ // src/useStoryContext.js
1292
+ import { useStorybookState } from "storybook/manager-api";
1293
+ function useStoryContext() {
1294
+ var _a;
1295
+ const state = useStorybookState();
1296
+ if (!state || !state.storyId) {
1297
+ return { storyId: null, componentPath: null, storyTitle: null };
1298
+ }
1299
+ const story = (_a = state.index) == null ? void 0 : _a[state.storyId];
1300
+ if (!story) {
1301
+ return { storyId: state.storyId, componentPath: null, storyTitle: null };
1302
+ }
1303
+ const componentPath = extractComponentPath(story);
1304
+ const storyTitle = story.title || null;
1305
+ return {
1306
+ storyId: state.storyId,
1307
+ componentPath,
1308
+ storyTitle
1309
+ };
1310
+ }
1311
+ function buildContextPrefix(storyContext) {
1312
+ if (!storyContext.componentPath && !storyContext.storyTitle) {
1313
+ return null;
1314
+ }
1315
+ const parts = ["[Context from Storybook]"];
1316
+ if (storyContext.componentPath) {
1317
+ parts.push(`The user is viewing the component at: ${storyContext.componentPath}`);
1318
+ }
1319
+ if (storyContext.storyTitle) {
1320
+ parts.push(`Story: ${storyContext.storyTitle}`);
1321
+ }
1322
+ parts.push("Please focus your edits on this component file.");
1323
+ return parts.join("\n");
1324
+ }
1325
+ function extractComponentPath(story) {
1326
+ if (story.importPath) {
1327
+ return storyFileToComponentFile(story.importPath);
1328
+ }
1329
+ const params = story.parameters;
1330
+ if (params == null ? void 0 : params.fileName) {
1331
+ return storyFileToComponentFile(params.fileName);
1332
+ }
1333
+ return null;
1334
+ }
1335
+ function storyFileToComponentFile(storyPath) {
1336
+ const converted = storyPath.replace(/\.stories\.([jt]sx?)$/, ".$1");
1337
+ return converted !== storyPath ? converted : storyPath;
1338
+ }
1339
+
1340
+ // src/Panel.jsx
1341
+ function ClaudePanel({ active }) {
1342
+ const [addonState, setAddonState] = useAddonState(ADDON_ID, {
1343
+ token: getInitialToken(),
1344
+ port: getInitialPort(),
1345
+ contextEnabled: true
1346
+ });
1347
+ const token = (addonState == null ? void 0 : addonState.token) || null;
1348
+ const port = (addonState == null ? void 0 : addonState.port) || DEFAULT_PORT;
1349
+ const contextEnabled = (addonState == null ? void 0 : addonState.contextEnabled) ?? true;
1350
+ const storyContext = useStoryContext();
1351
+ const {
1352
+ connectionState,
1353
+ messages,
1354
+ sessionId,
1355
+ isProcessing,
1356
+ authFailed,
1357
+ sendMessage,
1358
+ startSession,
1359
+ disconnect,
1360
+ newChat,
1361
+ isConnected,
1362
+ client
1363
+ } = useClaudeSession({
1364
+ host: DEFAULT_HOST,
1365
+ port,
1366
+ token
17
1367
  });
1368
+ const gitContext = useGitContext(client, connectionState);
1369
+ const handleTokenSubmit = useCallback3(
1370
+ (newToken, newPort) => {
1371
+ setAddonState({ ...addonState, token: newToken, port: newPort || DEFAULT_PORT });
1372
+ },
1373
+ [addonState, setAddonState]
1374
+ );
1375
+ const handleDisconnect = useCallback3(() => {
1376
+ disconnect();
1377
+ setAddonState({ ...addonState, token: null });
1378
+ }, [addonState, setAddonState, disconnect]);
1379
+ const handleSend = useCallback3(
1380
+ (text) => {
1381
+ if (!sessionId) {
1382
+ startSession();
1383
+ }
1384
+ const prefix = contextEnabled ? buildContextPrefix(storyContext) : null;
1385
+ sendMessage(text, prefix);
1386
+ },
1387
+ [sessionId, startSession, sendMessage, contextEnabled, storyContext]
1388
+ );
1389
+ const toggleContext = useCallback3(() => {
1390
+ setAddonState({ ...addonState, contextEnabled: !contextEnabled });
1391
+ }, [addonState, contextEnabled, setAddonState]);
1392
+ if (!active) return null;
1393
+ if (authFailed && !token) {
1394
+ return /* @__PURE__ */ React7.createElement("div", { style: styles6.panel }, /* @__PURE__ */ React7.createElement(TokenInput, { onSubmit: handleTokenSubmit }));
1395
+ }
1396
+ return /* @__PURE__ */ React7.createElement("div", { style: styles6.panel }, /* @__PURE__ */ React7.createElement(ConnectionStatus, { state: connectionState, sessionId, onDisconnect: handleDisconnect, onNewChat: newChat, isProcessing }), /* @__PURE__ */ React7.createElement(GitContextBar, { branch: gitContext.branch, pr: gitContext.pr, loading: gitContext.loading }), storyContext.componentPath && /* @__PURE__ */ React7.createElement("div", { style: styles6.contextBar }, /* @__PURE__ */ React7.createElement("label", { style: styles6.contextLabel }, /* @__PURE__ */ React7.createElement(
1397
+ "input",
1398
+ {
1399
+ type: "checkbox",
1400
+ checked: contextEnabled,
1401
+ onChange: toggleContext,
1402
+ style: styles6.contextCheckbox
1403
+ }
1404
+ ), "Context: ", /* @__PURE__ */ React7.createElement("code", { style: styles6.contextPath }, storyContext.componentPath))), /* @__PURE__ */ React7.createElement(MessageList, { messages }), /* @__PURE__ */ React7.createElement(
1405
+ MessageInput,
1406
+ {
1407
+ onSend: handleSend,
1408
+ disabled: !isConnected,
1409
+ isProcessing
1410
+ }
1411
+ ));
1412
+ }
1413
+ function getInitialPort() {
1414
+ var _a;
1415
+ try {
1416
+ if (typeof window !== "undefined" && window.__CLAUDE_DAEMON_PORT__) {
1417
+ return parseInt(window.__CLAUDE_DAEMON_PORT__, 10) || DEFAULT_PORT;
1418
+ }
1419
+ const envPort = typeof process !== "undefined" && ((_a = process.env) == null ? void 0 : _a.STORYBOOK_CLAUDE_DAEMON_PORT) || null;
1420
+ return envPort ? parseInt(envPort, 10) : DEFAULT_PORT;
1421
+ } catch {
1422
+ return DEFAULT_PORT;
1423
+ }
1424
+ }
1425
+ function getInitialToken() {
1426
+ var _a, _b;
1427
+ try {
1428
+ return typeof process !== "undefined" && ((_a = process.env) == null ? void 0 : _a.STORYBOOK_CLAUDE_DAEMON_TOKEN) || typeof process !== "undefined" && ((_b = process.env) == null ? void 0 : _b.CLAUDE_DAEMON_TOKEN) || null;
1429
+ } catch {
1430
+ return null;
1431
+ }
1432
+ }
1433
+ var styles6 = {
1434
+ panel: {
1435
+ display: "flex",
1436
+ flexDirection: "column",
1437
+ height: "100%",
1438
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'
1439
+ },
1440
+ contextBar: {
1441
+ padding: "4px 12px",
1442
+ borderBottom: "1px solid rgba(0,0,0,0.1)",
1443
+ backgroundColor: "#fafafa",
1444
+ fontSize: "12px"
1445
+ },
1446
+ contextLabel: {
1447
+ display: "flex",
1448
+ alignItems: "center",
1449
+ gap: "4px",
1450
+ color: "#666",
1451
+ cursor: "pointer"
1452
+ },
1453
+ contextCheckbox: {
1454
+ margin: 0
1455
+ },
1456
+ contextPath: {
1457
+ fontFamily: "monospace",
1458
+ fontSize: "11px",
1459
+ backgroundColor: "#eee",
1460
+ padding: "1px 4px",
1461
+ borderRadius: "3px"
1462
+ }
1463
+ };
1464
+
1465
+ // src/GlobalPanel.jsx
1466
+ import React9, { useCallback as useCallback4, useMemo as useMemo2 } from "react";
1467
+ import { useAddonState as useAddonState2 } from "storybook/manager-api";
18
1468
 
19
- // Global chat page (full-page route, not scoped to any component)
20
- _managerApi.addons.add(_constants.PAGE_ID, {
21
- type: _managerApi.types.experimental_PAGE,
22
- title: 'Claude Global',
23
- url: '/claude',
24
- render: _GlobalPanel.GlobalPanel
1469
+ // src/components/SkillsBar.jsx
1470
+ import React8 from "react";
1471
+ function SkillsBar({ skills, onTrigger, disabled }) {
1472
+ if (!skills || skills.length === 0) return null;
1473
+ return /* @__PURE__ */ React8.createElement("div", { style: styles7.bar }, skills.map((skill) => /* @__PURE__ */ React8.createElement(
1474
+ "button",
1475
+ {
1476
+ key: skill.id,
1477
+ style: {
1478
+ ...styles7.button,
1479
+ ...disabled ? styles7.buttonDisabled : {}
1480
+ },
1481
+ onClick: () => onTrigger(skill),
1482
+ disabled,
1483
+ title: skill.prompt
1484
+ },
1485
+ /* @__PURE__ */ React8.createElement("span", { style: styles7.icon }, skill.icon),
1486
+ /* @__PURE__ */ React8.createElement("span", null, skill.label)
1487
+ )));
1488
+ }
1489
+ var styles7 = {
1490
+ bar: {
1491
+ display: "flex",
1492
+ gap: "6px",
1493
+ padding: "8px 12px",
1494
+ overflowX: "auto",
1495
+ flexShrink: 0
1496
+ },
1497
+ button: {
1498
+ display: "inline-flex",
1499
+ alignItems: "center",
1500
+ gap: "4px",
1501
+ padding: "5px 10px",
1502
+ border: "1px solid rgba(0,0,0,0.12)",
1503
+ borderRadius: "16px",
1504
+ backgroundColor: "#fff",
1505
+ color: "#444",
1506
+ fontSize: "12px",
1507
+ cursor: "pointer",
1508
+ whiteSpace: "nowrap",
1509
+ transition: "background-color 0.15s"
1510
+ },
1511
+ buttonDisabled: {
1512
+ opacity: 0.45,
1513
+ cursor: "default"
1514
+ },
1515
+ icon: {
1516
+ fontSize: "13px"
1517
+ }
1518
+ };
1519
+
1520
+ // src/skills.js
1521
+ var STATIC_SKILLS = [
1522
+ {
1523
+ id: "sync-components",
1524
+ label: "Sync Components",
1525
+ icon: "\u21BB",
1526
+ prompt: "Synchronize all Storybook components with their Figma designs. Report what changed."
1527
+ },
1528
+ {
1529
+ id: "run-tests",
1530
+ label: "Run Tests",
1531
+ icon: "\u25B6",
1532
+ prompt: "Run the full test suite and summarize the results."
1533
+ },
1534
+ {
1535
+ id: "audit-stories",
1536
+ label: "Audit Stories",
1537
+ icon: "\u{1F50D}",
1538
+ prompt: "Audit all components and list any that are missing Storybook stories or have outdated ones."
1539
+ }
1540
+ ];
1541
+ function getPrSkill(pr) {
1542
+ if (pr && pr.number) {
1543
+ return {
1544
+ id: "finalize-pr",
1545
+ label: "Finalize PR",
1546
+ icon: "\u2714",
1547
+ prompt: `Check the status of PR #${pr.number} (${pr.url}). Review CI checks, review status, and merge conflicts. If everything is green and approved, offer to merge it. If there are issues, summarize what needs attention to make it ready for merge.`
1548
+ };
1549
+ }
1550
+ return {
1551
+ id: "new-pr",
1552
+ label: "New PR",
1553
+ icon: "\u2795",
1554
+ prompt: "Create a new pull request for the current branch with a summary of all changes."
1555
+ };
1556
+ }
1557
+ function getGlobalSkills(gitContext) {
1558
+ const pr = (gitContext == null ? void 0 : gitContext.pr) || null;
1559
+ return [
1560
+ STATIC_SKILLS[0],
1561
+ // Sync Components
1562
+ getPrSkill(pr),
1563
+ // New PR / Finalize PR
1564
+ STATIC_SKILLS[1],
1565
+ // Run Tests
1566
+ STATIC_SKILLS[2]
1567
+ // Audit Stories
1568
+ ];
1569
+ }
1570
+
1571
+ // src/GlobalPanel.jsx
1572
+ function GlobalPanel() {
1573
+ const handleBack = useCallback4(() => {
1574
+ window.history.back();
1575
+ }, []);
1576
+ const [addonState, setAddonState] = useAddonState2(ADDON_ID, {
1577
+ token: getInitialToken2(),
1578
+ port: getInitialPort2()
1579
+ });
1580
+ const token = (addonState == null ? void 0 : addonState.token) || null;
1581
+ const port = (addonState == null ? void 0 : addonState.port) || DEFAULT_PORT;
1582
+ const {
1583
+ connectionState,
1584
+ messages,
1585
+ sessionId,
1586
+ isProcessing,
1587
+ authFailed,
1588
+ sendMessage,
1589
+ startSession,
1590
+ setMessages,
1591
+ isConnected,
1592
+ client
1593
+ } = useClaudeSession({
1594
+ host: DEFAULT_HOST,
1595
+ port,
1596
+ token,
1597
+ sessionId: GLOBAL_SESSION_KEY,
1598
+ storageKey: `${ADDON_ID}:globalSessionId`
25
1599
  });
1600
+ const gitContext = useGitContext(client, connectionState);
1601
+ const skills = useMemo2(() => getGlobalSkills(gitContext), [gitContext]);
1602
+ const handleTokenSubmit = useCallback4(
1603
+ (newToken, newPort) => {
1604
+ setAddonState({ ...addonState, token: newToken, port: newPort || DEFAULT_PORT });
1605
+ },
1606
+ [addonState, setAddonState]
1607
+ );
1608
+ const handleSend = useCallback4(
1609
+ (text) => {
1610
+ if (!sessionId) {
1611
+ startSession(GLOBAL_SESSION_KEY);
1612
+ }
1613
+ sendMessage(text);
1614
+ },
1615
+ [sessionId, startSession, sendMessage]
1616
+ );
1617
+ const handleSkillTrigger = useCallback4(
1618
+ (skill) => {
1619
+ setMessages((prev) => [
1620
+ ...prev,
1621
+ {
1622
+ type: "skill_invocation",
1623
+ skill,
1624
+ id: crypto.randomUUID(),
1625
+ timestamp: Date.now()
1626
+ }
1627
+ ]);
1628
+ handleSend(skill.prompt);
1629
+ },
1630
+ [handleSend, setMessages]
1631
+ );
1632
+ if (authFailed && !token) {
1633
+ return /* @__PURE__ */ React9.createElement("div", { style: styles8.page }, /* @__PURE__ */ React9.createElement(TokenInput, { onSubmit: handleTokenSubmit }));
1634
+ }
1635
+ const statusColor = connectionState === CONNECTION_STATES.CONNECTED ? "#4caf50" : connectionState === CONNECTION_STATES.DISCONNECTED ? "#f44336" : "#ff9800";
1636
+ const statusLabel = connectionState === CONNECTION_STATES.CONNECTED ? "Connected" : connectionState === CONNECTION_STATES.CONNECTING ? "Connecting..." : connectionState === CONNECTION_STATES.RECONNECTING ? "Reconnecting..." : "Disconnected";
1637
+ return /* @__PURE__ */ React9.createElement("div", { style: styles8.page }, /* @__PURE__ */ React9.createElement("div", { style: styles8.titleBar }, /* @__PURE__ */ React9.createElement("div", { style: styles8.titleLeft }, /* @__PURE__ */ React9.createElement("div", { style: styles8.title }, "Claude \u2014 Global Chat"), /* @__PURE__ */ React9.createElement("span", { style: { ...styles8.statusDot, backgroundColor: statusColor }, title: statusLabel }), /* @__PURE__ */ React9.createElement("span", { style: styles8.statusLabel }, statusLabel), /* @__PURE__ */ React9.createElement(
1638
+ "span",
1639
+ {
1640
+ style: styles8.infoIcon,
1641
+ title: "Chat with Claude about your entire project. Not scoped to any specific component."
1642
+ },
1643
+ /* @__PURE__ */ React9.createElement("svg", { viewBox: "0 0 16 16", width: "14", height: "14", fill: "currentColor" }, /* @__PURE__ */ React9.createElement("path", { d: "M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zm.93 12.588-.003.009H7.074l-.003-.01c-.027-.18-.04-.362-.04-.545 0-1.02.356-1.852 1.065-2.497.295-.268.59-.502.793-.737.2-.232.303-.493.303-.784 0-.318-.112-.564-.337-.738-.228-.177-.56-.266-.994-.266-.39 0-.738.074-1.046.222-.307.148-.57.35-.787.607l-.12.152-.94-.762.12-.152c.32-.395.727-.71 1.222-.943A3.77 3.77 0 0 1 7.92 5.4c.744 0 1.35.19 1.818.57.47.382.706.886.706 1.514 0 .474-.14.892-.42 1.254-.277.358-.656.68-1.137.966-.38.226-.636.463-.765.71-.13.25-.196.564-.196.943 0 .082.003.163.01.244l-.006-.013zM8 14.074a.87.87 0 0 1-.638-.262.87.87 0 0 1-.262-.638c0-.25.087-.463.262-.638A.87.87 0 0 1 8 12.274c.25 0 .463.087.638.262a.87.87 0 0 1 .262.638.87.87 0 0 1-.262.638A.87.87 0 0 1 8 14.074z" }))
1644
+ )), /* @__PURE__ */ React9.createElement("button", { style: styles8.backButton, onClick: handleBack, title: "Back to components" }, /* @__PURE__ */ React9.createElement("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React9.createElement("path", { d: "M19 12H5" }), /* @__PURE__ */ React9.createElement("path", { d: "M12 19l-7-7 7-7" })), /* @__PURE__ */ React9.createElement("span", { style: { marginLeft: "4px" } }, "Back"))), /* @__PURE__ */ React9.createElement("div", { style: styles8.toolsBar }, /* @__PURE__ */ React9.createElement(SkillsBar, { skills, onTrigger: handleSkillTrigger, disabled: !isConnected }), gitContext.branch && /* @__PURE__ */ React9.createElement("div", { style: styles8.gitContext }, /* @__PURE__ */ React9.createElement("div", { style: styles8.branchName }, /* @__PURE__ */ React9.createElement("span", { style: styles8.branchIcon, title: "Git branch" }, "\u2387"), gitContext.branch), gitContext.pr && /* @__PURE__ */ React9.createElement(
1645
+ "a",
1646
+ {
1647
+ href: gitContext.pr.url,
1648
+ target: "_blank",
1649
+ rel: "noopener noreferrer",
1650
+ style: styles8.prLink,
1651
+ title: gitContext.pr.title
1652
+ },
1653
+ "PR #",
1654
+ gitContext.pr.number
1655
+ ))), /* @__PURE__ */ React9.createElement("div", { style: styles8.chatArea }, /* @__PURE__ */ React9.createElement(MessageList, { messages }), /* @__PURE__ */ React9.createElement(
1656
+ MessageInput,
1657
+ {
1658
+ onSend: handleSend,
1659
+ disabled: !isConnected,
1660
+ isProcessing,
1661
+ placeholder: "Ask Claude about your project..."
1662
+ }
1663
+ )));
1664
+ }
1665
+ function getInitialPort2() {
1666
+ var _a;
1667
+ try {
1668
+ if (typeof window !== "undefined" && window.__CLAUDE_DAEMON_PORT__) {
1669
+ return parseInt(window.__CLAUDE_DAEMON_PORT__, 10) || DEFAULT_PORT;
1670
+ }
1671
+ const envPort = typeof process !== "undefined" && ((_a = process.env) == null ? void 0 : _a.STORYBOOK_CLAUDE_DAEMON_PORT) || null;
1672
+ return envPort ? parseInt(envPort, 10) : DEFAULT_PORT;
1673
+ } catch {
1674
+ return DEFAULT_PORT;
1675
+ }
1676
+ }
1677
+ function getInitialToken2() {
1678
+ var _a, _b;
1679
+ try {
1680
+ return typeof process !== "undefined" && ((_a = process.env) == null ? void 0 : _a.STORYBOOK_CLAUDE_DAEMON_TOKEN) || typeof process !== "undefined" && ((_b = process.env) == null ? void 0 : _b.CLAUDE_DAEMON_TOKEN) || null;
1681
+ } catch {
1682
+ return null;
1683
+ }
1684
+ }
1685
+ var styles8 = {
1686
+ page: {
1687
+ display: "flex",
1688
+ flexDirection: "column",
1689
+ height: "100vh",
1690
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'
1691
+ },
1692
+ // Bar 1
1693
+ titleBar: {
1694
+ display: "flex",
1695
+ justifyContent: "space-between",
1696
+ alignItems: "center",
1697
+ padding: "10px 16px",
1698
+ borderBottom: "1px solid rgba(0,0,0,0.1)",
1699
+ backgroundColor: "#fafafa"
1700
+ },
1701
+ titleLeft: {
1702
+ display: "flex",
1703
+ alignItems: "center",
1704
+ gap: "8px"
1705
+ },
1706
+ title: {
1707
+ fontSize: "16px",
1708
+ fontWeight: 600,
1709
+ color: "#333"
1710
+ },
1711
+ statusDot: {
1712
+ width: "8px",
1713
+ height: "8px",
1714
+ borderRadius: "50%",
1715
+ display: "inline-block",
1716
+ flexShrink: 0
1717
+ },
1718
+ statusLabel: {
1719
+ fontSize: "12px",
1720
+ color: "#888"
1721
+ },
1722
+ infoIcon: {
1723
+ color: "#bbb",
1724
+ cursor: "help",
1725
+ display: "inline-flex",
1726
+ alignItems: "center"
1727
+ },
1728
+ backButton: {
1729
+ display: "inline-flex",
1730
+ alignItems: "center",
1731
+ padding: "6px 12px",
1732
+ border: "1px solid rgba(0,0,0,0.15)",
1733
+ borderRadius: "4px",
1734
+ backgroundColor: "transparent",
1735
+ color: "#555",
1736
+ fontSize: "13px",
1737
+ cursor: "pointer",
1738
+ whiteSpace: "nowrap"
1739
+ },
1740
+ // Bar 2
1741
+ toolsBar: {
1742
+ display: "flex",
1743
+ alignItems: "center",
1744
+ justifyContent: "space-between",
1745
+ borderBottom: "1px solid rgba(0,0,0,0.06)",
1746
+ backgroundColor: "#fff"
1747
+ },
1748
+ gitContext: {
1749
+ display: "flex",
1750
+ flexDirection: "column",
1751
+ alignItems: "flex-end",
1752
+ gap: "1px",
1753
+ padding: "6px 12px",
1754
+ flexShrink: 0
1755
+ },
1756
+ branchName: {
1757
+ fontFamily: "monospace",
1758
+ fontSize: "11px",
1759
+ color: "#555",
1760
+ display: "flex",
1761
+ alignItems: "center",
1762
+ gap: "3px"
1763
+ },
1764
+ branchIcon: {
1765
+ fontSize: "12px",
1766
+ color: "#888"
1767
+ },
1768
+ prLink: {
1769
+ color: "#0366d6",
1770
+ textDecoration: "none",
1771
+ fontFamily: "monospace",
1772
+ fontSize: "11px"
1773
+ },
1774
+ // Chat area
1775
+ chatArea: {
1776
+ display: "flex",
1777
+ flexDirection: "column",
1778
+ flex: 1,
1779
+ overflow: "hidden"
1780
+ }
1781
+ };
26
1782
 
27
- // Toolbar button for quick navigation to global chat
28
- _managerApi.addons.add(_constants.TOOL_ID, {
29
- type: _managerApi.types.TOOLEXTRA,
30
- title: 'Claude Global Chat',
31
- render: _GlobalChatButton.GlobalChatButton
1783
+ // src/components/GlobalChatButton.jsx
1784
+ import React10, { useCallback as useCallback5 } from "react";
1785
+ import { useStorybookApi } from "storybook/manager-api";
1786
+ import { Button } from "storybook/internal/components";
1787
+ function GlobalChatButton() {
1788
+ const api = useStorybookApi();
1789
+ const handleClick = useCallback5(() => {
1790
+ api.navigateUrl("/?path=/claude", { replace: false });
1791
+ }, [api]);
1792
+ return /* @__PURE__ */ React10.createElement(Button, { variant: "ghost", padding: "small", "aria-label": "Open Claude Global Chat", onClick: handleClick }, /* @__PURE__ */ React10.createElement(
1793
+ "svg",
1794
+ {
1795
+ viewBox: "0 0 24 24",
1796
+ width: "14",
1797
+ height: "14",
1798
+ fill: "none",
1799
+ stroke: "currentColor",
1800
+ strokeWidth: "2",
1801
+ strokeLinecap: "round",
1802
+ strokeLinejoin: "round"
1803
+ },
1804
+ /* @__PURE__ */ React10.createElement("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
1805
+ ));
1806
+ }
1807
+
1808
+ // src/manager.js
1809
+ addons.register(ADDON_ID, (api) => {
1810
+ addons.add(PANEL_ID, {
1811
+ type: types.PANEL,
1812
+ title: "Claude",
1813
+ match: ({ viewMode }) => viewMode === "story",
1814
+ render: ClaudePanel
1815
+ });
1816
+ addons.add(PAGE_ID, {
1817
+ type: types.experimental_PAGE,
1818
+ title: "Claude Global",
1819
+ url: "/claude",
1820
+ render: GlobalPanel
1821
+ });
1822
+ addons.add(TOOL_ID, {
1823
+ type: types.TOOLEXTRA,
1824
+ title: "Claude Global Chat",
1825
+ render: GlobalChatButton
32
1826
  });
33
- });
1827
+ });