@radnine/storybook-addon-claude 0.2.9 → 0.3.1

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.
@@ -0,0 +1,299 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.GlobalPanel = GlobalPanel;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _managerApi = require("storybook/manager-api");
9
+ var _MessageList = require("./components/MessageList");
10
+ var _MessageInput = require("./components/MessageInput");
11
+ var _TokenInput = require("./components/TokenInput");
12
+ var _useClaudeSession = require("./useClaudeSession");
13
+ var _SkillsBar = require("./components/SkillsBar");
14
+ var _useGitContext = require("./useGitContext");
15
+ var _constants = require("./constants");
16
+ var _skills = require("./skills");
17
+ var _jsxRuntime = require("react/jsx-runtime");
18
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
19
+ /**
20
+ * Full-page global Claude chat — not scoped to any component.
21
+ *
22
+ * Registered as an experimental_PAGE so it gets its own route.
23
+ * Uses a fixed session key so the conversation persists across navigations.
24
+ * Does not inject any story/component context.
25
+ */
26
+ function GlobalPanel() {
27
+ const handleBack = (0, _react.useCallback)(() => {
28
+ window.history.back();
29
+ }, []);
30
+ const [addonState, setAddonState] = (0, _managerApi.useAddonState)(_constants.ADDON_ID, {
31
+ token: getInitialToken(),
32
+ port: getInitialPort()
33
+ });
34
+ const token = addonState?.token || null;
35
+ const port = addonState?.port || _constants.DEFAULT_PORT;
36
+ const {
37
+ connectionState,
38
+ messages,
39
+ sessionId,
40
+ isProcessing,
41
+ authFailed,
42
+ sendMessage,
43
+ startSession,
44
+ setMessages,
45
+ isConnected,
46
+ client
47
+ } = (0, _useClaudeSession.useClaudeSession)({
48
+ host: _constants.DEFAULT_HOST,
49
+ port,
50
+ token,
51
+ sessionId: _constants.GLOBAL_SESSION_KEY,
52
+ storageKey: `${_constants.ADDON_ID}:globalSessionId`
53
+ });
54
+
55
+ // Git context from daemon
56
+ const gitContext = (0, _useGitContext.useGitContext)(client, connectionState);
57
+ const skills = (0, _react.useMemo)(() => (0, _skills.getGlobalSkills)(gitContext), [gitContext]);
58
+ const handleTokenSubmit = (0, _react.useCallback)((newToken, newPort) => {
59
+ setAddonState({
60
+ ...addonState,
61
+ token: newToken,
62
+ port: newPort || _constants.DEFAULT_PORT
63
+ });
64
+ }, [addonState, setAddonState]);
65
+ const handleSend = (0, _react.useCallback)(text => {
66
+ if (!sessionId) {
67
+ startSession(_constants.GLOBAL_SESSION_KEY);
68
+ }
69
+ sendMessage(text);
70
+ }, [sessionId, startSession, sendMessage]);
71
+ const handleSkillTrigger = (0, _react.useCallback)(skill => {
72
+ // Insert a visual indicator in the chat
73
+ setMessages(prev => [...prev, {
74
+ type: 'skill_invocation',
75
+ skill,
76
+ id: crypto.randomUUID(),
77
+ timestamp: Date.now()
78
+ }]);
79
+ // Send the skill's prompt through the normal chat flow
80
+ handleSend(skill.prompt);
81
+ }, [handleSend, setMessages]);
82
+ if (authFailed && !token) {
83
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
84
+ style: styles.page,
85
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_TokenInput.TokenInput, {
86
+ onSubmit: handleTokenSubmit
87
+ })
88
+ });
89
+ }
90
+ const statusColor = connectionState === _constants.CONNECTION_STATES.CONNECTED ? '#4caf50' : connectionState === _constants.CONNECTION_STATES.DISCONNECTED ? '#f44336' : '#ff9800';
91
+ const statusLabel = connectionState === _constants.CONNECTION_STATES.CONNECTED ? 'Connected' : connectionState === _constants.CONNECTION_STATES.CONNECTING ? 'Connecting...' : connectionState === _constants.CONNECTION_STATES.RECONNECTING ? 'Reconnecting...' : 'Disconnected';
92
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
93
+ style: styles.page,
94
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
95
+ style: styles.titleBar,
96
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
97
+ style: styles.titleLeft,
98
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
99
+ style: styles.title,
100
+ children: "Claude \u2014 Global Chat"
101
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
102
+ style: {
103
+ ...styles.statusDot,
104
+ backgroundColor: statusColor
105
+ },
106
+ title: statusLabel
107
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
108
+ style: styles.statusLabel,
109
+ children: statusLabel
110
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
111
+ style: styles.infoIcon,
112
+ title: "Chat with Claude about your entire project. Not scoped to any specific component.",
113
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)("svg", {
114
+ viewBox: "0 0 16 16",
115
+ width: "14",
116
+ height: "14",
117
+ fill: "currentColor",
118
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)("path", {
119
+ 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"
120
+ })
121
+ })
122
+ })]
123
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)("button", {
124
+ style: styles.backButton,
125
+ onClick: handleBack,
126
+ title: "Back to components",
127
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("svg", {
128
+ viewBox: "0 0 24 24",
129
+ width: "14",
130
+ height: "14",
131
+ fill: "none",
132
+ stroke: "currentColor",
133
+ strokeWidth: "2",
134
+ strokeLinecap: "round",
135
+ strokeLinejoin: "round",
136
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("path", {
137
+ d: "M19 12H5"
138
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("path", {
139
+ d: "M12 19l-7-7 7-7"
140
+ })]
141
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
142
+ style: {
143
+ marginLeft: '4px'
144
+ },
145
+ children: "Back"
146
+ })]
147
+ })]
148
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
149
+ style: styles.toolsBar,
150
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_SkillsBar.SkillsBar, {
151
+ skills: skills,
152
+ onTrigger: handleSkillTrigger,
153
+ disabled: !isConnected
154
+ }), gitContext.branch && /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
155
+ style: styles.gitContext,
156
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
157
+ style: styles.branchName,
158
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
159
+ style: styles.branchIcon,
160
+ title: "Git branch",
161
+ children: "\u2387"
162
+ }), gitContext.branch]
163
+ }), gitContext.pr && /*#__PURE__*/(0, _jsxRuntime.jsxs)("a", {
164
+ href: gitContext.pr.url,
165
+ target: "_blank",
166
+ rel: "noopener noreferrer",
167
+ style: styles.prLink,
168
+ title: gitContext.pr.title,
169
+ children: ["PR #", gitContext.pr.number]
170
+ })]
171
+ })]
172
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
173
+ style: styles.chatArea,
174
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_MessageList.MessageList, {
175
+ messages: messages
176
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_MessageInput.MessageInput, {
177
+ onSend: handleSend,
178
+ disabled: !isConnected,
179
+ isProcessing: isProcessing,
180
+ placeholder: "Ask Claude about your project..."
181
+ })]
182
+ })]
183
+ });
184
+ }
185
+ function getInitialPort() {
186
+ try {
187
+ if (typeof window !== 'undefined' && window.__CLAUDE_DAEMON_PORT__) {
188
+ return parseInt(window.__CLAUDE_DAEMON_PORT__, 10) || _constants.DEFAULT_PORT;
189
+ }
190
+ const envPort = typeof process !== 'undefined' && process.env?.STORYBOOK_CLAUDE_DAEMON_PORT || null;
191
+ return envPort ? parseInt(envPort, 10) : _constants.DEFAULT_PORT;
192
+ } catch {
193
+ return _constants.DEFAULT_PORT;
194
+ }
195
+ }
196
+ function getInitialToken() {
197
+ try {
198
+ return typeof process !== 'undefined' && process.env?.STORYBOOK_CLAUDE_DAEMON_TOKEN || typeof process !== 'undefined' && process.env?.CLAUDE_DAEMON_TOKEN || null;
199
+ } catch {
200
+ return null;
201
+ }
202
+ }
203
+ const styles = {
204
+ page: {
205
+ display: 'flex',
206
+ flexDirection: 'column',
207
+ height: '100vh',
208
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'
209
+ },
210
+ // Bar 1
211
+ titleBar: {
212
+ display: 'flex',
213
+ justifyContent: 'space-between',
214
+ alignItems: 'center',
215
+ padding: '10px 16px',
216
+ borderBottom: '1px solid rgba(0,0,0,0.1)',
217
+ backgroundColor: '#fafafa'
218
+ },
219
+ titleLeft: {
220
+ display: 'flex',
221
+ alignItems: 'center',
222
+ gap: '8px'
223
+ },
224
+ title: {
225
+ fontSize: '16px',
226
+ fontWeight: 600,
227
+ color: '#333'
228
+ },
229
+ statusDot: {
230
+ width: '8px',
231
+ height: '8px',
232
+ borderRadius: '50%',
233
+ display: 'inline-block',
234
+ flexShrink: 0
235
+ },
236
+ statusLabel: {
237
+ fontSize: '12px',
238
+ color: '#888'
239
+ },
240
+ infoIcon: {
241
+ color: '#bbb',
242
+ cursor: 'help',
243
+ display: 'inline-flex',
244
+ alignItems: 'center'
245
+ },
246
+ backButton: {
247
+ display: 'inline-flex',
248
+ alignItems: 'center',
249
+ padding: '6px 12px',
250
+ border: '1px solid rgba(0,0,0,0.15)',
251
+ borderRadius: '4px',
252
+ backgroundColor: 'transparent',
253
+ color: '#555',
254
+ fontSize: '13px',
255
+ cursor: 'pointer',
256
+ whiteSpace: 'nowrap'
257
+ },
258
+ // Bar 2
259
+ toolsBar: {
260
+ display: 'flex',
261
+ alignItems: 'center',
262
+ justifyContent: 'space-between',
263
+ borderBottom: '1px solid rgba(0,0,0,0.06)',
264
+ backgroundColor: '#fff'
265
+ },
266
+ gitContext: {
267
+ display: 'flex',
268
+ flexDirection: 'column',
269
+ alignItems: 'flex-end',
270
+ gap: '1px',
271
+ padding: '6px 12px',
272
+ flexShrink: 0
273
+ },
274
+ branchName: {
275
+ fontFamily: 'monospace',
276
+ fontSize: '11px',
277
+ color: '#555',
278
+ display: 'flex',
279
+ alignItems: 'center',
280
+ gap: '3px'
281
+ },
282
+ branchIcon: {
283
+ fontSize: '12px',
284
+ color: '#888'
285
+ },
286
+ prLink: {
287
+ color: '#0366d6',
288
+ textDecoration: 'none',
289
+ fontFamily: 'monospace',
290
+ fontSize: '11px'
291
+ },
292
+ // Chat area
293
+ chatArea: {
294
+ display: 'flex',
295
+ flexDirection: 'column',
296
+ flex: 1,
297
+ overflow: 'hidden'
298
+ }
299
+ };
package/dist/Panel.js CHANGED
@@ -10,13 +10,15 @@ var _ConnectionStatus = require("./components/ConnectionStatus");
10
10
  var _MessageList = require("./components/MessageList");
