@pennyfarthing/cyclist 10.0.0 → 10.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pennyfarthing/cyclist",
3
- "version": "10.0.0",
3
+ "version": "10.0.1",
4
4
  "description": "Visual terminal interface for Claude Code",
5
5
  "author": "1898andCo",
6
6
  "type": "module",
@@ -19,36 +19,7 @@
19
19
  "src/public/",
20
20
  "portraits/"
21
21
  ],
22
- "scripts": {
23
- "dev": "npm run build && concurrently -k -n tsc,preload,vite,electron -c blue,cyan,magenta,green \"tsc --watch --preserveWatchOutput\" \"tsc -p tsconfig.preload.json --watch --preserveWatchOutput\" \"vite build --watch\" \"electron dist/main.js\"",
24
- "dev:cdp": "npm run build && concurrently -k -n tsc,preload,vite,electron -c blue,cyan,magenta,green \"tsc --watch --preserveWatchOutput\" \"tsc -p tsconfig.preload.json --watch --preserveWatchOutput\" \"vite build --watch\" \"electron --remote-debugging-port=9222 dist/main.js\"",
25
- "dev:once": "npm run build && electron dist/main.js",
26
- "dev:web": "CYCLIST_ELECTRON_MODE= CYCLIST_DEV_WEB=1 CYCLIST_PROJECT_DIR=${CYCLIST_PROJECT_DIR:-$PWD} tsx watch src/server.ts",
27
- "dev:server": "tsx watch src/server.ts",
28
- "dev:debug": "CYCLIST_DEV_WEB=1 OTEL_DEBUG=true CYCLIST_PROJECT_DIR=${CYCLIST_PROJECT_DIR:-$PWD} node --inspect src/server.ts",
29
- "dev:debug-brk": "CYCLIST_DEV_WEB=1 OTEL_DEBUG=true CYCLIST_PROJECT_DIR=${CYCLIST_PROJECT_DIR:-$PWD} node --inspect-brk src/server.ts",
30
- "dev:electron-debug": "npm run build && concurrently -k -n tsc,preload,vite,electron -c blue,cyan,magenta,green \"tsc --watch --preserveWatchOutput\" \"tsc -p tsconfig.preload.json --watch --preserveWatchOutput\" \"vite build --watch\" \"electron --inspect=9229 dist/main.js\"",
31
- "dev:vite": "vite --config vite.config.ts",
32
- "build": "npm run build:commands && tsc --build && tsc -p tsconfig.preload.json && npm run build:react",
33
- "build:react": "vite build",
34
- "build:commands": "node scripts/generate-slash-commands.js",
35
- "build:electron": "npm run build && electron-builder -c.npmRebuild=false",
36
- "start": "node dist/server.js",
37
- "test": "vitest run",
38
- "test:watch": "vitest",
39
- "test:e2e": "playwright test",
40
- "test:e2e:web": "playwright test --project=web-chromium",
41
- "test:e2e:ui": "playwright test --ui",
42
- "test:e2e:debug": "playwright test --debug",
43
- "test:e2e:trace": "playwright show-trace",
44
- "prepack": "./scripts/copy-portraits.sh",
45
- "install:app": "./scripts/install-app.sh",
46
- "install:cli": "./scripts/install-cli.sh",
47
- "install:all": "npm run install:app && npm run install:cli"
48
- },
49
22
  "dependencies": {
50
- "@pennyfarthing/core": "workspace:*",
51
- "@pennyfarthing/shared": "workspace:*",
52
23
  "@radix-ui/react-alert-dialog": "^1.1.15",
53
24
  "@radix-ui/react-checkbox": "^1.3.3",
54
25
  "@radix-ui/react-collapsible": "^1.1.12",
@@ -78,7 +49,9 @@
78
49
  "ws": "^8.19.0",
79
50
  "xterm": "^5.3.0",
80
51
  "xterm-addon-fit": "^0.8.0",
81
- "yaml": "^2.8.2"
52
+ "yaml": "^2.8.2",
53
+ "@pennyfarthing/core": "^10.0.1",
54
+ "@pennyfarthing/shared": "^10.0.1"
82
55
  },
