@pennyfarthing/cyclist 10.4.0 → 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 +2 -2
- 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 +42 -75
- package/dist/websocket.js.map +1 -1
- package/package.json +3 -6
- 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/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
package/dist/api/spans.js
CHANGED
|
@@ -1,245 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* REST API for enriched span data with filtering, pagination, and export.
|
|
5
|
-
*
|
|
6
|
-
* Endpoints:
|
|
7
|
-
* - GET /api/spans - List all enriched spans (with filtering and pagination)
|
|
8
|
-
* - GET /api/spans/export - Export spans as JSON with metadata
|
|
9
|
-
* - GET /api/spans/summary - Get span statistics
|
|
10
|
-
* - GET /api/spans/:spanId - Get single span details
|
|
11
|
-
*/
|
|
12
|
-
import { Router } from 'express';
|
|
13
|
-
import { getEnrichedSpans, filterSpans, formatSpanForExport, exportEnrichedSpans, } from '../enriched-span-exporter.js';
|
|
14
|
-
// =============================================================================
|
|
15
|
-
// Constants
|
|
16
|
-
// =============================================================================
|
|
17
|
-
const DEFAULT_LIMIT = 100;
|
|
18
|
-
const MAX_LIMIT = 1000;
|
|
19
|
-
// =============================================================================
|
|
20
|
-
// Helper Functions
|
|
21
|
-
// =============================================================================
|
|
22
|
-
/**
|
|
23
|
-
* Parse and validate filter parameters from query string
|
|
24
|
-
*/
|
|
25
|
-
function parseFilters(query) {
|
|
26
|
-
const filter = {};
|
|
27
|
-
// Parse tool types
|
|
28
|
-
if (query.toolType) {
|
|
29
|
-
const toolType = String(query.toolType);
|
|
30
|
-
filter.toolTypes = toolType.split(',').map((t) => t.trim());
|
|
31
|
-
}
|
|
32
|
-
// Parse status
|
|
33
|
-
if (query.status) {
|
|
34
|
-
const status = String(query.status);
|
|
35
|
-
if (status !== 'success' && status !== 'error') {
|
|
36
|
-
return { error: `Invalid status value: ${status}. Must be 'success' or 'error'` };
|
|
37
|
-
}
|
|
38
|
-
filter.status = status;
|
|
39
|
-
}
|
|
40
|
-
// Parse time range
|
|
41
|
-
if (query.startTime !== undefined) {
|
|
42
|
-
const startTime = Number(query.startTime);
|
|
43
|
-
if (isNaN(startTime)) {
|
|
44
|
-
return { error: 'Invalid startTime: must be a number' };
|
|
45
|
-
}
|
|
46
|
-
filter.startTime = startTime;
|
|
47
|
-
}
|
|
48
|
-
if (query.endTime !== undefined) {
|
|
49
|
-
const endTime = Number(query.endTime);
|
|
50
|
-
if (isNaN(endTime)) {
|
|
51
|
-
return { error: 'Invalid endTime: must be a number' };
|
|
52
|
-
}
|
|
53
|
-
filter.endTime = endTime;
|
|
54
|
-
}
|
|
55
|
-
return filter;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Parse and validate pagination parameters
|
|
59
|
-
*/
|
|
60
|
-
function parsePagination(query) {
|
|
61
|
-
let offset = 0;
|
|
62
|
-
let limit = DEFAULT_LIMIT;
|
|
63
|
-
if (query.offset !== undefined) {
|
|
64
|
-
offset = Number(query.offset);
|
|
65
|
-
if (isNaN(offset) || offset < 0) {
|
|
66
|
-
return { error: 'Invalid offset: must be a non-negative number' };
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (query.limit !== undefined) {
|
|
70
|
-
limit = Number(query.limit);
|
|
71
|
-
if (isNaN(limit) || limit < 1) {
|
|
72
|
-
return { error: 'Invalid limit: must be a positive number' };
|
|
73
|
-
}
|
|
74
|
-
limit = Math.min(limit, MAX_LIMIT);
|
|
75
|
-
}
|
|
76
|
-
return { offset, limit };
|
|
77
|
-
}
|
|
78
|
-
// =============================================================================
|
|
79
|
-
// Router Factory
|
|
80
|
-
// =============================================================================
|
|
81
|
-
/**
|
|
82
|
-
* Create spans API router
|
|
83
|
-
*/
|
|
84
|
-
export function createSpansRouter() {
|
|
85
|
-
const router = Router();
|
|
86
|
-
/**
|
|
87
|
-
* GET /api/spans
|
|
88
|
-
*
|
|
89
|
-
* Returns list of enriched spans with filtering and pagination.
|
|
90
|
-
*
|
|
91
|
-
* Query parameters:
|
|
92
|
-
* - toolType: Filter by tool type(s), comma-separated (e.g., "Bash,Read")
|
|
93
|
-
* - status: Filter by status ("success" or "error")
|
|
94
|
-
* - startTime: Filter spans starting after this Unix timestamp (ms)
|
|
95
|
-
* - endTime: Filter spans starting before this Unix timestamp (ms)
|
|
96
|
-
* - offset: Pagination offset (default: 0)
|
|
97
|
-
* - limit: Pagination limit (default: 100, max: 1000)
|
|
98
|
-
*/
|
|
99
|
-
router.get('/', async (req, res) => {
|
|
100
|
-
try {
|
|
101
|
-
// Parse filters
|
|
102
|
-
const filterResult = parseFilters(req.query);
|
|
103
|
-
if ('error' in filterResult) {
|
|
104
|
-
return res.status(400).json({ error: filterResult.error });
|
|
105
|
-
}
|
|
106
|
-
// Parse pagination
|
|
107
|
-
const paginationResult = parsePagination(req.query);
|
|
108
|
-
if ('error' in paginationResult) {
|
|
109
|
-
return res.status(400).json({ error: paginationResult.error });
|
|
110
|
-
}
|
|
111
|
-
const { offset, limit } = paginationResult;
|
|
112
|
-
// Get all spans
|
|
113
|
-
const allSpans = await getEnrichedSpans();
|
|
114
|
-
// Return 404 if no spans
|
|
115
|
-
if (allSpans.length === 0) {
|
|
116
|
-
return res.status(404).json({ error: 'No spans available' });
|
|
117
|
-
}
|
|
118
|
-
// Apply filters
|
|
119
|
-
const filteredSpans = filterSpans(allSpans, filterResult);
|
|
120
|
-
// Apply pagination
|
|
121
|
-
const paginatedSpans = filteredSpans.slice(offset, offset + limit);
|
|
122
|
-
// Format for response
|
|
123
|
-
const formattedSpans = paginatedSpans.map(formatSpanForExport);
|
|
124
|
-
res.json({
|
|
125
|
-
spans: formattedSpans,
|
|
126
|
-
total: filteredSpans.length,
|
|
127
|
-
offset,
|
|
128
|
-
limit,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
catch (error) {
|
|
132
|
-
console.error('[Spans API] Error fetching spans:', error);
|
|
133
|
-
res.status(500).json({ error: 'Internal server error' });
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
/**
|
|
137
|
-
* GET /api/spans/export
|
|
138
|
-
*
|
|
139
|
-
* Export enriched spans as JSON with metadata and summary.
|
|
140
|
-
* Supports same filter parameters as GET /api/spans.
|
|
141
|
-
* Add ?download=true to set Content-Disposition for file download.
|
|
142
|
-
*/
|
|
143
|
-
router.get('/export', async (req, res) => {
|
|
144
|
-
try {
|
|
145
|
-
// Parse filters
|
|
146
|
-
const filterResult = parseFilters(req.query);
|
|
147
|
-
if ('error' in filterResult) {
|
|
148
|
-
return res.status(400).json({ error: filterResult.error });
|
|
149
|
-
}
|
|
150
|
-
// Get all spans
|
|
151
|
-
const allSpans = await getEnrichedSpans();
|
|
152
|
-
// Export with filter
|
|
153
|
-
const exported = exportEnrichedSpans(allSpans, filterResult);
|
|
154
|
-
// Set download header if requested
|
|
155
|
-
if (req.query.download === 'true') {
|
|
156
|
-
const filename = `cyclist-spans-${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
|
|
157
|
-
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
|
158
|
-
}
|
|
159
|
-
res.json(exported);
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
console.error('[Spans API] Error exporting spans:', error);
|
|
163
|
-
res.status(500).json({ error: 'Internal server error' });
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
/**
|
|
167
|
-
* GET /api/spans/summary
|
|
168
|
-
*
|
|
169
|
-
* Returns summary statistics for all spans.
|
|
170
|
-
*/
|
|
171
|
-
router.get('/summary', async (req, res) => {
|
|
172
|
-
try {
|
|
173
|
-
// Get all spans
|
|
174
|
-
const allSpans = await getEnrichedSpans();
|
|
175
|
-
// Return 404 if no spans
|
|
176
|
-
if (allSpans.length === 0) {
|
|
177
|
-
return res.status(404).json({ error: 'No spans available' });
|
|
178
|
-
}
|
|
179
|
-
// Calculate statistics
|
|
180
|
-
const byToolType = {};
|
|
181
|
-
let totalDurationMs = 0;
|
|
182
|
-
let successCount = 0;
|
|
183
|
-
let errorCount = 0;
|
|
184
|
-
let earliest = Infinity;
|
|
185
|
-
let latest = 0;
|
|
186
|
-
for (const span of allSpans) {
|
|
187
|
-
byToolType[span.toolName] = (byToolType[span.toolName] || 0) + 1;
|
|
188
|
-
totalDurationMs += span.durationMs;
|
|
189
|
-
if (span.success) {
|
|
190
|
-
successCount++;
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
errorCount++;
|
|
194
|
-
}
|
|
195
|
-
if (span.startTime < earliest) {
|
|
196
|
-
earliest = span.startTime;
|
|
197
|
-
}
|
|
198
|
-
if (span.startTime > latest) {
|
|
199
|
-
latest = span.startTime;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
res.json({
|
|
203
|
-
totalSpans: allSpans.length,
|
|
204
|
-
successCount,
|
|
205
|
-
errorCount,
|
|
206
|
-
totalDurationMs,
|
|
207
|
-
byToolType,
|
|
208
|
-
timeRange: {
|
|
209
|
-
earliest: earliest === Infinity ? null : earliest,
|
|
210
|
-
latest: latest === 0 ? null : latest,
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
catch (error) {
|
|
215
|
-
console.error('[Spans API] Error getting summary:', error);
|
|
216
|
-
res.status(500).json({ error: 'Internal server error' });
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
/**
|
|
220
|
-
* GET /api/spans/:spanId
|
|
221
|
-
*
|
|
222
|
-
* Returns a single span by ID with full enrichment data.
|
|
223
|
-
*/
|
|
224
|
-
router.get('/:spanId', async (req, res) => {
|
|
225
|
-
try {
|
|
226
|
-
const { spanId } = req.params;
|
|
227
|
-
// Get all spans
|
|
228
|
-
const allSpans = await getEnrichedSpans();
|
|
229
|
-
// Find the span
|
|
230
|
-
const span = allSpans.find((s) => s.spanId === spanId);
|
|
231
|
-
if (!span) {
|
|
232
|
-
return res.status(404).json({ error: `Span not found: ${spanId}` });
|
|
233
|
-
}
|
|
234
|
-
// Format and return
|
|
235
|
-
const formatted = formatSpanForExport(span);
|
|
236
|
-
res.json(formatted);
|
|
237
|
-
}
|
|
238
|
-
catch (error) {
|
|
239
|
-
console.error('[Spans API] Error fetching span:', error);
|
|
240
|
-
res.status(500).json({ error: 'Internal server error' });
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
return router;
|
|
244
|
-
}
|
|
1
|
+
// Re-export from @pennyfarthing/core (Story 98-17)
|
|
2
|
+
export * from '@pennyfarthing/core/dist/server/api/spans.js';
|
|
245
3
|
//# sourceMappingURL=spans.js.map
|
package/dist/api/spans.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spans.js","sourceRoot":"","sources":["../../src/api/spans.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"spans.js","sourceRoot":"","sources":["../../src/api/spans.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,cAAc,8CAA8C,CAAC"}
|
package/dist/api/stats.d.ts
CHANGED
|
@@ -1,13 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import { WebSocket } from 'ws';
|
|
3
|
-
import { ParsedStats } from '../parser.js';
|
|
4
|
-
export declare function getCurrentStats(): {
|
|
5
|
-
model: string;
|
|
6
|
-
status: string;
|
|
7
|
-
pwd: string;
|
|
8
|
-
};
|
|
9
|
-
export declare function getStatsClients(): Set<WebSocket>;
|
|
10
|
-
export declare function updatePwd(pwd: string): void;
|
|
11
|
-
export declare function broadcastStats(stats: ParsedStats): void;
|
|
12
|
-
export declare function createStatsRouter(): Router;
|
|
1
|
+
export * from '@pennyfarthing/core/dist/server/api/stats.js';
|
|
13
2
|
//# sourceMappingURL=stats.d.ts.map
|
package/dist/api/stats.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/api/stats.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/api/stats.ts"],"names":[],"mappings":"AACA,cAAc,8CAA8C,CAAC"}
|
package/dist/api/stats.js
CHANGED
|
@@ -1,85 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// Stats state (in-memory for prototype)
|
|
4
|
-
// Context is handled separately via dedicated context IPC channel (B-19)
|
|
5
|
-
let currentStats = {
|
|
6
|
-
model: '—', // Unknown until parsed from PTY output
|
|
7
|
-
status: '—',
|
|
8
|
-
pwd: '', // Claude's current working directory (from Bash spans)
|
|
9
|
-
};
|
|
10
|
-
// Stats WebSocket clients (for real-time updates)
|
|
11
|
-
const statsClients = new Set();
|
|
12
|
-
// Debounce state for stats broadcasts
|
|
13
|
-
let pendingStats = null;
|
|
14
|
-
let debounceTimer = null;
|
|
15
|
-
const DEBOUNCE_MS = 100;
|
|
16
|
-
// Get current stats state
|
|
17
|
-
export function getCurrentStats() {
|
|
18
|
-
return currentStats;
|
|
19
|
-
}
|
|
20
|
-
// Get stats clients set (for WebSocket setup)
|
|
21
|
-
export function getStatsClients() {
|
|
22
|
-
return statsClients;
|
|
23
|
-
}
|
|
24
|
-
// Update pwd and broadcast (called when Bash tool completes)
|
|
25
|
-
export function updatePwd(pwd) {
|
|
26
|
-
if (pwd && pwd !== currentStats.pwd) {
|
|
27
|
-
currentStats.pwd = pwd;
|
|
28
|
-
broadcastStats({ pwd });
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
// Broadcast stats to all connected clients (debounced)
|
|
32
|
-
export function broadcastStats(stats) {
|
|
33
|
-
// Merge with pending stats
|
|
34
|
-
pendingStats = pendingStats ? { ...pendingStats, ...stats } : stats;
|
|
35
|
-
// Clear existing timer
|
|
36
|
-
if (debounceTimer) {
|
|
37
|
-
clearTimeout(debounceTimer);
|
|
38
|
-
}
|
|
39
|
-
// Schedule broadcast after debounce period
|
|
40
|
-
debounceTimer = setTimeout(() => {
|
|
41
|
-
if (pendingStats) {
|
|
42
|
-
const message = JSON.stringify(pendingStats);
|
|
43
|
-
for (const client of statsClients) {
|
|
44
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
45
|
-
client.send(message);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
pendingStats = null;
|
|
49
|
-
}
|
|
50
|
-
debounceTimer = null;
|
|
51
|
-
}, DEBOUNCE_MS);
|
|
52
|
-
}
|
|
53
|
-
// Create stats API router
|
|
54
|
-
export function createStatsRouter() {
|
|
55
|
-
const router = Router();
|
|
56
|
-
// Stats API - GET current stats
|
|
57
|
-
router.get('/', (_req, res) => {
|
|
58
|
-
res.json(currentStats);
|
|
59
|
-
});
|
|
60
|
-
// Stats API - SET stats (partial update supported)
|
|
61
|
-
// Context is handled separately via dedicated context IPC channel (B-19)
|
|
62
|
-
router.post('/', (req, res) => {
|
|
63
|
-
const { model, status, pwd } = req.body;
|
|
64
|
-
// Validate types if provided
|
|
65
|
-
if (model !== undefined && typeof model !== 'string') {
|
|
66
|
-
return res.status(400).json({ error: 'model must be a string' });
|
|
67
|
-
}
|
|
68
|
-
if (status !== undefined && typeof status !== 'string') {
|
|
69
|
-
return res.status(400).json({ error: 'status must be a string' });
|
|
70
|
-
}
|
|
71
|
-
if (pwd !== undefined && typeof pwd !== 'string') {
|
|
72
|
-
return res.status(400).json({ error: 'pwd must be a string' });
|
|
73
|
-
}
|
|
74
|
-
// Partial update - merge with existing stats
|
|
75
|
-
currentStats = {
|
|
76
|
-
...currentStats,
|
|
77
|
-
...(model !== undefined && { model }),
|
|
78
|
-
...(status !== undefined && { status }),
|
|
79
|
-
...(pwd !== undefined && { pwd }),
|
|
80
|
-
};
|
|
81
|
-
res.json({ success: true, ...currentStats });
|
|
82
|
-
});
|
|
83
|
-
return router;
|
|
84
|
-
}
|
|
1
|
+
// Re-export from @pennyfarthing/core (Story 98-17)
|
|
2
|
+
export * from '@pennyfarthing/core/dist/server/api/stats.js';
|
|
85
3
|
//# sourceMappingURL=stats.js.map
|
package/dist/api/stats.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stats.js","sourceRoot":"","sources":["../../src/api/stats.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"stats.js","sourceRoot":"","sources":["../../src/api/stats.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,cAAc,8CAA8C,CAAC"}
|
package/dist/api/story.d.ts
CHANGED
package/dist/api/story.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"story.d.ts","sourceRoot":"","sources":["../../src/api/story.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"story.d.ts","sourceRoot":"","sources":["../../src/api/story.ts"],"names":[],"mappings":"AACA,cAAc,8CAA8C,CAAC"}
|
package/dist/api/story.js
CHANGED
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// Create story API router
|
|
4
|
-
export function createStoryRouter(getProjectDir) {
|
|
5
|
-
const router = Router();
|
|
6
|
-
// Story API - GET current story info
|
|
7
|
-
// Returns graceful empty response (id: null) when no session exists
|
|
8
|
-
router.get('/', (_req, res) => {
|
|
9
|
-
const projectDir = getProjectDir();
|
|
10
|
-
const storyInfo = getStoryInfo(projectDir);
|
|
11
|
-
res.json(storyInfo);
|
|
12
|
-
});
|
|
13
|
-
return router;
|
|
14
|
-
}
|
|
1
|
+
// Re-export from @pennyfarthing/core (Story 98-17)
|
|
2
|
+
export * from '@pennyfarthing/core/dist/server/api/story.js';
|
|
15
3
|
//# sourceMappingURL=story.js.map
|
package/dist/api/story.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"story.js","sourceRoot":"","sources":["../../src/api/story.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"story.js","sourceRoot":"","sources":["../../src/api/story.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,cAAc,8CAA8C,CAAC"}
|
package/dist/api/telemetry.d.ts
CHANGED
|
@@ -1,19 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
* Telemetry API Router - Stories 19-6, 19-7
|
|
3
|
-
*
|
|
4
|
-
* REST API for telemetry data including TDD metrics, session stats,
|
|
5
|
-
* tool usage, agent stats, and story costs.
|
|
6
|
-
*/
|
|
7
|
-
import { Router } from 'express';
|
|
8
|
-
/**
|
|
9
|
-
* Create telemetry API router
|
|
10
|
-
*
|
|
11
|
-
* Endpoints:
|
|
12
|
-
* - GET /tdd - Returns current TDD phase metrics
|
|
13
|
-
* - GET /session - Returns session stats (tokens, cost, duration)
|
|
14
|
-
* - GET /tools - Returns tool usage breakdown
|
|
15
|
-
* - GET /agents - Returns per-agent token stats
|
|
16
|
-
* - GET /stories - Returns per-story token stats
|
|
17
|
-
*/
|
|
18
|
-
export declare function createTelemetryRouter(): Router;
|
|
1
|
+
export * from '@pennyfarthing/core/dist/server/api/telemetry.js';
|
|
19
2
|
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/api/telemetry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/api/telemetry.ts"],"names":[],"mappings":"AACA,cAAc,kDAAkD,CAAC"}
|
package/dist/api/telemetry.js
CHANGED
|
@@ -1,165 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* REST API for telemetry data including TDD metrics, session stats,
|
|
5
|
-
* tool usage, agent stats, and story costs.
|
|
6
|
-
*/
|
|
7
|
-
import { Router } from 'express';
|
|
8
|
-
import { getTDDMetrics } from '../tdd-metrics.js';
|
|
9
|
-
import { getSpanHierarchy } from '../span-hierarchy.js';
|
|
10
|
-
import { getTokenStatsByAgent } from '../agent-context.js';
|
|
11
|
-
import { getTokenStatsByStory } from '../story-context.js';
|
|
12
|
-
/**
|
|
13
|
-
* Create telemetry API router
|
|
14
|
-
*
|
|
15
|
-
* Endpoints:
|
|
16
|
-
* - GET /tdd - Returns current TDD phase metrics
|
|
17
|
-
* - GET /session - Returns session stats (tokens, cost, duration)
|
|
18
|
-
* - GET /tools - Returns tool usage breakdown
|
|
19
|
-
* - GET /agents - Returns per-agent token stats
|
|
20
|
-
* - GET /stories - Returns per-story token stats
|
|
21
|
-
*/
|
|
22
|
-
export function createTelemetryRouter() {
|
|
23
|
-
const router = Router();
|
|
24
|
-
/**
|
|
25
|
-
* GET /api/telemetry/tdd
|
|
26
|
-
*
|
|
27
|
-
* Returns TDD phase timing metrics for the current story.
|
|
28
|
-
*/
|
|
29
|
-
router.get('/tdd', (_req, res) => {
|
|
30
|
-
const metrics = getTDDMetrics();
|
|
31
|
-
if (!metrics) {
|
|
32
|
-
return res.status(404).json({ error: 'No TDD metrics available' });
|
|
33
|
-
}
|
|
34
|
-
res.json(metrics);
|
|
35
|
-
});
|
|
36
|
-
/**
|
|
37
|
-
* GET /api/telemetry/session
|
|
38
|
-
*
|
|
39
|
-
* Returns aggregate session statistics including token counts,
|
|
40
|
-
* total cost, duration, and span count.
|
|
41
|
-
*/
|
|
42
|
-
router.get('/session', (_req, res) => {
|
|
43
|
-
const spans = getSpanHierarchy();
|
|
44
|
-
if (!spans || spans.length === 0) {
|
|
45
|
-
return res.status(404).json({ error: 'No session data available' });
|
|
46
|
-
}
|
|
47
|
-
// Calculate session stats from spans
|
|
48
|
-
let totalInputTokens = 0;
|
|
49
|
-
let totalOutputTokens = 0;
|
|
50
|
-
const totalCacheRead = 0;
|
|
51
|
-
let totalCost = 0;
|
|
52
|
-
let spanCount = 0;
|
|
53
|
-
let minStartTime = Infinity;
|
|
54
|
-
let maxEndTime = 0;
|
|
55
|
-
for (const span of spans) {
|
|
56
|
-
// Count parent span
|
|
57
|
-
spanCount++;
|
|
58
|
-
// Aggregate tokens from span attributes (OTEL gen_ai.* convention)
|
|
59
|
-
if (span.attributes) {
|
|
60
|
-
totalInputTokens += span.attributes['gen_ai.usage.input_tokens'] || 0;
|
|
61
|
-
totalOutputTokens += span.attributes['gen_ai.usage.output_tokens'] || 0;
|
|
62
|
-
totalCost += span.attributes['gen_ai.usage.total_cost_usd'] || 0;
|
|
63
|
-
}
|
|
64
|
-
// Track timing
|
|
65
|
-
if (span.startTime) {
|
|
66
|
-
minStartTime = Math.min(minStartTime, span.startTime);
|
|
67
|
-
}
|
|
68
|
-
if (span.endTime) {
|
|
69
|
-
maxEndTime = Math.max(maxEndTime, span.endTime);
|
|
70
|
-
}
|
|
71
|
-
// Count child spans (tools)
|
|
72
|
-
if (span.childSpans) {
|
|
73
|
-
spanCount += span.childSpans.length;
|
|
74
|
-
for (const child of span.childSpans) {
|
|
75
|
-
if (child.startTime) {
|
|
76
|
-
minStartTime = Math.min(minStartTime, child.startTime);
|
|
77
|
-
}
|
|
78
|
-
if (child.endTime) {
|
|
79
|
-
maxEndTime = Math.max(maxEndTime, child.endTime);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
const duration = maxEndTime > minStartTime ? maxEndTime - minStartTime : 0;
|
|
85
|
-
res.json({
|
|
86
|
-
totalTokens: {
|
|
87
|
-
input: totalInputTokens,
|
|
88
|
-
output: totalOutputTokens,
|
|
89
|
-
cache_read: totalCacheRead,
|
|
90
|
-
},
|
|
91
|
-
totalCost,
|
|
92
|
-
duration,
|
|
93
|
-
spanCount,
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
/**
|
|
97
|
-
* GET /api/telemetry/tools
|
|
98
|
-
*
|
|
99
|
-
* Returns tool usage breakdown with count, totalDuration, and avgDuration
|
|
100
|
-
* per tool type.
|
|
101
|
-
*/
|
|
102
|
-
router.get('/tools', (_req, res) => {
|
|
103
|
-
const spans = getSpanHierarchy();
|
|
104
|
-
if (!spans || spans.length === 0) {
|
|
105
|
-
return res.status(404).json({ error: 'No tool data available' });
|
|
106
|
-
}
|
|
107
|
-
// Collect all tool spans
|
|
108
|
-
const toolStats = {};
|
|
109
|
-
for (const span of spans) {
|
|
110
|
-
if (span.childSpans) {
|
|
111
|
-
for (const child of span.childSpans) {
|
|
112
|
-
const toolName = child.attributes['tool.name'] || 'unknown';
|
|
113
|
-
// Use tool.duration_ms attribute, or calculate from start/end times
|
|
114
|
-
const duration = child.attributes['tool.duration_ms'] ??
|
|
115
|
-
(child.endTime && child.startTime ? child.endTime - child.startTime : 0);
|
|
116
|
-
if (!toolStats[toolName]) {
|
|
117
|
-
toolStats[toolName] = { count: 0, totalDuration: 0 };
|
|
118
|
-
}
|
|
119
|
-
toolStats[toolName].count++;
|
|
120
|
-
toolStats[toolName].totalDuration += duration;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
// If no tools found, return 404
|
|
125
|
-
if (Object.keys(toolStats).length === 0) {
|
|
126
|
-
return res.status(404).json({ error: 'No tool data available' });
|
|
127
|
-
}
|
|
128
|
-
// Calculate avgDuration and format response
|
|
129
|
-
const tools = {};
|
|
130
|
-
for (const [toolName, stats] of Object.entries(toolStats)) {
|
|
131
|
-
tools[toolName] = {
|
|
132
|
-
count: stats.count,
|
|
133
|
-
totalDuration: stats.totalDuration,
|
|
134
|
-
avgDuration: stats.count > 0 ? stats.totalDuration / stats.count : 0,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
res.json({ tools });
|
|
138
|
-
});
|
|
139
|
-
/**
|
|
140
|
-
* GET /api/telemetry/agents
|
|
141
|
-
*
|
|
142
|
-
* Returns per-agent token statistics for performance comparison.
|
|
143
|
-
*/
|
|
144
|
-
router.get('/agents', (_req, res) => {
|
|
145
|
-
const agentStats = getTokenStatsByAgent();
|
|
146
|
-
if (!agentStats || Object.keys(agentStats).length === 0) {
|
|
147
|
-
return res.status(404).json({ error: 'No agent data available' });
|
|
148
|
-
}
|
|
149
|
-
res.json(agentStats);
|
|
150
|
-
});
|
|
151
|
-
/**
|
|
152
|
-
* GET /api/telemetry/stories
|
|
153
|
-
*
|
|
154
|
-
* Returns per-story token statistics for budget tracking.
|
|
155
|
-
*/
|
|
156
|
-
router.get('/stories', (_req, res) => {
|
|
157
|
-
const storyStats = getTokenStatsByStory();
|
|
158
|
-
if (!storyStats || Object.keys(storyStats).length === 0) {
|
|
159
|
-
return res.status(404).json({ error: 'No story data available' });
|
|
160
|
-
}
|
|
161
|
-
res.json(storyStats);
|
|
162
|
-
});
|
|
163
|
-
return router;
|
|
164
|
-
}
|
|
1
|
+
// Re-export from @pennyfarthing/core (Story 98-17)
|
|
2
|
+
export * from '@pennyfarthing/core/dist/server/api/telemetry.js';
|
|
165
3
|
//# sourceMappingURL=telemetry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/api/telemetry.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/api/telemetry.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,cAAc,kDAAkD,CAAC"}
|