11
11
  var _MessageInput = require("./components/MessageInput");
12
12
  var _TokenInput = require("./components/TokenInput");
13
+ var _GitContextBar = require("./components/GitContextBar");
13
14
  var _useClaudeSession = require("./useClaudeSession");
15
+ var _useGitContext = require("./useGitContext");
14
16
  var _useStoryContext = require("./useStoryContext");
15
17
  var _constants = require("./constants");
16
18
  var _jsxRuntime = require("react/jsx-runtime");
17
19
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
18
20
  /**
19
- * Main Storybook addon panel for Claude.
21
+ * Component-scoped Storybook addon panel for Claude.
20
22
  *
21
23
  * Manages the auth token, connects to the standalone daemon,
22
24
  * and provides the chat interface with story-context awareness.
@@ -47,12 +49,16 @@ function ClaudePanel({
47
49
  sendMessage,
48
50
  startSession,
49
51
  disconnect,
50
- isConnected
52
+ isConnected,
53
+ client
51
54
  } = (0, _useClaudeSession.useClaudeSession)({
52
55
  host: _constants.DEFAULT_HOST,
53
56
  port,
54
57
  token
55
58
  });
59
+
60
+ // Git context from daemon
61
+ const gitContext = (0, _useGitContext.useGitContext)(client, connectionState);
56
62
  const handleTokenSubmit = (0, _react.useCallback)((newToken, newPort) => {
57
63
  setAddonState({
58
64
  ...addonState,
@@ -69,7 +75,6 @@ function ClaudePanel({
69
75
  }, [addonState, setAddonState, disconnect]);
70
76
  const handleSend = (0, _react.useCallback)(text => {
71
77
  if (!sessionId) {
72
- // Auto-start a session on first message
73
78
  startSession();
74
79
  }
75
80
  const prefix = contextEnabled ? (0, _useStoryContext.buildContextPrefix)(storyContext) : null;
@@ -100,6 +105,10 @@ function ClaudePanel({
100
105
  state: connectionState,
101
106
  sessionId: sessionId,
102
107
  onDisconnect: handleDisconnect
108
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_GitContextBar.GitContextBar, {
109
+ branch: gitContext.branch,
110
+ pr: gitContext.pr,
111
+ loading: gitContext.loading
103
112
  }), storyContext.componentPath && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
104
113
  style: styles.contextBar,
105
114
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)("label", {
@@ -151,6 +151,16 @@ class WebSocketClient {
151
151
  });
152
152
  }
153
153
 
154
+ /**
155
+ * Send a query to the daemon.
156
+ */
157
+ sendQuery(queryType) {
158
+ this._send({
159
+ type: 'query',
160
+ queryType
161
+ });
162
+ }
163
+
154
164
  // ---------------------------------------------------------------------------
