@pennyfarthing/cyclist 10.3.1 → 11.0.0-alpha.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/api/agent-load.d.ts +1 -2
- package/dist/api/agent-load.d.ts.map +1 -1
- package/dist/api/agent-load.js +2 -123
- package/dist/api/agent-load.js.map +1 -1
- package/dist/api/audit-log.d.ts +1 -17
- package/dist/api/audit-log.d.ts.map +1 -1
- package/dist/api/audit-log.js +2 -162
- package/dist/api/audit-log.js.map +1 -1
- package/dist/api/background-tasks.d.ts +1 -26
- package/dist/api/background-tasks.d.ts.map +1 -1
- package/dist/api/background-tasks.js +2 -55
- package/dist/api/background-tasks.js.map +1 -1
- package/dist/api/bell.d.ts +1 -18
- package/dist/api/bell.d.ts.map +1 -1
- package/dist/api/bell.js +2 -33
- package/dist/api/bell.js.map +1 -1
- package/dist/api/code-markers.d.ts +1 -8
- package/dist/api/code-markers.d.ts.map +1 -1
- package/dist/api/code-markers.js +2 -61
- package/dist/api/code-markers.js.map +1 -1
- package/dist/api/complexity.d.ts +1 -2
- package/dist/api/complexity.d.ts.map +1 -1
- package/dist/api/complexity.js +2 -46
- package/dist/api/complexity.js.map +1 -1
- package/dist/api/context.d.ts +1 -37
- package/dist/api/context.d.ts.map +1 -1
- package/dist/api/context.js +2 -143
- package/dist/api/context.js.map +1 -1
- package/dist/api/dead-code.d.ts +1 -2
- package/dist/api/dead-code.d.ts.map +1 -1
- package/dist/api/dead-code.js +2 -69
- package/dist/api/dead-code.js.map +1 -1
- package/dist/api/dependencies.d.ts +1 -2
- package/dist/api/dependencies.d.ts.map +1 -1
- package/dist/api/dependencies.js +2 -42
- package/dist/api/dependencies.js.map +1 -1
- package/dist/api/evaluation.d.ts +1 -19
- package/dist/api/evaluation.d.ts.map +1 -1
- package/dist/api/evaluation.js +2 -127
- package/dist/api/evaluation.js.map +1 -1
- package/dist/api/file-browser.d.ts +1 -8
- package/dist/api/file-browser.d.ts.map +1 -1
- package/dist/api/file-browser.js +2 -114
- package/dist/api/file-browser.js.map +1 -1
- package/dist/api/git.d.ts +1 -46
- package/dist/api/git.d.ts.map +1 -1
- package/dist/api/git.js +2 -354
- package/dist/api/git.js.map +1 -1
- package/dist/api/health-score.d.ts +1 -2
- package/dist/api/health-score.d.ts.map +1 -1
- package/dist/api/health-score.js +2 -46
- package/dist/api/health-score.js.map +1 -1
- package/dist/api/hook-request.d.ts +1 -40
- package/dist/api/hook-request.d.ts.map +1 -1
- package/dist/api/hook-request.js +2 -277
- package/dist/api/hook-request.js.map +1 -1
- package/dist/api/hotspots.d.ts +1 -2
- package/dist/api/hotspots.d.ts.map +1 -1
- package/dist/api/hotspots.js +2 -61
- package/dist/api/hotspots.js.map +1 -1
- package/dist/api/identity.d.ts +1 -16
- package/dist/api/identity.d.ts.map +1 -1
- package/dist/api/identity.js +2 -78
- package/dist/api/identity.js.map +1 -1
- package/dist/api/index.d.ts +1 -34
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -44
- package/dist/api/index.js.map +1 -1
- package/dist/api/mode.d.ts +1 -22
- package/dist/api/mode.d.ts.map +1 -1
- package/dist/api/mode.js +2 -37
- package/dist/api/mode.js.map +1 -1
- package/dist/api/otlp.d.ts +1 -2
- package/dist/api/otlp.d.ts.map +1 -1
- package/dist/api/otlp.js +2 -46
- package/dist/api/otlp.js.map +1 -1
- package/dist/api/permissions.d.ts +1 -15
- package/dist/api/permissions.d.ts.map +1 -1
- package/dist/api/permissions.js +2 -66
- package/dist/api/permissions.js.map +1 -1
- package/dist/api/persona.d.ts +1 -8
- package/dist/api/persona.d.ts.map +1 -1
- package/dist/api/persona.js +2 -67
- package/dist/api/persona.js.map +1 -1
- package/dist/api/portrait.d.ts +1 -5
- package/dist/api/portrait.d.ts.map +1 -1
- package/dist/api/portrait.js +2 -27
- package/dist/api/portrait.js.map +1 -1
- package/dist/api/settings.d.ts +1 -53
- package/dist/api/settings.d.ts.map +1 -1
- package/dist/api/settings.js +2 -464
- package/dist/api/settings.js.map +1 -1
- package/dist/api/spans.d.ts +1 -16
- package/dist/api/spans.d.ts.map +1 -1
- package/dist/api/spans.js +2 -244
- package/dist/api/spans.js.map +1 -1
- package/dist/api/stats.d.ts +1 -12
- package/dist/api/stats.d.ts.map +1 -1
- package/dist/api/stats.js +2 -84
- package/dist/api/stats.js.map +1 -1
- package/dist/api/story.d.ts +1 -2
- package/dist/api/story.d.ts.map +1 -1
- package/dist/api/story.js +2 -14
- package/dist/api/story.js.map +1 -1
- package/dist/api/telemetry.d.ts +1 -18
- package/dist/api/telemetry.d.ts.map +1 -1
- package/dist/api/telemetry.js +2 -164
- package/dist/api/telemetry.js.map +1 -1
- package/dist/api/theme-agents.d.ts +1 -60
- package/dist/api/theme-agents.d.ts.map +1 -1
- package/dist/api/theme-agents.js +2 -213
- package/dist/api/theme-agents.js.map +1 -1
- package/dist/api/todos.d.ts +1 -32
- package/dist/api/todos.d.ts.map +1 -1
- package/dist/api/todos.js +2 -43
- package/dist/api/todos.js.map +1 -1
- package/dist/api/token-stats.d.ts +1 -7
- package/dist/api/token-stats.d.ts.map +1 -1
- package/dist/api/token-stats.js +2 -35
- package/dist/api/token-stats.js.map +1 -1
- package/dist/api/welcome.d.ts +1 -21
- package/dist/api/welcome.d.ts.map +1 -1
- package/dist/api/welcome.js +2 -34
- package/dist/api/welcome.js.map +1 -1
- package/dist/env.d.ts +6 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +10 -0
- package/dist/env.js.map +1 -0
- package/dist/focus.d.ts +53 -0
- package/dist/focus.d.ts.map +1 -0
- package/dist/focus.js +122 -0
- package/dist/focus.js.map +1 -0
- package/dist/menu-builder.d.ts.map +1 -1
- package/dist/menu-builder.js +0 -1
- package/dist/menu-builder.js.map +1 -1
- package/dist/public/css/react.css +1 -1
- package/dist/public/js/react/react.js +51 -59
- package/dist/server.d.ts +16 -85
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +38 -409
- package/dist/server.js.map +1 -1
- package/dist/sprint-data.d.ts +1 -1
- package/dist/sprint-data.d.ts.map +1 -1
- package/dist/sprint-data.js +29 -8
- package/dist/sprint-data.js.map +1 -1
- package/dist/websocket.d.ts +2 -0
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +45 -78
- package/dist/websocket.js.map +1 -1
- package/package.json +33 -35
- package/portraits/hogans-heroes/large/burkhalter-35312.png +0 -0
- package/portraits/hogans-heroes/large/carter-34352.png +0 -0
- package/portraits/hogans-heroes/large/hochstetter-45314.png +0 -0
- package/portraits/hogans-heroes/large/hogan-44541.png +0 -0
- package/portraits/hogans-heroes/large/kinch-35241.png +0 -0
- package/portraits/hogans-heroes/large/klink-23434.png +0 -0
- package/portraits/hogans-heroes/large/lebeau-45443.png +0 -0
- package/portraits/hogans-heroes/large/marya-53543.png +0 -0
- package/portraits/hogans-heroes/large/newkirk-54432.png +0 -0
- package/portraits/hogans-heroes/large/schultz-42453.png +0 -0
- package/portraits/hogans-heroes/large/underground-55131.png +0 -0
- package/portraits/hogans-heroes/medium/burkhalter-35312.png +0 -0
- package/portraits/hogans-heroes/medium/carter-34352.png +0 -0
- package/portraits/hogans-heroes/medium/hochstetter-45314.png +0 -0
- package/portraits/hogans-heroes/medium/hogan-44541.png +0 -0
- package/portraits/hogans-heroes/medium/kinch-35241.png +0 -0
- package/portraits/hogans-heroes/medium/klink-23434.png +0 -0
- package/portraits/hogans-heroes/medium/lebeau-45443.png +0 -0
- package/portraits/hogans-heroes/medium/marya-53543.png +0 -0
- package/portraits/hogans-heroes/medium/newkirk-54432.png +0 -0
- package/portraits/hogans-heroes/medium/schultz-42453.png +0 -0
- package/portraits/hogans-heroes/medium/underground-55131.png +0 -0
- package/portraits/monty-python/large/announcer-44441.png +0 -0
- package/portraits/monty-python/large/arguer-35412.png +0 -0
- package/portraits/monty-python/large/bicycle-repair-man-35241.png +0 -0
- package/portraits/monty-python/large/colonel-35423.png +0 -0
- package/portraits/monty-python/large/counsellor-45341.png +0 -0
- package/portraits/monty-python/large/gumbys-23524.png +0 -0
- package/portraits/monty-python/large/nudge-43533.png +0 -0
- package/portraits/monty-python/large/praline-45413.png +0 -0
- package/portraits/monty-python/large/silly-walks-55322.png +0 -0
- package/portraits/monty-python/large/wensleydale-54451.png +0 -0
- package/portraits/monty-python/large/xim-nez-43534.png +0 -0
- package/portraits/monty-python/medium/announcer-44441.png +0 -0
- package/portraits/monty-python/medium/arguer-35412.png +0 -0
- package/portraits/monty-python/medium/bicycle-repair-man-35241.png +0 -0
- package/portraits/monty-python/medium/colonel-35423.png +0 -0
- package/portraits/monty-python/medium/counsellor-45341.png +0 -0
- package/portraits/monty-python/medium/gumbys-23524.png +0 -0
- package/portraits/monty-python/medium/nudge-43533.png +0 -0
- package/portraits/monty-python/medium/praline-45413.png +0 -0
- package/portraits/monty-python/medium/silly-walks-55322.png +0 -0
- package/portraits/monty-python/medium/wensleydale-54451.png +0 -0
- package/portraits/monty-python/medium/xim-nez-43534.png +0 -0
- package/portraits/stephen-king/large/andy-55231.png +0 -0
- package/portraits/stephen-king/large/christine-25112.png +0 -0
- package/portraits/stephen-king/large/danny-53243.png +0 -0
- package/portraits/stephen-king/large/flagg-55311.png +0 -0
- package/portraits/stephen-king/large/gaunt-54421.png +0 -0
- package/portraits/stephen-king/large/jack-44224.png +0 -0
- package/portraits/stephen-king/large/johnny-44353.png +0 -0
- package/portraits/stephen-king/large/margaret-15415.png +0 -0
- package/portraits/stephen-king/large/paul-45233.png +0 -0
- package/portraits/stephen-king/large/pennywise-54411.png +0 -0
- package/portraits/stephen-king/large/roland-35121.png +0 -0
- package/portraits/stephen-king/medium/andy-55231.png +0 -0
- package/portraits/stephen-king/medium/christine-25112.png +0 -0
- package/portraits/stephen-king/medium/danny-53243.png +0 -0
- package/portraits/stephen-king/medium/flagg-55311.png +0 -0
- package/portraits/stephen-king/medium/gaunt-54421.png +0 -0
- package/portraits/stephen-king/medium/jack-44224.png +0 -0
- package/portraits/stephen-king/medium/johnny-44353.png +0 -0
- package/portraits/stephen-king/medium/margaret-15415.png +0 -0
- package/portraits/stephen-king/medium/paul-45233.png +0 -0
- package/portraits/stephen-king/medium/pennywise-54411.png +0 -0
- package/portraits/stephen-king/medium/roland-35121.png +0 -0
- package/src/public/App.tsx +21 -5
- package/src/public/components/BikeRackIndex.tsx +0 -1
- package/src/public/components/BikeRackWorkspace.tsx +86 -11
- package/src/public/components/DockviewWorkspace.tsx +19 -8
- package/src/public/components/StandalonePanel.tsx +1 -3
- package/src/public/components/panel-registry.ts +3 -1
- package/src/public/components/panels/AuditLogPanel.tsx +28 -4
- package/src/public/components/panels/GitPanel.tsx +1 -20
- package/src/public/components/panels/SettingsPanel.tsx +0 -1
- package/src/public/components/panels/SprintPanel.tsx +32 -1
- package/src/public/components/panels/index.ts +0 -2
- package/src/public/hooks/useFocusPanel.ts +137 -0
- package/src/public/hooks/useLayoutPersistence.ts +8 -5
- package/src/public/styles/dockview-theme.css +1 -84
- package/src/public/styles/tailwind.css +27 -32
- package/src/public/utils/slash-commands.ts +122 -98
- package/LICENSE +0 -14
- package/dist/hooks/cyclist-pretooluse-hook.d.ts +0 -60
- package/dist/hooks/cyclist-pretooluse-hook.d.ts.map +0 -1
- package/dist/hooks/cyclist-pretooluse-hook.js +0 -57
- package/dist/hooks/cyclist-pretooluse-hook.js.map +0 -1
- package/dist/hooks/pretooluse-hook.d.ts +0 -89
- package/dist/hooks/pretooluse-hook.d.ts.map +0 -1
- package/dist/hooks/pretooluse-hook.js +0 -235
- package/dist/hooks/pretooluse-hook.js.map +0 -1
- package/dist/notification-sound.d.ts +0 -59
- package/dist/notification-sound.d.ts.map +0 -1
- package/dist/notification-sound.js +0 -219
- package/dist/notification-sound.js.map +0 -1
- package/dist/plugin-loader.test.d.ts +0 -17
- package/dist/plugin-loader.test.d.ts.map +0 -1
- package/dist/plugin-loader.test.js +0 -407
- package/dist/plugin-loader.test.js.map +0 -1
- package/portraits/star-trek-tng/large/beverly-44352.png +0 -0
- package/portraits/star-trek-tng/large/data-55241.png +0 -0
- package/portraits/star-trek-tng/large/deanna-43353.png +0 -0
- package/portraits/star-trek-tng/large/geordi-54342.png +0 -0
- package/portraits/star-trek-tng/large/jean-luc-45342.png +0 -0
- package/portraits/star-trek-tng/large/kathryn-45332.png +0 -0
- package/portraits/star-trek-tng/large/miles-35342.png +0 -0
- package/portraits/star-trek-tng/large/q-53521.png +0 -0
- package/portraits/star-trek-tng/large/spock-45231.png +0 -0
- package/portraits/star-trek-tng/large/troi-44352.png +0 -0
- package/portraits/star-trek-tng/medium/beverly-44352.png +0 -0
- package/portraits/star-trek-tng/medium/data-55241.png +0 -0
- package/portraits/star-trek-tng/medium/deanna-43353.png +0 -0
- package/portraits/star-trek-tng/medium/geordi-54342.png +0 -0
- package/portraits/star-trek-tng/medium/jean-luc-45342.png +0 -0
- package/portraits/star-trek-tng/medium/kathryn-45332.png +0 -0
- package/portraits/star-trek-tng/medium/miles-35342.png +0 -0
- package/portraits/star-trek-tng/medium/q-53521.png +0 -0
- package/portraits/star-trek-tng/medium/spock-45231.png +0 -0
- package/portraits/star-trek-tng/medium/troi-44352.png +0 -0
- package/src/public/components/panels/TTYPanel.tsx +0 -299
- package/src/public/types/electron.d.ts +0 -18
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cyclist PreToolUse Hook - Core Logic
|
|
3
|
-
*
|
|
4
|
-
* Testable module containing all hook logic. The companion runner script
|
|
5
|
-
* (cyclist-pretooluse-hook.js) imports from the compiled output and invokes main().
|
|
6
|
-
*
|
|
7
|
-
* Flow:
|
|
8
|
-
* 1. Claude Code calls the runner script with tool info via stdin (JSON)
|
|
9
|
-
* 2. Script reads port from .cyclist-port in project directory
|
|
10
|
-
* 3. Script sends request to WheelHub's /api/hook-request endpoint
|
|
11
|
-
* 4. WheelHub checks grants / shows approval modal, user decides
|
|
12
|
-
* 5. Script receives response, outputs JSON decision to stdout
|
|
13
|
-
* 6. Claude Code proceeds or blocks based on decision
|
|
14
|
-
*
|
|
15
|
-
* Per ADR-0004: All communication converges through WheelHub.
|
|
16
|
-
*
|
|
17
|
-
* Story: MSSCI-14320 - Update and register PreToolUse hook
|
|
18
|
-
*/
|
|
19
|
-
import http from 'node:http';
|
|
20
|
-
import fs from 'node:fs';
|
|
21
|
-
import path from 'node:path';
|
|
22
|
-
// =============================================================================
|
|
23
|
-
// Constants
|
|
24
|
-
// =============================================================================
|
|
25
|
-
export const DEFAULT_PORT = 7432;
|
|
26
|
-
export const HOST = '127.0.0.1';
|
|
27
|
-
export const TIMEOUT_MS = 120_000; // 2 minutes for user to decide
|
|
28
|
-
export const PORT_FILE = '.cyclist-port';
|
|
29
|
-
export const LEGACY_PORT_FILE = '.cyclist-approval-port';
|
|
30
|
-
export const ENDPOINT = '/api/hook-request';
|
|
31
|
-
// =============================================================================
|
|
32
|
-
// Project Root Detection
|
|
33
|
-
// =============================================================================
|
|
34
|
-
/**
|
|
35
|
-
* Find the project root by looking for port files or .claude directory.
|
|
36
|
-
* Walks up from startDir (defaults to cwd) until found or reaches filesystem root.
|
|
37
|
-
*/
|
|
38
|
-
export function findProjectRoot(startDir) {
|
|
39
|
-
let dir = startDir ?? process.cwd();
|
|
40
|
-
const root = path.parse(dir).root;
|
|
41
|
-
while (dir !== root) {
|
|
42
|
-
if (fs.existsSync(path.join(dir, PORT_FILE))) {
|
|
43
|
-
return dir;
|
|
44
|
-
}
|
|
45
|
-
if (fs.existsSync(path.join(dir, LEGACY_PORT_FILE))) {
|
|
46
|
-
return dir;
|
|
47
|
-
}
|
|
48
|
-
if (fs.existsSync(path.join(dir, '.claude'))) {
|
|
49
|
-
return dir;
|
|
50
|
-
}
|
|
51
|
-
dir = path.dirname(dir);
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
// =============================================================================
|
|
56
|
-
// Port Discovery
|
|
57
|
-
// =============================================================================
|
|
58
|
-
/**
|
|
59
|
-
* Read the WheelHub server port. Prefers .cyclist-port, falls back to
|
|
60
|
-
* .cyclist-approval-port for migration compatibility, then default port.
|
|
61
|
-
*
|
|
62
|
-
* Mirrors hooks.py:get_cyclist_port() behavior.
|
|
63
|
-
*/
|
|
64
|
-
export function getPort(projectRoot) {
|
|
65
|
-
const root = projectRoot ?? findProjectRoot();
|
|
66
|
-
if (!root) {
|
|
67
|
-
return DEFAULT_PORT;
|
|
68
|
-
}
|
|
69
|
-
// Try canonical port file first
|
|
70
|
-
const port = readPortFile(path.join(root, PORT_FILE));
|
|
71
|
-
if (port !== null) {
|
|
72
|
-
return port;
|
|
73
|
-
}
|
|
74
|
-
// Fall back to legacy port file
|
|
75
|
-
const legacyPort = readPortFile(path.join(root, LEGACY_PORT_FILE));
|
|
76
|
-
if (legacyPort !== null) {
|
|
77
|
-
return legacyPort;
|
|
78
|
-
}
|
|
79
|
-
return DEFAULT_PORT;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Read a port number from a file. Returns null if file doesn't exist or is invalid.
|
|
83
|
-
*/
|
|
84
|
-
export function readPortFile(filePath) {
|
|
85
|
-
if (!fs.existsSync(filePath)) {
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
try {
|
|
89
|
-
const content = fs.readFileSync(filePath, 'utf-8').trim();
|
|
90
|
-
const port = parseInt(content, 10);
|
|
91
|
-
if (!isNaN(port) && port > 0 && port < 65536) {
|
|
92
|
-
return port;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
// Fall through
|
|
97
|
-
}
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
// =============================================================================
|
|
101
|
-
// Stdin Reading
|
|
102
|
-
// =============================================================================
|
|
103
|
-
/**
|
|
104
|
-
* Read all stdin as JSON.
|
|
105
|
-
*/
|
|
106
|
-
export async function readStdin() {
|
|
107
|
-
return new Promise((resolve, reject) => {
|
|
108
|
-
let data = '';
|
|
109
|
-
process.stdin.setEncoding('utf8');
|
|
110
|
-
process.stdin.on('data', (chunk) => { data += chunk; });
|
|
111
|
-
process.stdin.on('end', () => {
|
|
112
|
-
try {
|
|
113
|
-
resolve(JSON.parse(data));
|
|
114
|
-
}
|
|
115
|
-
catch (e) {
|
|
116
|
-
reject(new Error(`Invalid JSON input: ${e.message}`));
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
process.stdin.on('error', reject);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
// =============================================================================
|
|
123
|
-
// HTTP Request
|
|
124
|
-
// =============================================================================
|
|
125
|
-
/**
|
|
126
|
-
* Send approval request to WheelHub and wait for response.
|
|
127
|
-
* Returns { decision: "ask" } when WheelHub is unreachable (ECONNREFUSED).
|
|
128
|
-
*/
|
|
129
|
-
export async function requestApproval(toolData, port) {
|
|
130
|
-
const resolvedPort = port ?? getPort();
|
|
131
|
-
const postData = JSON.stringify(toolData);
|
|
132
|
-
return new Promise((resolve, reject) => {
|
|
133
|
-
const options = {
|
|
134
|
-
hostname: HOST,
|
|
135
|
-
port: resolvedPort,
|
|
136
|
-
path: ENDPOINT,
|
|
137
|
-
method: 'POST',
|
|
138
|
-
headers: {
|
|
139
|
-
'Content-Type': 'application/json',
|
|
140
|
-
'Content-Length': Buffer.byteLength(postData),
|
|
141
|
-
},
|
|
142
|
-
timeout: TIMEOUT_MS,
|
|
143
|
-
};
|
|
144
|
-
const req = http.request(options, (res) => {
|
|
145
|
-
let data = '';
|
|
146
|
-
res.on('data', (chunk) => { data += chunk; });
|
|
147
|
-
res.on('end', () => {
|
|
148
|
-
try {
|
|
149
|
-
resolve(JSON.parse(data));
|
|
150
|
-
}
|
|
151
|
-
catch (e) {
|
|
152
|
-
reject(new Error(`Invalid response from WheelHub: ${e.message}`));
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
req.on('error', (e) => {
|
|
157
|
-
if (e.code === 'ECONNREFUSED') {
|
|
158
|
-
resolve({ decision: 'ask', reason: 'WheelHub not running, deferring to Claude Code' });
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
reject(e);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
req.on('timeout', () => {
|
|
165
|
-
req.destroy();
|
|
166
|
-
reject(new Error('Approval request timed out'));
|
|
167
|
-
});
|
|
168
|
-
req.write(postData);
|
|
169
|
-
req.end();
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
// =============================================================================
|
|
173
|
-
// Output Formatting
|
|
174
|
-
// =============================================================================
|
|
175
|
-
/**
|
|
176
|
-
* Build the Claude Code hook output object.
|
|
177
|
-
* MSSCI-11947: Supports updatedInput passthrough for interactive tools.
|
|
178
|
-
*/
|
|
179
|
-
export function buildOutput(decision, reason, updatedInput) {
|
|
180
|
-
const output = {
|
|
181
|
-
hookSpecificOutput: {
|
|
182
|
-
hookEventName: 'PreToolUse',
|
|
183
|
-
permissionDecision: decision,
|
|
184
|
-
permissionDecisionReason: reason,
|
|
185
|
-
},
|
|
186
|
-
};
|
|
187
|
-
if (updatedInput) {
|
|
188
|
-
output.hookSpecificOutput.updatedInput = updatedInput;
|
|
189
|
-
}
|
|
190
|
-
return output;
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Output decision to stdout in Claude Code hook format.
|
|
194
|
-
*/
|
|
195
|
-
export function outputDecision(decision, reason, updatedInput) {
|
|
196
|
-
console.log(JSON.stringify(buildOutput(decision, reason, updatedInput)));
|
|
197
|
-
}
|
|
198
|
-
// =============================================================================
|
|
199
|
-
// Main Entry Point
|
|
200
|
-
// =============================================================================
|
|
201
|
-
/**
|
|
202
|
-
* Main hook logic. Reads stdin, sends to WheelHub, outputs decision.
|
|
203
|
-
* MSSCI-11947: Handles data field passthrough for interactive tools.
|
|
204
|
-
*/
|
|
205
|
-
export async function main() {
|
|
206
|
-
try {
|
|
207
|
-
const toolData = await readStdin();
|
|
208
|
-
const { tool_name, tool_input, tool_use_id, session_id } = toolData;
|
|
209
|
-
const response = await requestApproval({
|
|
210
|
-
toolName: tool_name,
|
|
211
|
-
toolId: tool_use_id,
|
|
212
|
-
input: tool_input,
|
|
213
|
-
sessionId: session_id,
|
|
214
|
-
});
|
|
215
|
-
if (response.decision === 'allow') {
|
|
216
|
-
outputDecision('allow', response.reason || 'Approved by user', response.data || null);
|
|
217
|
-
}
|
|
218
|
-
else if (response.decision === 'deny') {
|
|
219
|
-
outputDecision('deny', response.reason || 'Rejected by user', response.data || null);
|
|
220
|
-
}
|
|
221
|
-
else if (response.decision === 'ask') {
|
|
222
|
-
outputDecision('ask', response.reason || 'Deferred to Claude Code');
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
// Unknown decision - defer to Claude Code rather than silently allowing
|
|
226
|
-
outputDecision('ask', 'Unknown response from WheelHub');
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
catch (error) {
|
|
230
|
-
console.error(`[cyclist-hook] Error: ${error.message}`);
|
|
231
|
-
// Output ask so Claude Code shows its built-in dialog, don't silently allow
|
|
232
|
-
outputDecision('ask', 'Hook error, deferring to Claude Code');
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
//# sourceMappingURL=pretooluse-hook.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pretooluse-hook.js","sourceRoot":"","sources":["../../src/hooks/pretooluse-hook.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Notification Sound Service (Story MSSCI-14191)
|
|
3
|
-
*
|
|
4
|
-
* Provides audio notifications for workflow events using Web Audio API.
|
|
5
|
-
* Different tones for different event types:
|
|
6
|
-
* - HANDOFF: Low tone (agent transition)
|
|
7
|
-
* - QUESTION: Higher tone (user input needed)
|
|
8
|
-
* - COMPLETION: Chord (workflow complete)
|
|
9
|
-
*
|
|
10
|
-
* State is managed in-memory but can sync with settings via loadNotificationSoundSetting().
|
|
11
|
-
*/
|
|
12
|
-
/**
|
|
13
|
-
* Notification event types that trigger sounds
|
|
14
|
-
*/
|
|
15
|
-
export declare const NotificationEvent: {
|
|
16
|
-
readonly HANDOFF: "handoff";
|
|
17
|
-
readonly QUESTION: "question";
|
|
18
|
-
readonly COMPLETION: "completion";
|
|
19
|
-
};
|
|
20
|
-
export type NotificationEventType = (typeof NotificationEvent)[keyof typeof NotificationEvent];
|
|
21
|
-
/**
|
|
22
|
-
* Service class for notification sounds (exported for type checking)
|
|
23
|
-
*/
|
|
24
|
-
export declare class NotificationSound {
|
|
25
|
-
static isEnabled(): boolean;
|
|
26
|
-
static setEnabled(enabled: boolean): void;
|
|
27
|
-
static play(event: NotificationEventType): Promise<void>;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Check if notification sounds are enabled
|
|
31
|
-
*/
|
|
32
|
-
export declare function isNotificationSoundEnabled(): boolean;
|
|
33
|
-
/**
|
|
34
|
-
* Enable or disable notification sounds
|
|
35
|
-
*/
|
|
36
|
-
export declare function setNotificationSoundEnabled(enabled: boolean): void;
|
|
37
|
-
/**
|
|
38
|
-
* Load notification sound setting from REST API
|
|
39
|
-
*/
|
|
40
|
-
export declare function loadNotificationSoundSetting(): Promise<void>;
|
|
41
|
-
/**
|
|
42
|
-
* Check if audio playback is permitted (handles autoplay restrictions)
|
|
43
|
-
* Returns true if audio can play, false if user gesture is required
|
|
44
|
-
*/
|
|
45
|
-
export declare function checkAudioPermission(): Promise<boolean>;
|
|
46
|
-
/**
|
|
47
|
-
* Play a notification sound for the given event type
|
|
48
|
-
*/
|
|
49
|
-
export declare function playNotificationSound(event: NotificationEventType): Promise<void>;
|
|
50
|
-
/**
|
|
51
|
-
* Reset state (for testing)
|
|
52
|
-
*/
|
|
53
|
-
export declare function resetNotificationSoundState(): void;
|
|
54
|
-
/**
|
|
55
|
-
* Inject a custom AudioContext class (for testing)
|
|
56
|
-
* Pass null to use the default detection
|
|
57
|
-
*/
|
|
58
|
-
export declare function setAudioContextClass(cls: (new () => AudioContext) | null): void;
|
|
59
|
-
//# sourceMappingURL=notification-sound.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"notification-sound.d.ts","sourceRoot":"","sources":["../src/notification-sound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;CAIpB,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;AA0B/F;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,MAAM,CAAC,SAAS,IAAI,OAAO;IAI3B,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;WAI5B,IAAI,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;CAG/D;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAElE;AAED;;GAEG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,IAAI,CAAC,CAUlE;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC,CAa7D;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BvF;AA6ED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,IAAI,CAUlD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,CAAC,UAAU,YAAY,CAAC,GAAG,IAAI,GAAG,IAAI,CAI/E"}
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Notification Sound Service (Story MSSCI-14191)
|
|
3
|
-
*
|
|
4
|
-
* Provides audio notifications for workflow events using Web Audio API.
|
|
5
|
-
* Different tones for different event types:
|
|
6
|
-
* - HANDOFF: Low tone (agent transition)
|
|
7
|
-
* - QUESTION: Higher tone (user input needed)
|
|
8
|
-
* - COMPLETION: Chord (workflow complete)
|
|
9
|
-
*
|
|
10
|
-
* State is managed in-memory but can sync with settings via loadNotificationSoundSetting().
|
|
11
|
-
*/
|
|
12
|
-
// =============================================================================
|
|
13
|
-
// Types
|
|
14
|
-
// =============================================================================
|
|
15
|
-
/**
|
|
16
|
-
* Notification event types that trigger sounds
|
|
17
|
-
*/
|
|
18
|
-
export const NotificationEvent = {
|
|
19
|
-
HANDOFF: 'handoff', // Agent handoff / phase transition
|
|
20
|
-
QUESTION: 'question', // User input required
|
|
21
|
-
COMPLETION: 'completion', // Workflow completion
|
|
22
|
-
};
|
|
23
|
-
/**
|
|
24
|
-
* Frequency configurations for each event type (in Hz)
|
|
25
|
-
*/
|
|
26
|
-
const EVENT_FREQUENCIES = {
|
|
27
|
-
[NotificationEvent.HANDOFF]: [330], // E4 - single low tone
|
|
28
|
-
[NotificationEvent.QUESTION]: [523], // C5 - higher, attention-grabbing
|
|
29
|
-
[NotificationEvent.COMPLETION]: [523, 659, 784], // C5-E5-G5 major chord
|
|
30
|
-
};
|
|
31
|
-
// =============================================================================
|
|
32
|
-
// State
|
|
33
|
-
// =============================================================================
|
|
34
|
-
let soundEnabled = false;
|
|
35
|
-
let audioContext = null;
|
|
36
|
-
// For testing: injectable AudioContext constructor
|
|
37
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
-
let _audioContextClass = null;
|
|
39
|
-
// =============================================================================
|
|
40
|
-
// Public API
|
|
41
|
-
// =============================================================================
|
|
42
|
-
/**
|
|
43
|
-
* Service class for notification sounds (exported for type checking)
|
|
44
|
-
*/
|
|
45
|
-
export class NotificationSound {
|
|
46
|
-
static isEnabled() {
|
|
47
|
-
return isNotificationSoundEnabled();
|
|
48
|
-
}
|
|
49
|
-
static setEnabled(enabled) {
|
|
50
|
-
setNotificationSoundEnabled(enabled);
|
|
51
|
-
}
|
|
52
|
-
static async play(event) {
|
|
53
|
-
return playNotificationSound(event);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Check if notification sounds are enabled
|
|
58
|
-
*/
|
|
59
|
-
export function isNotificationSoundEnabled() {
|
|
60
|
-
return soundEnabled;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Enable or disable notification sounds
|
|
64
|
-
*/
|
|
65
|
-
export function setNotificationSoundEnabled(enabled) {
|
|
66
|
-
soundEnabled = enabled;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Load notification sound setting from REST API
|
|
70
|
-
*/
|
|
71
|
-
export async function loadNotificationSoundSetting() {
|
|
72
|
-
try {
|
|
73
|
-
const response = await fetch('/api/settings');
|
|
74
|
-
if (response.ok) {
|
|
75
|
-
const settings = await response.json();
|
|
76
|
-
soundEnabled = settings?.notifications?.sound ?? false;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
// Silently fail - keep current state
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Check if audio playback is permitted (handles autoplay restrictions)
|
|
85
|
-
* Returns true if audio can play, false if user gesture is required
|
|
86
|
-
*/
|
|
87
|
-
export async function checkAudioPermission() {
|
|
88
|
-
try {
|
|
89
|
-
const ctx = getOrCreateAudioContext();
|
|
90
|
-
if (!ctx)
|
|
91
|
-
return false;
|
|
92
|
-
if (ctx.state === 'suspended') {
|
|
93
|
-
await ctx.resume();
|
|
94
|
-
}
|
|
95
|
-
return ctx.state === 'running';
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Play a notification sound for the given event type
|
|
103
|
-
*/
|
|
104
|
-
export async function playNotificationSound(event) {
|
|
105
|
-
// Exit early if disabled
|
|
106
|
-
if (!soundEnabled) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
try {
|
|
110
|
-
const ctx = getOrCreateAudioContext();
|
|
111
|
-
if (!ctx) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
// Handle suspended state (autoplay restrictions)
|
|
115
|
-
if (ctx.state === 'suspended') {
|
|
116
|
-
await ctx.resume();
|
|
117
|
-
}
|
|
118
|
-
const frequencies = EVENT_FREQUENCIES[event] || EVENT_FREQUENCIES[NotificationEvent.HANDOFF];
|
|
119
|
-
// Play each frequency in the pattern
|
|
120
|
-
for (let i = 0; i < frequencies.length; i++) {
|
|
121
|
-
const freq = frequencies[i];
|
|
122
|
-
const startTime = ctx.currentTime + i * 0.1; // Slight offset for chord/arpeggio effect
|
|
123
|
-
playTone(ctx, freq, startTime, 0.2);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
catch {
|
|
127
|
-
// Silently fail - audio is optional
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
// =============================================================================
|
|
131
|
-
// Internal Helpers
|
|
132
|
-
// =============================================================================
|
|
133
|
-
/**
|
|
134
|
-
* Get the AudioContext constructor (allows for mocking in tests)
|
|
135
|
-
* Checks multiple locations to support different environments and test mocking
|
|
136
|
-
*/
|
|
137
|
-
function getAudioContextClass() {
|
|
138
|
-
// Check if a test-injected class is set
|
|
139
|
-
if (_audioContextClass !== null) {
|
|
140
|
-
return _audioContextClass;
|
|
141
|
-
}
|
|
142
|
-
// Check various locations where AudioContext might be defined
|
|
143
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
144
|
-
const g = globalThis;
|
|
145
|
-
if (g.AudioContext) {
|
|
146
|
-
return g.AudioContext;
|
|
147
|
-
}
|
|
148
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
149
|
-
if (typeof window !== 'undefined' && window.AudioContext) {
|
|
150
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
151
|
-
return window.AudioContext;
|
|
152
|
-
}
|
|
153
|
-
// webkitAudioContext for Safari
|
|
154
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
155
|
-
if (typeof window !== 'undefined' && window.webkitAudioContext) {
|
|
156
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
157
|
-
return window.webkitAudioContext;
|
|
158
|
-
}
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Get or create the shared AudioContext
|
|
163
|
-
*/
|
|
164
|
-
function getOrCreateAudioContext() {
|
|
165
|
-
const AudioContextClass = getAudioContextClass();
|
|
166
|
-
if (!AudioContextClass) {
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
if (!audioContext) {
|
|
170
|
-
try {
|
|
171
|
-
audioContext = new AudioContextClass();
|
|
172
|
-
}
|
|
173
|
-
catch {
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return audioContext;
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Play a single tone using Web Audio API
|
|
181
|
-
*/
|
|
182
|
-
function playTone(ctx, frequency, startTime, duration) {
|
|
183
|
-
const oscillator = ctx.createOscillator();
|
|
184
|
-
const gainNode = ctx.createGain();
|
|
185
|
-
oscillator.connect(gainNode);
|
|
186
|
-
gainNode.connect(ctx.destination);
|
|
187
|
-
oscillator.type = 'sine';
|
|
188
|
-
oscillator.frequency.setValueAtTime(frequency, startTime);
|
|
189
|
-
// Envelope: quick attack, gradual decay
|
|
190
|
-
gainNode.gain.setValueAtTime(0.3, startTime);
|
|
191
|
-
gainNode.gain.exponentialRampToValueAtTime(0.01, startTime + duration);
|
|
192
|
-
oscillator.start(startTime);
|
|
193
|
-
oscillator.stop(startTime + duration);
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Reset state (for testing)
|
|
197
|
-
*/
|
|
198
|
-
export function resetNotificationSoundState() {
|
|
199
|
-
soundEnabled = false;
|
|
200
|
-
if (audioContext) {
|
|
201
|
-
try {
|
|
202
|
-
audioContext.close().catch(() => { });
|
|
203
|
-
}
|
|
204
|
-
catch {
|
|
205
|
-
// Ignore errors closing mock audio context
|
|
206
|
-
}
|
|
207
|
-
audioContext = null;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Inject a custom AudioContext class (for testing)
|
|
212
|
-
* Pass null to use the default detection
|
|
213
|
-
*/
|
|
214
|
-
export function setAudioContextClass(cls) {
|
|
215
|
-
_audioContextClass = cls;
|
|
216
|
-
// Reset the existing context so the new class is used
|
|
217
|
-
audioContext = null;
|
|
218
|
-
}
|
|
219
|
-
//# sourceMappingURL=notification-sound.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"notification-sound.js","sourceRoot":"","sources":["../src/notification-sound.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Story 93-6: Plugin Router Loader for Cyclist
|
|
3
|
-
*
|
|
4
|
-
* Tests the dynamic discovery and mounting of API routers from installed
|
|
5
|
-
* @pennyfarthing/* plugin packages. The loader uses the plugin discovery
|
|
6
|
-
* system (93-3) to find plugins and mount their Express routers.
|
|
7
|
-
*
|
|
8
|
-
* Test categories:
|
|
9
|
-
* 1. initPluginRouters() - Core loading behavior
|
|
10
|
-
* 2. Plugin with API router - Benchmark plugin integration
|
|
11
|
-
* 3. Graceful degradation - Missing packages, failed imports
|
|
12
|
-
* 4. Cyclist starts without plugins - Empty state
|
|
13
|
-
*
|
|
14
|
-
* Run with: cd packages/cyclist && pnpm test
|
|
15
|
-
*/
|
|
16
|
-
export {};
|
|
17
|
-
//# sourceMappingURL=plugin-loader.test.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-loader.test.d.ts","sourceRoot":"","sources":["../src/plugin-loader.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|