@pennyfarthing/cyclist 9.3.0 → 10.0.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.
Files changed (101) hide show
  1. package/dist/api/hook-request.d.ts +11 -0
  2. package/dist/api/hook-request.d.ts.map +1 -1
  3. package/dist/api/hook-request.js +126 -28
  4. package/dist/api/hook-request.js.map +1 -1
  5. package/dist/api/hotspots.d.ts +3 -0
  6. package/dist/api/hotspots.d.ts.map +1 -0
  7. package/dist/api/hotspots.js +54 -0
  8. package/dist/api/hotspots.js.map +1 -0
  9. package/dist/api/index.d.ts +2 -0
  10. package/dist/api/index.d.ts.map +1 -1
  11. package/dist/api/index.js +3 -0
  12. package/dist/api/index.js.map +1 -1
  13. package/dist/api/permissions.d.ts +16 -0
  14. package/dist/api/permissions.d.ts.map +1 -0
  15. package/dist/api/permissions.js +67 -0
  16. package/dist/api/permissions.js.map +1 -0
  17. package/dist/api/settings.d.ts +1 -1
  18. package/dist/api/settings.d.ts.map +1 -1
  19. package/dist/api/settings.js +44 -17
  20. package/dist/api/settings.js.map +1 -1
  21. package/dist/api/theme-agents.d.ts +4 -0
  22. package/dist/api/theme-agents.d.ts.map +1 -1
  23. package/dist/api/theme-agents.js +3 -0
  24. package/dist/api/theme-agents.js.map +1 -1
  25. package/dist/approval-gate.d.ts +3 -75
  26. package/dist/approval-gate.d.ts.map +1 -1
  27. package/dist/approval-gate.js +4 -121
  28. package/dist/approval-gate.js.map +1 -1
  29. package/dist/hooks/cyclist-pretooluse-hook.d.ts +60 -0
  30. package/dist/hooks/cyclist-pretooluse-hook.d.ts.map +1 -0
  31. package/dist/hooks/cyclist-pretooluse-hook.js +57 -0
  32. package/dist/hooks/cyclist-pretooluse-hook.js.map +1 -0
  33. package/dist/hooks/pretooluse-hook.d.ts +89 -0
  34. package/dist/hooks/pretooluse-hook.d.ts.map +1 -0
  35. package/dist/hooks/pretooluse-hook.js +235 -0
  36. package/dist/hooks/pretooluse-hook.js.map +1 -0
  37. package/dist/main.d.ts +1 -134
  38. package/dist/main.d.ts.map +1 -1
  39. package/dist/main.js +42 -373
  40. package/dist/main.js.map +1 -1
  41. package/dist/menu-builder.d.ts +7 -1
  42. package/dist/menu-builder.d.ts.map +1 -1
  43. package/dist/menu-builder.js +36 -1
  44. package/dist/menu-builder.js.map +1 -1
  45. package/dist/otlp-receiver.d.ts.map +1 -1
  46. package/dist/otlp-receiver.js +6 -0
  47. package/dist/otlp-receiver.js.map +1 -1
  48. package/dist/public/css/react.css +1 -1
  49. package/dist/public/js/react/react.js +42 -42
  50. package/dist/server.d.ts.map +1 -1
  51. package/dist/server.js +16 -3
  52. package/dist/server.js.map +1 -1
  53. package/dist/settings-store.d.ts +3 -1
  54. package/dist/settings-store.d.ts.map +1 -1
  55. package/dist/settings-store.js +18 -9
  56. package/dist/settings-store.js.map +1 -1
  57. package/dist/story-parser.d.ts +17 -0
  58. package/dist/story-parser.d.ts.map +1 -1
  59. package/dist/story-parser.js +183 -13
  60. package/dist/story-parser.js.map +1 -1
  61. package/dist/websocket.d.ts +1 -0
  62. package/dist/websocket.d.ts.map +1 -1
  63. package/dist/websocket.js +48 -5
  64. package/dist/websocket.js.map +1 -1
  65. package/dist/workflow-presets.d.ts +72 -0
  66. package/dist/workflow-presets.d.ts.map +1 -0
  67. package/dist/workflow-presets.js +93 -0
  68. package/dist/workflow-presets.js.map +1 -0
  69. package/package.json +2 -2
  70. package/src/public/App.tsx +61 -1
  71. package/src/public/components/ApprovalModal/index.tsx +31 -1
  72. package/src/public/components/ControlBar.tsx +19 -20
  73. package/src/public/components/DockviewWorkspace.tsx +39 -5
  74. package/src/public/components/FontPicker/index.tsx +118 -33
  75. package/src/public/components/FullFileTree.tsx +223 -0
  76. package/src/public/components/Message.tsx +89 -11
  77. package/src/public/components/MessageView.tsx +206 -93
  78. package/src/public/components/PersonaHeader.tsx +47 -15
  79. package/src/public/components/SubagentSpan.tsx +15 -8
  80. package/src/public/components/panels/BackgroundPanel.tsx +1 -1
  81. package/src/public/components/panels/ChangedPanel.tsx +30 -44
  82. package/src/public/components/panels/HotspotsPanel.tsx +365 -0
  83. package/src/public/components/panels/MessagePanel.tsx +79 -5
  84. package/src/public/components/panels/SettingsPanel.tsx +3 -28
  85. package/src/public/components/panels/WorkflowPanel.tsx +108 -13
  86. package/src/public/components/panels/index.ts +1 -0
  87. package/src/public/contexts/ClaudeContext.tsx +16 -1
  88. package/src/public/css/theme-system.css +46 -38
  89. package/src/public/hooks/useColorScheme.ts +27 -0
  90. package/src/public/hooks/useFileBrowser.ts +71 -0
  91. package/src/public/hooks/useHotspots.ts +113 -0
  92. package/src/public/hooks/usePlanModeExit.ts +105 -0
  93. package/src/public/hooks/useStory.ts +12 -3
  94. package/src/public/images/cyclist-dark.png +0 -0
  95. package/src/public/images/cyclist-light.png +0 -0
  96. package/src/public/styles/dockview-theme.css +31 -33
  97. package/src/public/styles/tailwind.css +417 -58
  98. package/src/public/types/message.ts +6 -1
  99. package/src/public/utils/markdown.ts +2 -2
  100. package/src/public/utils/slash-commands.ts +1 -1
  101. package/src/public/utils/toolStackGrouper.ts +5 -6