155
165
  // Internal
156
166
  // ---------------------------------------------------------------------------
@@ -174,6 +184,7 @@ class WebSocketClient {
174
184
  case 'error':
175
185
  case 'user_input':
176
186
  case 'pong':
187
+ case 'query_result':
177
188
  this._emit(msg.type, msg);
178
189
  break;
179
190
  default:
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.GitContextBar = GitContextBar;
7
+ var _react = _interopRequireDefault(require("react"));
8
+ var _jsxRuntime = require("react/jsx-runtime");
9
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ /**
11
+ * Thin horizontal bar displaying the current git branch and PR link.
12
+ *
13
+ * Renders nothing if loading or no data is available.
14
+ */
15
+ function GitContextBar({
16
+ branch,
17
+ pr,
18
+ loading
19
+ }) {
20
+ if (loading || !branch) return null;
21
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
22
+ style: styles.container,
23
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
24
+ style: styles.left,
25
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
26
+ style: styles.branchIcon,
27
+ title: "Git branch",
28
+ children: "\u2387"
29
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
30
+ style: styles.branchName,
31
+ children: branch
32
+ })]
33
+ }), pr && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
34
+ style: styles.right,
35
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)("a", {
36
+ href: pr.url,
37
+ target: "_blank",
38
+ rel: "noopener noreferrer",
39
+ style: styles.prLink,
40
+ title: pr.title,
41
+ children: ["PR #", pr.number]
42
+ })
43
+ })]
44
+ });
45
+ }
46
+ const styles = {
47
+ container: {
48
+ display: 'flex',
49
+ alignItems: 'center',
50
+ justifyContent: 'space-between',
51
+ padding: '3px 12px',
52
+ borderBottom: '1px solid rgba(0,0,0,0.1)',
53
+ backgroundColor: '#f5f5f5',
54
+ fontSize: '11px',
55
+ fontFamily: 'inherit'
56
+ },
57
+ left: {
58
+ display: 'flex',
59
+ alignItems: 'center',
60
+ gap: '4px'
61
+ },
62
+ branchIcon: {
63
+ fontSize: '12px',
64
+ color: '#888'
65
+ },
66
+ branchName: {
67
+ fontFamily: 'monospace',
68
+ fontSize: '11px',
69
+ color: '#555',
70
+ backgroundColor: '#eee',
71
+ padding: '1px 5px',
72
+ borderRadius: '3px'
73
+ },
74
+ right: {
75
+ display: 'flex',
76
+ alignItems: 'center'
77
+ },
78
+ prLink: {
79
+ color: '#0366d6',
80
+ textDecoration: 'none',
81
+ fontFamily: 'monospace',
82
+ fontSize: '11px'
83
+ }
84
+ };
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.GlobalChatButton = GlobalChatButton;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _managerApi = require("storybook/manager-api");
9
+ var _components = require("storybook/internal/components");
10
+ var _jsxRuntime = require("react/jsx-runtime");
11
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
12
+ /**
13
+ * Toolbar button that navigates to the global Claude chat page.
14
+ */
15
+ function GlobalChatButton() {
16
+ const api = (0, _managerApi.useStorybookApi)();
17
+ const handleClick = (0, _react.useCallback)(() => {
18
+ api.navigateUrl('/?path=/claude', {
19
+ replace: false
20
+ });
21
+ }, [api]);
22
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.IconButton, {
23
+ title: "Open Claude Global Chat",
24
+ onClick: handleClick,
25
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)("svg", {
26
+ viewBox: "0 0 24 24",
27
+ width: "14",
28
+ height: "14",
29
+ fill: "none",
30
+ stroke: "currentColor",
31
+ strokeWidth: "2",
32
+ strokeLinecap: "round",
33
+ strokeLinejoin: "round",
34
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)("path", {
35
+ d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
36
+ })
37
+ })
38
+ });
39
+ }
@@ -14,7 +14,8 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
14
14
  function MessageInput({
15
15
  onSend,
16
16
  disabled,
17
- isProcessing
17
+ isProcessing,
18
+ placeholder
18
19
  }) {
19
20
  const [text, setText] = (0, _react.useState)('');
20
21
  const textareaRef = (0, _react.useRef)(null);
@@ -49,7 +50,7 @@ function MessageInput({
49
50
  value: text,
50
51
  onChange: handleInput,
51
52
  onKeyDown: handleKeyDown,
52
- placeholder: disabled ? 'Connect to daemon first...' : 'Ask Claude...',
53
+ placeholder: disabled ? 'Connect to daemon first...' : placeholder || 'Ask Claude...',
53
54
  disabled: disabled,
54
55
  rows: 1
55
56
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)("button", {
@@ -53,6 +53,10 @@ function MessageItem({
53
53
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(UserMessage, {
54
54
  text: message.text
55
55
  });
56
+ case 'skill_invocation':
57
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(SkillInvocationMessage, {
58
+ skill: message.skill
59
+ });
56
60
  case 'output':
57
61
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(OutputMessage, {
58
62
  data: message.data,
@@ -354,6 +358,23 @@ function CompleteMessage({
354
358
  })
355
359
  });
356
360
  }
361
+ function SkillInvocationMessage({
362
+ skill
363
+ }) {
364
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
365
+ style: styles.skillRow,
366
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
367
+ style: styles.skillBubble,
368
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
369
+ style: styles.skillIcon,
370
+ children: skill.icon
371
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)("span", {
372
+ style: styles.skillLabel,
373
+ children: ["Skill: ", skill.label]
374
+ })]
375
+ })
376
+ });
377
+ }
357
378
  function ErrorMessage({
358
379
  message
359
380
  }) {
@@ -567,6 +588,28 @@ const styles = {
567
588
  maxHeight: '200px',
568
589
  maxWidth: '90%'
569
590
  },
591
+ // Skill invocation
592
+ skillRow: {
593
+ display: 'flex',
594
+ justifyContent: 'center'
595
+ },
596
+ skillBubble: {
597
+ display: 'inline-flex',
598
+ alignItems: 'center',
599
+ gap: '6px',
600
+ padding: '6px 14px',
601
+ borderRadius: '8px',
602
+ backgroundColor: '#e8eaf6',
603
+ fontSize: '12px',
604
+ color: '#3949ab',
605
+ fontWeight: 500
606
+ },
607
+ skillIcon: {
608
+ fontSize: '14px'
609
+ },
610
+ skillLabel: {
611
+ fontWeight: 600
612
+ },
570
613
  // Error messages
571
614
  errorRow: {
572
615
  display: 'flex',
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.SkillsBar = SkillsBar;
7
+ var _react = _interopRequireDefault(require("react"));
8
+ var _jsxRuntime = require("react/jsx-runtime");
9
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ /**
11
+ * Horizontal row of skill buttons rendered in the global chat page.
12
+ *
13
+ * Each button triggers its skill's prompt through the chat session.
14
+ * Disabled when not connected to the daemon.
15
+ */
16
+ function SkillsBar({
17
+ skills,
18
+ onTrigger,
19
+ disabled
20
+ }) {
21
+ if (!skills || skills.length === 0) return null;
22
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
23
+ style: styles.bar,
24
+ children: skills.map(skill => /*#__PURE__*/(0, _jsxRuntime.jsxs)("button", {
25
+ style: {
26
+ ...styles.button,
27
+ ...(disabled ? styles.buttonDisabled : {})
28
+ },
29
+ onClick: () => onTrigger(skill),
30
+ disabled: disabled,
31
+ title: skill.prompt,
32
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
33
+ style: styles.icon,
34
+ children: skill.icon
35
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
36
+ children: skill.label
37
+ })]
38
+ }, skill.id))
39
+ });
40
+ }
41
+ const styles = {
42
+ bar: {
43
+ display: 'flex',
44
+ gap: '6px',
45
+ padding: '8px 12px',
46
+ overflowX: 'auto',
47
+ flexShrink: 0
48
+ },
49
+ button: {
50
+ display: 'inline-flex',
51
+ alignItems: 'center',
52
+ gap: '4px',
53
+ padding: '5px 10px',
54
+ border: '1px solid rgba(0,0,0,0.12)',
55
+ borderRadius: '16px',
56
+ backgroundColor: '#fff',
57
+ color: '#444',
58
+ fontSize: '12px',
59
+ cursor: 'pointer',
60
+ whiteSpace: 'nowrap',
61
+ transition: 'background-color 0.15s'
62
+ },
63
+ buttonDisabled: {
64
+ opacity: 0.45,
65
+ cursor: 'default'
66
+ },
67
+ icon: {
68
+ fontSize: '13px'
69
+ }
70
+ };
package/dist/constants.js CHANGED
@@ -3,12 +3,15 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.PARAM_KEY = exports.PANEL_ID = exports.DEFAULT_PORT = exports.DEFAULT_HOST = exports.CONNECTION_STATES = exports.ADDON_ID = void 0;
6
+ exports.TOOL_ID = exports.PARAM_KEY = exports.PANEL_ID = exports.PAGE_ID = exports.GLOBAL_SESSION_KEY = exports.DEFAULT_PORT = exports.DEFAULT_HOST = exports.CONNECTION_STATES = exports.ADDON_ID = void 0;
7
7
  const ADDON_ID = exports.ADDON_ID = 'storybook-addon-claude';
