@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.
|
|
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
|
|
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
|
+
}
|