@@ -16,6 +16,16 @@
16
16
  */
17
17
  import { Router } from 'express';
18
18
  import { WebSocket } from 'ws';
19
+ export type HookSeverity = 'safe' | 'normal' | 'destructive';
20
+ interface SeverityResult {
21
+ severity: HookSeverity;
22
+ warning?: string;
23
+ }
24
+ /**
25
+ * Classify the severity of a hook request server-side.
26
+ * Combines tool-name classification with dangerous-path detection.
27
+ */
28
+ export declare function classifyHookSeverity(toolName: string, input: Record<string, unknown>): SeverityResult;
19
29
  export declare function getHookClients(): Set<WebSocket>;
20
30
  export declare function addHookClient(ws: WebSocket): void;
21
31
  /**
@@ -27,4 +37,5 @@ export declare function resolveApproval(toolId: string, approved: boolean, data?
27
37
  */
28
38
  export declare function handleHookWebSocketMessage(ws: WebSocket, message: string): void;
29
39
  export declare function createHookRequestRouter(): Router;
40
+ export {};
30
41
  //# sourceMappingURL=hook-request.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hook-request.d.ts","sourceRoot":"","sources":["../../src/api/hook-request.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAuE/B,wBAAgB,cAAc,IAAI,GAAG,CAAC,SAAS,CAAC,CAE/C;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAGjD;AA4BD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,OAAO,EACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAcT;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAU/E;AA0ED,wBAAgB,uBAAuB,IAAI,MAAM,CAkBhD"}