8
8
  const PANEL_ID = exports.PANEL_ID = `${ADDON_ID}/panel`;
9
+ const PAGE_ID = exports.PAGE_ID = `${ADDON_ID}/page`;
10
+ const TOOL_ID = exports.TOOL_ID = `${ADDON_ID}/tool`;
9
11
  const PARAM_KEY = exports.PARAM_KEY = 'claude';
10
12
  const DEFAULT_PORT = exports.DEFAULT_PORT = 3001;
11
13
  const DEFAULT_HOST = exports.DEFAULT_HOST = 'localhost';
14
+ const GLOBAL_SESSION_KEY = exports.GLOBAL_SESSION_KEY = 'claude-global';
12
15
  const CONNECTION_STATES = exports.CONNECTION_STATES = {
13
16
  DISCONNECTED: 'disconnected',
14
17
  CONNECTING: 'connecting',
package/dist/manager.js CHANGED
@@ -3,7 +3,10 @@
3
3
  var _managerApi = require("storybook/manager-api");
4
4
  var _constants = require("./constants");
5
5
  var _Panel = require("./Panel");
6
+ var _GlobalPanel = require("./GlobalPanel");
7
+ var _GlobalChatButton = require("./components/GlobalChatButton");
6
8
  _managerApi.addons.register(_constants.ADDON_ID, api => {
9
+ // Component-scoped chat panel (addon area below canvas)
7
10
  _managerApi.addons.add(_constants.PANEL_ID, {
8
11
  type: _managerApi.types.PANEL,
9
12
  title: 'Claude',
@@ -12,4 +15,19 @@ _managerApi.addons.register(_constants.ADDON_ID, api => {
12
15
  }) => viewMode === 'story',
13
16
  render: _Panel.ClaudePanel
14
17
  });
18
+
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
25
+ });
26
+
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
32
+ });
15
33
  });