83
56
  "devDependencies": {
84
57
  "@electron/rebuild": "^3.6.0",
@@ -115,7 +88,7 @@
115
88
  "build": {
116
89
  "appId": "com.cyclist.app",
117
90
  "productName": "Cyclist",
118
- "copyright": "Copyright \u00a9 2026",
91
+ "copyright": "Copyright © 2026",
119
92
  "asar": true,
120
93
  "directories": {
121
94
  "output": "release",
@@ -186,5 +159,31 @@
186
159
  ],
187
160
  "category": "Development"
188
161
  }
162
+ },
163
+ "scripts": {
164
+ "dev": "npm run build && concurrently -k -n tsc,preload,vite,electron -c blue,cyan,magenta,green \"tsc --watch --preserveWatchOutput\" \"tsc -p tsconfig.preload.json --watch --preserveWatchOutput\" \"vite build --watch\" \"electron dist/main.js\"",
165
+ "dev:cdp": "npm run build && concurrently -k -n tsc,preload,vite,electron -c blue,cyan,magenta,green \"tsc --watch --preserveWatchOutput\" \"tsc -p tsconfig.preload.json --watch --preserveWatchOutput\" \"vite build --watch\" \"electron --remote-debugging-port=9222 dist/main.js\"",
166
+ "dev:once": "npm run build && electron dist/main.js",
167
+ "dev:web": "CYCLIST_ELECTRON_MODE= CYCLIST_DEV_WEB=1 CYCLIST_PROJECT_DIR=${CYCLIST_PROJECT_DIR:-$PWD} tsx watch src/server.ts",
168
+ "dev:server": "tsx watch src/server.ts",
169
+ "dev:debug": "CYCLIST_DEV_WEB=1 OTEL_DEBUG=true CYCLIST_PROJECT_DIR=${CYCLIST_PROJECT_DIR:-$PWD} node --inspect src/server.ts",
170
+ "dev:debug-brk": "CYCLIST_DEV_WEB=1 OTEL_DEBUG=true CYCLIST_PROJECT_DIR=${CYCLIST_PROJECT_DIR:-$PWD} node --inspect-brk src/server.ts",
171
+ "dev:electron-debug": "npm run build && concurrently -k -n tsc,preload,vite,electron -c blue,cyan,magenta,green \"tsc --watch --preserveWatchOutput\" \"tsc -p tsconfig.preload.json --watch --preserveWatchOutput\" \"vite build --watch\" \"electron --inspect=9229 dist/main.js\"",
172
+ "dev:vite": "vite --config vite.config.ts",
173
+ "build": "npm run build:commands && tsc --build && tsc -p tsconfig.preload.json && npm run build:react",
174
+ "build:react": "vite build",
175
+ "build:commands": "node scripts/generate-slash-commands.js",
176
+ "build:electron": "npm run build && electron-builder -c.npmRebuild=false",
177
+ "start": "node dist/server.js",
178
+ "test": "vitest run",
179
+ "test:watch": "vitest",
180
+ "test:e2e": "playwright test",
181
+ "test:e2e:web": "playwright test --project=web-chromium",
182
+ "test:e2e:ui": "playwright test --ui",
183
+ "test:e2e:debug": "playwright test --debug",
184
+ "test:e2e:trace": "playwright show-trace",
185
+ "install:app": "./scripts/install-app.sh",
186
+ "install:cli": "./scripts/install-cli.sh",
187
+ "install:all": "npm run install:app && npm run install:cli"
189
188
  }
190
- }
189
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * AskUserQuestionBlock Component
3
+ *
4
+ * Renders interactive buttons for AskUserQuestion tool_use messages.
5
+ * Uses the existing ClaudeContext to send responses back via WebSocket.
6
+ *
7
+ * Story: MSSCI-14395 - Render AskUserQuestion tool via Reflector QuickActions
8
+ */
9
+
10
+ import React, { useState, useCallback } from 'react';
11
+ import { Button } from '@/components/ui/button';
12
+ import { Badge } from '@/components/ui/badge';
13
+ import { useClaudeContext } from '../contexts/ClaudeContext';
14
+
15
+ interface QuestionOption {
16
+ label: string;
17
+ description: string;
18
+ }
19
+
20
+ interface Question {
21
+ question: string;
22
+ header: string;
23
+ options: QuestionOption[];
24
+ multiSelect: boolean;
25
+ }
26
+
27
+ interface AskUserQuestionToolUse {
28
+ type: 'tool_use';
29
+ tool_name: string;
30
+ tool_id: string;
31
+ input: {
32
+ questions: Question[];
33
+ };
34
+ timestamp: number;
35
+ }
36
+
37
+ interface AskUserQuestionBlockProps {
38
+ toolUse: AskUserQuestionToolUse;
39
+ }
40
+
41
+ function SingleSelectQuestion({ question, onSubmit, disabled }: {
42
+ question: Question;
43
+ onSubmit: (answer: string) => void;
44
+ disabled: boolean;
45
+ }) {
46
+ return (
47
+ <div className="ask-question-group">
48
+ <Badge variant="secondary" className="ask-question-header">{question.header}</Badge>
49
+ <p className="ask-question-text">{question.question}</p>
50
+ <div className="ask-question-options">
51
+ {question.options.map((opt) => (
52
+ <Button
53
+ key={opt.label}
54
+ variant="secondary"
55
+ size="sm"
56
+ className="ask-question-option"
57
+ onClick={() => onSubmit(opt.label)}
58
+ disabled={disabled}
59
+ >
60
+ <span className="ask-option-label">{opt.label}</span>
61
+ <span className="ask-option-desc">{opt.description}</span>
62
+ </Button>
63
+ ))}
64
+ </div>
65
+ </div>
66
+ );
67
+ }
68
+
69
+ function MultiSelectQuestion({ question, onSubmit, disabled }: {
70
+ question: Question;
71
+ onSubmit: (answers: string[]) => void;
72
+ disabled: boolean;
73
+ }) {
74
+ const [selected, setSelected] = useState<Set<string>>(new Set());
75
+
76
+ const toggleOption = useCallback((label: string) => {
77
+ setSelected(prev => {
78
+ const next = new Set(prev);
79
+ if (next.has(label)) {
80
+ next.delete(label);
81
+ } else {
82
+ next.add(label);
83
+ }
84
+ return next;
85
+ });
86
+ }, []);
87
+
88
+ const handleSubmit = useCallback(() => {
89
+ onSubmit(Array.from(selected));
90
+ }, [selected, onSubmit]);
91
+
92
+ return (
93
+ <div className="ask-question-group">
94
+ <Badge variant="secondary" className="ask-question-header">{question.header}</Badge>
95
+ <p className="ask-question-text">{question.question}</p>
96
+ <div className="ask-question-options">
97
+ {question.options.map((opt) => (
98
+ <Button
99
+ key={opt.label}
100
+ variant="secondary"
101
+ size="sm"
102
+ className="ask-question-option"
103
+ onClick={() => toggleOption(opt.label)}
104
+ disabled={disabled}
105
+ aria-pressed={selected.has(opt.label)}
106
+ >
107
+ <span className="ask-option-label">{opt.label}</span>
108
+ <span className="ask-option-desc">{opt.description}</span>
109
+ </Button>
110
+ ))}
111
+ </div>
112
+ <Button
113
+ variant="default"
114
+ size="sm"
115
+ className="ask-question-submit"
116
+ onClick={handleSubmit}
117
+ disabled={disabled || selected.size === 0}
118
+ aria-label="Confirm"
119
+ >
120
+ Confirm
121
+ </Button>
122
+ </div>
123
+ );
124
+ }
125
+
126
+ export function AskUserQuestionBlock({ toolUse }: AskUserQuestionBlockProps): React.ReactElement {
127
+ const { send } = useClaudeContext();
128
+ const [isDisabled, setIsDisabled] = useState(false);
129
+ const questions = toolUse.input?.questions || [];
130
+
131
+ const handleSingleSelect = useCallback((answer: string) => {
132
+ setIsDisabled(true);
133
+ send(answer, []);
134
+ }, [send]);
135
+
136
+ const handleMultiSelect = useCallback((answers: string[]) => {
137
+ setIsDisabled(true);
138
+ send(answers.join(', '), []);
139
+ }, [send]);
140
+
141
+ return (
142
+ <div className="ask-user-question-block">
143
+ {questions.map((q, i) => (
144
+ q.multiSelect ? (
145
+ <MultiSelectQuestion
146
+ key={i}
147
+ question={q}
148
+ onSubmit={handleMultiSelect}
149
+ disabled={isDisabled}
150
+ />
151
+ ) : (
152
+ <SingleSelectQuestion
153
+ key={i}
154
+ question={q}
155
+ onSubmit={handleSingleSelect}
156
+ disabled={isDisabled}
157
+ />
158
+ )
159
+ ))}
160
+ </div>
161
+ );
162
+ }
@@ -20,6 +20,7 @@ import { Button } from '@/components/ui/button';
20
20
  import MessageList, { MessageListHandle } from './MessageList';
21
21
  import Message from './Message';
22
22
  import ToolCallBlock from './ToolCallBlock';
23
+ import { AskUserQuestionBlock } from './AskUserQuestionBlock';
23
24
  import ToolStack from './ToolStack';
24
25
  import SubagentSpan from './SubagentSpan';
25
26
  import QuickActions from './QuickActions';
@@ -227,6 +228,21 @@ export default function MessageView({ messages }: MessageViewProps): React.React
227
228
  const msg = item as MessageData;
228
229
 
229
230
  if (msg.type === 'tool_use' && msg.tool_name && msg.tool_id) {
231
+ // MSSCI-14395: Render AskUserQuestion as interactive buttons
232
+ if (msg.tool_name === 'AskUserQuestion') {
233
+ return (
234
+ <AskUserQuestionBlock
235
+ key={`ask-${msg.tool_id}`}
236
+ toolUse={{
237
+ type: 'tool_use',
238
+ tool_name: msg.tool_name,
239
+ tool_id: msg.tool_id,
240
+ input: (msg.input || {}) as { questions: Array<{ question: string; header: string; options: Array<{ label: string; description: string }>; multiSelect: boolean }> },
241
+ timestamp: msg.timestamp,
242
+ }}
243
+ />
244
+ );
245
+ }
230
246
  const result = toolResults.get(msg.tool_id);
231
247
  return (
232
248
  <ToolCallBlock
@@ -0,0 +1,21 @@
1
+ /**
2
+ * AskUserQuestion Utility
3
+ *
4
+ * Utility functions for detecting AskUserQuestion tool_use messages.
5
+ *
6
+ * Story: MSSCI-14395 - Render AskUserQuestion tool via Reflector QuickActions
7
+ */
8
+
9
+ interface ToolUseMessage {
10
+ type: string;
11
+ tool_name?: string;
12
+ tool_id?: string;
13
+ input?: Record<string, unknown>;
14
+ }
15
+
16
+ /**
17
+ * Check if a tool_use message is an AskUserQuestion tool call.
18
+ */
19
+ export function isAskUserQuestion(toolUse: ToolUseMessage): boolean {
20
+ return toolUse.tool_name === 'AskUserQuestion';
21
+ }