1
+ {"version":3,"file":"hook-request.d.ts","sourceRoot":"","sources":["../../src/api/hook-request.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AA2E/B,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,aAAa,CAAC;AAE7D,UAAU,cAAc;IACtB,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAuBD;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,cAAc,CAkDhB;AAMD,wBAAgB,cAAc,IAAI,GAAG,CAAC,SAAS,CAAC,CAE/C;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAGjD;AA+BD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,OAAO,EACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAcT;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CA6B/E;AAwFD,wBAAgB,uBAAuB,IAAI,MAAM,CAkBhD"}
@@ -16,6 +16,8 @@
16
16
  */
17
17
  import { Router } from 'express';
18
18
  import { WebSocket } from 'ws';
19
+ import { checkGrant, isAllowlisted, addGrant } from '../settings-store.js';
20
+ import { isDangerousPath, getPathCategory, extractBashTargetPaths } from '../dangerous-path.js';
19
21
  // =============================================================================
20
22
  // State
21
23
  // =============================================================================
@@ -26,24 +28,90 @@ const hookClients = new Set();
26
28
  // Approval timeout (2 minutes)
27
29
  const APPROVAL_TIMEOUT_MS = 120000;
28
30
  // =============================================================================
29
- // Allowlist and Grants (imported from settings-store when available)
31
+ // Scope Extraction (MSSCI-14321)
30
32
  // =============================================================================
31
- // Temporary allowlist for common safe commands
32
- const SAFE_COMMAND_PATTERNS = [
33
- /^ls\b/,
34
- /^pwd$/,
35
- /^echo\b/,
36
- /^cat\b.*\.(md|txt|json|yaml|yml|ts|js|py|sh)$/,
37
- /^git status/,
38
- /^git diff/,
39
- /^git log/,
40
- /^git branch/,
41
- /^npm run (build|test|lint)/,
42
- /^node --version/,
43
- /^npm --version/,
33
+ /**
34
+ * Extract the scope identifier from tool input for grant matching.
35
+ * Modeled after getToolScope() in approval-gate.ts.
36
+ */
37
+ function extractToolScope(toolName, input) {
38
+ switch (toolName) {
39
+ case 'Bash':
40
+ return input.command || '';
41
+ case 'WebFetch':
42
+ return input.url || '';
43
+ case 'Edit':
44
+ case 'Write':
45
+ case 'Read':
46
+ return input.file_path || '';
47
+ default:
48
+ return JSON.stringify(input);
49
+ }
50
+ }
51
+ const SAFE_TOOLS = new Set(['Read', 'Grep', 'Glob', 'WebSearch']);
52
+ const DESTRUCTIVE_BASH_PATTERNS = [
53
+ /rm\s+(-[rf]+\s+)*/,
54
+ /git\s+(reset\s+--hard|push\s+--force|clean\s+-[fd])/,
55
+ /drop\s+database/i,
56
+ /truncate\s+table/i,
44
57
  ];
45
- function isCommandAllowlisted(command) {
46
- return SAFE_COMMAND_PATTERNS.some(pattern => pattern.test(command.trim()));
58
+ const SAFE_BASH_PATTERNS = [
59
+ /^(ls|cat|head|tail|grep|find|pwd|echo|which|type|file|stat|wc|diff)\b/,
60
+ /^git\s+(status|log|diff|show|branch|remote)\b/,
61
+ ];
62
+ const CATEGORY_WARNINGS = {
63
+ secrets: 'Modifying sensitive secrets/credentials file',
64
+ git: 'Modifying git internals',
65
+ dependencies: 'Modifying dependency files',
66
+ system: 'Modifying system files',
67
+ };
68
+ /**
69
+ * Classify the severity of a hook request server-side.
70
+ * Combines tool-name classification with dangerous-path detection.
71
+ */
72
+ export function classifyHookSeverity(toolName, input) {
73
+ // Safe tools are always safe
74
+ if (SAFE_TOOLS.has(toolName)) {
75
+ return { severity: 'safe' };
76
+ }
77
+ // Check dangerous paths for Write/Edit
78
+ if (toolName === 'Write' || toolName === 'Edit') {
79
+ const filePath = input.file_path || '';
80
+ if (filePath && isDangerousPath(filePath)) {
81
+ const category = getPathCategory(filePath);
82
+ return {
83
+ severity: 'destructive',
84
+ warning: category ? CATEGORY_WARNINGS[category] : 'Modifying sensitive path',
85
+ };
86
+ }
87
+ return { severity: 'normal' };
88
+ }
89
+ // Bash command classification
90
+ if (toolName === 'Bash') {
91
+ const command = input.command || '';
92
+ // Check for destructive bash patterns
93
+ if (DESTRUCTIVE_BASH_PATTERNS.some(p => p.test(command))) {
94
+ return { severity: 'destructive', warning: 'Destructive command detected' };
95
+ }
96
+ // Check for bash redirecting to dangerous paths
97
+ const targetPaths = extractBashTargetPaths(command);
98
+ for (const targetPath of targetPaths) {
99
+ if (isDangerousPath(targetPath)) {
100
+ const category = getPathCategory(targetPath);
101
+ return {
102
+ severity: 'destructive',
103
+ warning: category ? CATEGORY_WARNINGS[category] : 'Redirecting to sensitive path',
104
+ };
105
+ }
106
+ }
107
+ // Check for safe bash patterns
108
+ if (SAFE_BASH_PATTERNS.some(p => p.test(command))) {
109
+ return { severity: 'safe' };
110
+ }
111
+ return { severity: 'normal' };
112
+ }
113
+ // Default: normal
114
+ return { severity: 'normal' };
47
115
  }
48
116
  // =============================================================================
49
117
  // WebSocket Client Management
@@ -92,6 +160,24 @@ export function handleHookWebSocketMessage(ws, message) {
92
160
  try {
93
161
  const data = JSON.parse(message);
94
162
  if (data.type === 'hook-response') {
163
+ // Store grant if user approved with a grantScope (MSSCI-14321 AC8)
164
+ if (data.approved && data.data?.grantScope) {
165
+ const pending = pendingApprovals.get(data.toolId);
166
+ const scope = pending
167
+ ? extractToolScope(pending.toolName, pending.input)
168
+ : '';
169
+ const grant = {
170
+ tool: pending?.toolName || '',
171
+ scope,
172
+ grant_type: data.data.grantScope,
173
+ granted_at: new Date().toISOString(),
174
+ };
175
+ // Include agent from the original request if present
176
+ if (pending?.agent) {
177
+ grant.agent = pending.agent;
178
+ }
179
+ addGrant(grant);
180
+ }
95
181
  resolveApproval(data.toolId, data.approved, data.data);
96
182
  }
97
183
  }
@@ -103,21 +189,27 @@ export function handleHookWebSocketMessage(ws, message) {
103
189
  // Request Handler
104
190
  // =============================================================================
105
191
  async function handleHookRequest(req, res) {
106
- const { toolName, toolId, input, sessionId: _sessionId, context } = req.body;
192
+ const { toolName, toolId, input, sessionId: _sessionId, agent, context } = req.body;
107
193
  if (!toolName || !toolId) {
108
194
  res.status(400).json({ error: 'Missing required fields: toolName, toolId' });
109
195
  return;
110
196
  }
111
- // Check for auto-approval
112
- if (toolName === 'Bash') {
113
- const command = input?.command || '';
114
- if (isCommandAllowlisted(command)) {
115
- res.json({
116
- decision: 'allow',
117
- reason: 'Command matches safe pattern allowlist',
118
- });
119
- return;
120
- }
197
+ // Check for auto-approval via grants (all tool types)
198
+ const scope = extractToolScope(toolName, input || {});
199
+ if (checkGrant(toolName, scope, agent)) {
200
+ res.json({
201
+ decision: 'allow',
202
+ reason: 'Granted by permission grant',
203
+ });
204
+ return;
205
+ }
206
+ // Check Bash allowlist for backward compatibility
207
+ if (toolName === 'Bash' && isAllowlisted(scope)) {
208
+ res.json({
209
+ decision: 'allow',
210
+ reason: 'Command matches allowlist pattern',
211
+ });
212
+ return;
121
213
  }
122
214
  // No clients connected - fall through to Claude Code's built-in approval
123
215
  if (hookClients.size === 0) {
@@ -133,6 +225,7 @@ async function handleHookRequest(req, res) {
133
225
  resolve,
134
226
  toolName,
135
227
  input: input || {},
228
+ agent,
136
229
  timestamp: Date.now(),
137
230
  });
138
231
  // Set timeout
@@ -146,12 +239,17 @@ async function handleHookRequest(req, res) {
146
239
  }
147
240
  }, APPROVAL_TIMEOUT_MS);
148
241
  });
149
- // Broadcast to clients (include context for UI display)
242
+ // Classify severity before broadcast (MSSCI-14323)
243
+ const { severity, warning } = classifyHookSeverity(toolName, input || {});
244
+ // Broadcast to clients (include context, severity, and agent for UI display)
150
245
  broadcastHookRequest({
151
246
  type: 'hook-request',
152
247
  toolId,
153
248
  toolName,
154
249
  input: input || {},
250
+ severity,
251
+ warning,
252
+ agent,
155
253
  context,
156
254
  });
157
255
  // Wait for response
@@ -1 +1 @@
1
- {"version":3,"file":"hook-request.js","sourceRoot":"","sources":["../../src/api/hook-request.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hook-request.js","sourceRoot":"","sources":["../../src/api/hook-request.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ export declare function createHotspotsRouter(getProjectDir: () => string): Router;
3
+ //# sourceMappingURL=hotspots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hotspots.d.ts","sourceRoot":"","sources":["../../src/api/hotspots.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAKjC,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,MAAM,MAAM,GAAG,MAAM,CAqDxE"}
@@ -0,0 +1,54 @@
1
+ import { Router } from 'express';
2
+ import { execFile } from 'child_process';
3
+ import { join } from 'path';
4
+ // Create hotspots API router
5
+ export function createHotspotsRouter(getProjectDir) {
6
+ const router = Router();
7
+ // GET /api/hotspots?days=90&repo=pennyfarthing
8
+ router.get('/', (req, res) => {
9
+ const projectDir = getProjectDir();
10
+ const days = String(req.query.days || '90');
11
+ const repo = req.query.repo;
12
+ const args = [
13
+ '-m', 'pennyfarthing_scripts.hotspots',
14
+ 'analyze',
15
+ '--format', 'json',
16
+ '--days', days,
17
+ ];
18
+ if (repo) {
19
+ args.push('--repo', repo);
20
+ }
21
+ else {
22
+ args.push('--path', projectDir);
23
+ }
24
+ // Find python in the project's pennyfarthing dir
25
+ const pythonPath = join(projectDir, 'pennyfarthing');
26
+ execFile('python3', args, {
27
+ cwd: pythonPath,
28
+ env: { ...process.env, PYTHONPATH: pythonPath },
29
+ timeout: 30000,
30
+ }, (err, stdout, stderr) => {
31
+ if (err) {
32
+ console.error('[Hotspots] Analysis failed:', stderr || err.message);
33
+ res.status(500).json({
34
+ success: false,
35
+ error: stderr || err.message,
36
+ });
37
+ return;
38
+ }
39
+ try {
40
+ const data = JSON.parse(stdout);
41
+ res.json(data);
42
+ }
43
+ catch (parseErr) {
44
+ console.error('[Hotspots] JSON parse failed:', parseErr);
45
+ res.status(500).json({
46
+ success: false,
47
+ error: 'Failed to parse hotspot analysis output',
48
+ });
49
+ }
50
+ });
51
+ });
52
+ return router;
53
+ }
54
+ //# sourceMappingURL=hotspots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hotspots.js","sourceRoot":"","sources":["../../src/api/hotspots.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,6BAA6B;AAC7B,MAAM,UAAU,oBAAoB,CAAC,aAA2B;IAC9D,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB,+CAA+C;IAC/C,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC3B,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;QAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,IAA0B,CAAC;QAElD,MAAM,IAAI,GAAG;YACX,IAAI,EAAE,gCAAgC;YACtC,SAAS;YACT,UAAU,EAAE,MAAM;YAClB,QAAQ,EAAE,IAAI;SACf,CAAC;QAEF,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC5B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAClC,CAAC;QAED,iDAAiD;QACjD,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QAErD,QAAQ,CAAC,SAAS,EAAE,IAAI,EAAE;YACxB,GAAG,EAAE,UAAU;YACf,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE;YAC/C,OAAO,EAAE,KAAK;SACf,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACzB,IAAI,GAAG,EAAE,CAAC;gBACR,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;gBACpE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACnB,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,MAAM,IAAI,GAAG,CAAC,OAAO;iBAC7B,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBAChC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjB,CAAC;YAAC,OAAO,QAAQ,EAAE,CAAC;gBAClB,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,QAAQ,CAAC,CAAC;gBACzD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACnB,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,yCAAyC;iBACjD,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -5,6 +5,7 @@ export { createGitRouter, getGitInfo, getAllReposGitInfo, getGitInfoAsync, getAl
5
5
  export type { GitInfo } from './git.js';
6
6
  export { createOTLPRouter } from './otlp.js';
7
7
  export { createStoryRouter } from './story.js';
8
+ export { createHotspotsRouter } from './hotspots.js';
8
9
  export { createFileBrowserRouter } from './file-browser.js';
9
10
  export { createTokenStatsRouter, broadcastTokenStats, getTokenStatsClients, initTokenStatsBroadcast } from './token-stats.js';
10
11
  export { createContextRouter, getContextUsage } from './context.js';
@@ -24,4 +25,5 @@ export { createIdentityRouter } from './identity.js';
24
25
  export type { IdentityInfo } from './identity.js';
25
26
  export { createTodosRouter, setWebModeTodos, getWebModeTodos } from './todos.js';
26
27
  export { createAuditLogRouter } from './audit-log.js';
28
+ export { createPermissionsRouter } from './permissions.js';
27
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5G,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,kBAAkB,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AACrH,YAAY,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC9H,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC5E,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC1D,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAGzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,EAAE,2BAA2B,EAAE,wBAAwB,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAEzJ,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAElE,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAExI,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEjF,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5G,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,kBAAkB,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AACrH,YAAY,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC9H,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC5E,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC1D,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAGzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,EAAE,2BAA2B,EAAE,wBAAwB,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAEzJ,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAElE,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAExI,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEjF,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/api/index.js CHANGED
@@ -5,6 +5,7 @@ export { createPersonaRouter, broadcastPersona, getPersonaClients } from './pers
5
5
  export { createGitRouter, getGitInfo, getAllReposGitInfo, getGitInfoAsync, getAllReposGitInfoAsync } from './git.js';
6
6
  export { createOTLPRouter } from './otlp.js';
7
7
  export { createStoryRouter } from './story.js';
8
+ export { createHotspotsRouter } from './hotspots.js';
8
9
  export { createFileBrowserRouter } from './file-browser.js';
9
10
  export { createTokenStatsRouter, broadcastTokenStats, getTokenStatsClients, initTokenStatsBroadcast } from './token-stats.js';
10
11
  export { createContextRouter, getContextUsage } from './context.js';
@@ -29,4 +30,6 @@ export { createIdentityRouter } from './identity.js';
29
30
  export { createTodosRouter, setWebModeTodos, getWebModeTodos } from './todos.js';
30
31
  // Audit log API
31
32
  export { createAuditLogRouter } from './audit-log.js';
33
+ // MSSCI-14325: Permissions API (grant management)
34
+ export { createPermissionsRouter } from './permissions.js';
32
35
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5G,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,kBAAkB,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAErH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC9H,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE5E,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,2FAA2F;AAC3F,6CAA6C;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,8BAA8B;AAC9B,OAAO,EAAE,2BAA2B,EAAE,wBAAwB,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AACzJ,kCAAkC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,gCAAgC;AAChC,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAClE,yDAAyD;AACzD,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AACxI,4CAA4C;AAC5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAErD,kCAAkC;AAClC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACjF,gBAAgB;AAChB,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5G,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,kBAAkB,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAErH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC9H,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE5E,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,2FAA2F;AAC3F,6CAA6C;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,8BAA8B;AAC9B,OAAO,EAAE,2BAA2B,EAAE,wBAAwB,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AACzJ,kCAAkC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,gCAAgC;AAChC,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAClE,yDAAyD;AACzD,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AACxI,4CAA4C;AAC5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAErD,kCAAkC;AAClC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACjF,gBAAgB;AAChB,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,kDAAkD;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Permissions API Router
3
+ *
4
+ * Bridges the /permissions skill to settings-store.ts grant management.
5
+ *
6
+ * Story: MSSCI-14325 - Connect /permissions skill to grant store
7
+ *
8
+ * Routes:
9
+ * - GET / List all active grants
10
+ * - POST /grant Add a permission grant
11
+ * - DELETE /revoke/:tool Revoke grants for a tool
12
+ * - GET /show/:tool Show grants for a specific tool
13
+ */
14
+ import { Router } from 'express';
15
+ export declare function createPermissionsRouter(): Router;
16
+ //# sourceMappingURL=permissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/api/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAKpD,wBAAgB,uBAAuB,IAAI,MAAM,CAiEhD"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Permissions API Router
3
+ *
4
+ * Bridges the /permissions skill to settings-store.ts grant management.
5
+ *
6
+ * Story: MSSCI-14325 - Connect /permissions skill to grant store
7
+ *
8
+ * Routes:
9
+ * - GET / List all active grants
10
+ * - POST /grant Add a permission grant
11
+ * - DELETE /revoke/:tool Revoke grants for a tool
12
+ * - GET /show/:tool Show grants for a specific tool
13
+ */
14
+ import { Router } from 'express';
15
+ import { getGrants, addGrant, removeGrant } from '../settings-store.js';
16
+ const VALID_GRANT_TYPES = ['once', 'session', 'always'];
17
+ export function createPermissionsRouter() {
18
+ const router = Router();
19
+ // GET / - List all active grants
20
+ router.get('/', (_req, res) => {
21
+ res.json({ grants: getGrants() });
22
+ });
23
+ // POST /grant - Add a permission grant
24
+ router.post('/grant', (req, res) => {
25
+ const { tool, scope, grant_type } = req.body;
26
+ if (!tool || typeof tool !== 'string') {
27
+ res.status(400).json({ error: 'Missing required field: tool' });
28
+ return;
29
+ }
30
+ if (!scope || typeof scope !== 'string') {
31
+ res.status(400).json({ error: 'Missing required field: scope' });
32
+ return;
33
+ }
34
+ const grantType = grant_type || 'session';
35
+ if (!VALID_GRANT_TYPES.includes(grantType)) {
36
+ res.status(400).json({ error: `Invalid grant_type: ${grantType}. Must be one of: ${VALID_GRANT_TYPES.join(', ')}` });
37
+ return;
38
+ }
39
+ const grant = {
40
+ tool,
41
+ scope,
42
+ grant_type: grantType,
43
+ granted_at: new Date().toISOString(),
44
+ };
45
+ addGrant(grant);
46
+ res.status(201).json({ grant });
47
+ });
48
+ // DELETE /revoke/:tool - Revoke grants for a tool
49
+ router.delete('/revoke/:tool', (req, res) => {
50
+ const { tool } = req.params;
51
+ const scopeFilter = req.query.scope;
52
+ const allGrants = getGrants();
53
+ const toRemove = allGrants.filter((g) => g.tool === tool && (!scopeFilter || g.scope === scopeFilter));
54
+ for (const grant of toRemove) {
55
+ removeGrant(grant);
56
+ }
57
+ res.json({ removed: toRemove.length });
58
+ });
59
+ // GET /show/:tool - Show grants for a specific tool
60
+ router.get('/show/:tool', (req, res) => {
61
+ const { tool } = req.params;
62
+ const grants = getGrants().filter((g) => g.tool === tool);
63
+ res.json({ grants });
64
+ });
65
+ return router;
66
+ }
67
+ //# sourceMappingURL=permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/api/permissions.ts"],"names":[],"mappings":"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"}
@@ -46,7 +46,7 @@ export declare function createErrorResponse(code: ErrorCode, message: string): E
46
46
  * Get settings for WebSocket broadcast
47
47
  * Used by websocket.ts for /ws/settings endpoint
48
48
  */
49
- export declare function getSettingsForWebSocket(projectDir: string | null): Promise<SettingsResponse>;
49
+ export declare function getSettingsForWebSocket(projectDir: string | null): Promise<Record<string, unknown>>;
50
50
  /**
51
51
  * Create the settings router
52
52
  */
@@ -1 +1 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/api/settings.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAIjC,OAAO,EAAwC,KAAK,eAAe,EAAsB,MAAM,gBAAgB,CAAC;AAShH;;;;;GAKG;AACH,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC;IACzE,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,GAAG;QACtC,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,CAAC;IACF,aAAa,CAAC,EAAE;QACd,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAMD;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,kBAAkB,GAAG,YAAY,GAAG,kBAAkB,GAAG,eAAe,CAAC;AAEjG;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,CAMnF;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsClG;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAya7C"}
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/api/settings.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAIjC,OAAO,EAAwC,KAAK,eAAe,EAAsB,MAAM,gBAAgB,CAAC;AAShH;;;;;GAKG;AACH,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC;IACzE,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,GAAG;QACtC,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,CAAC;IACF,aAAa,CAAC,EAAE;QACd,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAMD;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,kBAAkB,GAAG,YAAY,GAAG,kBAAkB,GAAG,eAAe,CAAC;AAEjG;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,CAMnF;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAmDzG;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAob7C"}
@@ -31,24 +31,33 @@ export function createErrorResponse(code, message) {
31
31
  */
32
32
  export async function getSettingsForWebSocket(projectDir) {
33
33
  const settings = getCurrentSettings();
34
- // Read theme, handoff_mode, and bell_mode from config.local.yaml
34
+ // Read config.local.yaml as single source of truth
35
35
  let theme = 'alice-in-wonderland';
36
36
  let handoffMode = 'manual';
37
37
  let bellMode = false;
38
+ let display;
39
+ let notifications;
38
40
  if (projectDir) {
39
41
  try {
40
42
  const configPath = path.join(projectDir, '.pennyfarthing', 'config.local.yaml');
41
43
  if (fs.existsSync(configPath)) {
42
44
  const content = fs.readFileSync(configPath, 'utf-8');
43
45
  const parsed = parse(content);
44
- if (parsed?.theme) {
46
+ if (parsed?.theme && typeof parsed.theme === 'string') {
45
47
  theme = parsed.theme;
46
48
  }
47
- if (parsed?.workflow?.handoff_mode) {
48
- handoffMode = parsed.workflow.handoff_mode;
49
+ const workflow = parsed?.workflow;
50
+ if (workflow?.handoff_mode) {
51
+ handoffMode = workflow.handoff_mode;
49
52
  }
50
- if (parsed?.workflow?.bell_mode !== undefined) {
51
- bellMode = parsed.workflow.bell_mode;
53
+ if (workflow?.bell_mode !== undefined) {
54
+ bellMode = workflow.bell_mode;
55
+ }
56
+ if (parsed?.display && typeof parsed.display === 'object') {
57
+ display = parsed.display;
58
+ }
59
+ if (parsed?.notifications && typeof parsed.notifications === 'object') {
60
+ notifications = parsed.notifications;
52
61
  }
53
62
  }
54
63
  }
@@ -56,7 +65,7 @@ export async function getSettingsForWebSocket(projectDir) {
56
65
  // Ignore project config errors - use defaults
57
66
  }
58
67
  }
59
- return {
68
+ const response = {
60
69
  ...settings,
61
70
  workflow: {
62
71
  ...settings.workflow,
@@ -65,6 +74,11 @@ export async function getSettingsForWebSocket(projectDir) {
65
74
  },
66
75
  pennyfarthing: { theme },
67
76
  };
77
+ if (display)
78
+ response.display = display;
79
+ if (notifications)
80
+ response.notifications = notifications;
81
+ return response;
68
82
  }
69
83
  /**
70
84
  * Create the settings router
@@ -79,10 +93,12 @@ export function createSettingsRouter() {
79
93
  router.get('/', async (_req, res) => {
80
94
  try {
81
95
  const settings = getCurrentSettings();
82
- // Read theme, handoff_mode, and bell_mode from config.local.yaml (single source of truth)
83
- let theme = 'alice-in-wonderland'; // Default fallback
84
- let handoffMode = 'manual'; // Default fallback
85
- let bellMode = false; // Default fallback
96
+ // Read config.local.yaml as single source of truth for all project settings
97
+ let theme = 'alice-in-wonderland';
98
+ let handoffMode = 'manual';
99
+ let bellMode = false;
100
+ let display;
101
+ let notifications;
86
102
  const projectDir = getProjectDirectory();
87
103
  if (projectDir) {
88
104
  try {
@@ -90,14 +106,21 @@ export function createSettingsRouter() {
90
106
  if (fs.existsSync(configPath)) {
91
107
  const content = fs.readFileSync(configPath, 'utf-8');
92
108
  const parsed = parse(content);
93
- if (parsed?.theme) {
109
+ if (parsed?.theme && typeof parsed.theme === 'string') {
94
110
  theme = parsed.theme;
95
111
  }
96
- if (parsed?.workflow?.handoff_mode) {
97
- handoffMode = parsed.workflow.handoff_mode;
112
+ const workflow = parsed?.workflow;
113
+ if (workflow?.handoff_mode) {
114
+ handoffMode = workflow.handoff_mode;
115
+ }
116
+ if (workflow?.bell_mode !== undefined) {
117
+ bellMode = workflow.bell_mode;
118
+ }
119
+ if (parsed?.display && typeof parsed.display === 'object') {
120
+ display = parsed.display;
98
121
  }
99
- if (parsed?.workflow?.bell_mode !== undefined) {
100
- bellMode = parsed.workflow.bell_mode;
122
+ if (parsed?.notifications && typeof parsed.notifications === 'object') {
123
+ notifications = parsed.notifications;
101
124
  }
102
125
  }
103
126
  }
@@ -105,7 +128,7 @@ export function createSettingsRouter() {
105
128
  // Ignore project config errors - use defaults
106
129
  }
107
130
  }
108
- // Construct response with theme, handoff_mode, and bell_mode added
131
+ // Construct response with all persisted settings
109
132
  const response = {
110
133
  ...settings,
111
134
  workflow: {
@@ -115,6 +138,10 @@ export function createSettingsRouter() {
115
138
  },
116
139
  pennyfarthing: { theme },
117
140
  };
141
+ if (display)
142
+ response.display = display;
143
+ if (notifications)
144
+ response.notifications = notifications;
118
145
  res.json(response);
119
146
  }
120
147
  catch (error) {