package/dist/skills.js ADDED
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getGlobalSkills = getGlobalSkills;
7
+ /**
8
+ * Hardcoded global skill definitions.
9
+ *
10
+ * Each skill is a chat shortcut — triggering it sends the prompt through the
11
+ * active session and shows a visual indicator in the message list.
12
+ */
13
+
14
+ const STATIC_SKILLS = [{
15
+ id: 'sync-components',
16
+ label: 'Sync Components',
17
+ icon: '\u21BB',
18
+ prompt: 'Synchronize all Storybook components with their Figma designs. Report what changed.'
19
+ }, {
20
+ id: 'run-tests',
21
+ label: 'Run Tests',
22
+ icon: '\u25B6',
23
+ prompt: 'Run the full test suite and summarize the results.'
24
+ }, {
25
+ id: 'audit-stories',
26
+ label: 'Audit Stories',
27
+ icon: '\uD83D\uDD0D',
28
+ prompt: 'Audit all components and list any that are missing Storybook stories or have outdated ones.'
29
+ }];
30
+
31
+ /**
32
+ * Returns the PR skill definition based on whether a PR exists on the current branch.
33
+ */
34
+ function getPrSkill(pr) {
35
+ if (pr && pr.number) {
36
+ return {
37
+ id: 'finalize-pr',
38
+ label: 'Finalize PR',
39
+ icon: '\u2714',
40
+ 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.`
41
+ };
42
+ }
43
+ return {
44
+ id: 'new-pr',
45
+ label: 'New PR',
46
+ icon: '\u2795',
47
+ prompt: 'Create a new pull request for the current branch with a summary of all changes.'
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Build the full skills list, with the PR skill adapting to git context.
53
+ */
54
+ function getGlobalSkills(gitContext) {
55
+ const pr = gitContext?.pr || null;
56
+ return [STATIC_SKILLS[0],
57
+ // Sync Components
58
+ getPrSkill(pr),
59
+ // New PR / Finalize PR
60
+ STATIC_SKILLS[1],
61
+ // Run Tests
62
+ STATIC_SKILLS[2] // Audit Stories
63
+ ];
64
+ }
@@ -7,20 +7,20 @@ exports.useClaudeSession = useClaudeSession;
7
7
  var _react = require("react");
8
8
  var _WebSocketClient = require("./WebSocketClient");
9
9
  var _constants = require("./constants");
10
- const SESSION_STORAGE_KEY = `${_constants.ADDON_ID}:sessionId`;
11
- function loadPersistedSessionId() {
10
+ const DEFAULT_STORAGE_KEY = `${_constants.ADDON_ID}:sessionId`;
11
+ function loadPersistedSessionId(storageKey) {
12
12
  try {
13
- return localStorage.getItem(SESSION_STORAGE_KEY) || null;
13
+ return localStorage.getItem(storageKey) || null;
14
14
  } catch {
15
15
  return null;
16
16
  }
17
17
  }
18
- function persistSessionId(id) {
18
+ function persistSessionId(storageKey, id) {
19
19
  try {
20
20
  if (id) {
21
- localStorage.setItem(SESSION_STORAGE_KEY, id);
21
+ localStorage.setItem(storageKey, id);
22
22
  } else {
23
- localStorage.removeItem(SESSION_STORAGE_KEY);
23
+ localStorage.removeItem(storageKey);
24
24
  }
25
25
  } catch {
26
26
  // localStorage may be unavailable
@@ -39,11 +39,13 @@ function useClaudeSession(options = {}) {
39
39
  host,
40
40
  port,
41
41
  token,
42
- sessionId: initialSessionId
42
+ sessionId: initialSessionId,
43
+ storageKey
43
44
  } = options;
45
+ const _storageKey = storageKey || DEFAULT_STORAGE_KEY;
44
46
  const [connectionState, setConnectionState] = (0, _react.useState)(_constants.CONNECTION_STATES.DISCONNECTED);
45
47
  const [messages, setMessages] = (0, _react.useState)([]);
46
- const [sessionId, setSessionId] = (0, _react.useState)(initialSessionId || loadPersistedSessionId());
48
+ const [sessionId, setSessionId] = (0, _react.useState)(initialSessionId || loadPersistedSessionId(_storageKey));
47
49
  const [isProcessing, setIsProcessing] = (0, _react.useState)(false);
48
50
  const [authFailed, setAuthFailed] = (0, _react.useState)(false);
49
51
  const clientRef = (0, _react.useRef)(null);
@@ -64,13 +66,18 @@ function useClaudeSession(options = {}) {
64
66
  setConnectionState(newState);
65
67
  }), client.on('session_activated', msg => {
66
68
  setSessionId(msg.sessionId);
67
- persistSessionId(msg.sessionId);
69
+ persistSessionId(_storageKey, msg.sessionId);
68
70
  }), client.on('output', msg => {
69
71
  if (msg.sessionId !== sessionIdRef.current) return;
70
72
  setMessages(prev => [...prev, {
71
73
  ...msg,
72
74
  id: crypto.randomUUID()
73
75
  }]);
76
+ // A 'result' output signals the end of Claude's turn in streaming mode
77
+ // (the CLI process stays alive, so 'complete' may never fire)
78
+ if (msg.data && msg.data.type === 'result') {
79
+ setIsProcessing(false);
80
+ }
74
81
  }), client.on('user_input', msg => {
75
82
  if (msg.sessionId !== sessionIdRef.current) return;
76
83
  // Replayed user messages from session history — strip context prefix
@@ -155,7 +162,7 @@ function useClaudeSession(options = {}) {
155
162
  // Synchronously update the ref so sendMessage sees it immediately
156
163
  sessionIdRef.current = sid;
157
164
  setSessionId(sid);
158
- persistSessionId(sid);
165
+ persistSessionId(_storageKey, sid);
159
166
  setMessages([]);
160
167
  if (client && client.isConnected) {
161
168
  client.activate(sid, workingDir);
@@ -172,7 +179,7 @@ function useClaudeSession(options = {}) {
172
179
  }
173
180
  sessionIdRef.current = null;
174
181
  setSessionId(null);
175
- persistSessionId(null);
182
+ persistSessionId(_storageKey, null);
176
183
  setMessages([]);
177
184
  setIsProcessing(false);
178
185
  }, []);
@@ -185,7 +192,9 @@ function useClaudeSession(options = {}) {
185
192
  sendMessage,
186
193
  startSession,
187
194
  clearMessages,
195
+ setMessages,
188
196
  disconnect,
189
- isConnected: connectionState === _constants.CONNECTION_STATES.CONNECTED
197
+ isConnected: connectionState === _constants.CONNECTION_STATES.CONNECTED,
198
+ client: clientRef.current
190
199
  };
191
200
  }
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useGitContext = useGitContext;
7
+ var _react = require("react");
8
+ var _constants = require("./constants");
9
+ /**
10
+ * React hook that fetches git context (branch, PR info) from the daemon.
11
+ *
12
+ * Sends a `query` message with `queryType: 'git_context'` when the WebSocket
13
+ * client is connected, and refreshes every 60 seconds.
14
+ *
15
+ * @param {import('./WebSocketClient').WebSocketClient} client - The WebSocket client instance
16
+ * @param {string} connectionState - Current connection state
17
+ * @returns {{ branch: string|null, pr: object|null, loading: boolean }}
18
+ */
19
+ function useGitContext(client, connectionState) {
20
+ const [gitData, setGitData] = (0, _react.useState)(null);
21
+ const [loading, setLoading] = (0, _react.useState)(false);
22
+ const intervalRef = (0, _react.useRef)(null);
23
+ const unsubRef = (0, _react.useRef)(null);
24
+ const fetchContext = (0, _react.useCallback)(() => {
25
+ if (!client || !client.isConnected) return;
26
+ setLoading(true);
27
+ client.sendQuery('git_context');
28
+ }, [client]);
29
+ (0, _react.useEffect)(() => {
30
+ if (!client) return;
31
+
32
+ // Listen for query_result messages
33
+ const unsub = client.on('query_result', msg => {
34
+ if (msg.queryType === 'git_context') {
35
+ setGitData(msg.data || null);
36
+ setLoading(false);
37
+ }
38
+ });
39
+
40
+ // Also listen for errors related to queries (older daemons)
41
+ const unsubErr = client.on('error', msg => {
42
+ if (msg.code === 'UNKNOWN_TYPE' || msg.code === 'UNKNOWN_QUERY') {
43
+ // Daemon doesn't support query — stop trying
44
+ setLoading(false);
45
+ }
46
+ });
47
+ unsubRef.current = () => {
48
+ unsub();
49
+ unsubErr();
50
+ };
51
+ return () => {
52
+ if (unsubRef.current) unsubRef.current();
53
+ };
54
+ }, [client]);
55
+
56
+ // Fetch on connect and refresh every 60 seconds
57
+ (0, _react.useEffect)(() => {
58
+ if (connectionState !== _constants.CONNECTION_STATES.CONNECTED) {
59
+ // Clear interval when not connected
60
+ if (intervalRef.current) {
61
+ clearInterval(intervalRef.current);
62
+ intervalRef.current = null;
63
+ }
64
+ return;
65
+ }
66
+
67
+ // Fetch immediately on connect
68
+ fetchContext();
69
+
70
+ // Refresh every 60 seconds
71
+ intervalRef.current = setInterval(fetchContext, 60000);
72
+ return () => {
73
+ if (intervalRef.current) {
74
+ clearInterval(intervalRef.current);
75
+ intervalRef.current = null;
76
+ }
77
+ };
78
+ }, [connectionState, fetchContext]);
79
+ return {
80
+ branch: gitData?.branch || null,
81
+ pr: gitData?.pr || null,
82
+ loading
83
+ };
84
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radnine/storybook-addon-claude",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
4
4
  "description": "Storybook addon panel for chatting with Claude via the standalone daemon",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {