@opensassi/opencode 0.1.3 → 0.1.4

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 (37) hide show
  1. package/dashboard/dashboard.e2e.test.ts +247 -0
  2. package/dashboard/dist/index.d.ts +9 -0
  3. package/dashboard/dist/index.js +36 -0
  4. package/dashboard/dist/routes/api.d.ts +2 -0
  5. package/dashboard/dist/routes/api.js +215 -0
  6. package/dashboard/dist/services/cache.d.ts +13 -0
  7. package/dashboard/dist/services/cache.js +29 -0
  8. package/dashboard/dist/services/experiments.d.ts +11 -0
  9. package/dashboard/dist/services/experiments.js +108 -0
  10. package/dashboard/dist/services/git.d.ts +12 -0
  11. package/dashboard/dist/services/git.js +149 -0
  12. package/dashboard/dist/services/sessions.d.ts +25 -0
  13. package/dashboard/dist/services/sessions.js +208 -0
  14. package/dashboard/dist/services/specs.d.ts +9 -0
  15. package/dashboard/dist/services/specs.js +102 -0
  16. package/dashboard/dist/types.d.ts +173 -0
  17. package/dashboard/dist/types.js +1 -0
  18. package/dashboard/opencode.e2e.test.ts +100 -0
  19. package/dashboard/playwright.config.ts +11 -0
  20. package/dashboard/public/app.js +961 -0
  21. package/dashboard/public/index.html +29 -0
  22. package/dashboard/public/style.css +231 -0
  23. package/dashboard/src/index.ts +53 -0
  24. package/dashboard/src/routes/api.ts +235 -0
  25. package/dashboard/src/services/cache.ts +38 -0
  26. package/dashboard/src/services/experiments.ts +117 -0
  27. package/dashboard/src/services/git.ts +139 -0
  28. package/dashboard/src/services/sessions.ts +216 -0
  29. package/dashboard/src/services/specs.ts +95 -0
  30. package/dashboard/src/types.ts +168 -0
  31. package/dashboard/technical-specification.md +414 -0
  32. package/dashboard/test-api.sh +127 -0
  33. package/dashboard/tsconfig.json +16 -0
  34. package/lib/util/paths.js +9 -1
  35. package/package.json +9 -1
  36. package/scripts/dashboard.js +17 -0
  37. package/scripts/generate-daily-summaries.js +190 -0
@@ -0,0 +1,414 @@
1
+ # Technical Specification: deepenc-harness Dashboard
2
+
3
+ ## 1. Overview
4
+
5
+ The dashboard is a localhost-only HTTP web application that visualizes session data from `../sessions/daily/`, provides drill-down into individual session transcripts (`.json.bz2`), and overlays Git history for context. It runs as a subcommand of `deepenc-harness` and is isolated from the core harness code.
6
+
7
+ **Key design constraints:**
8
+ - Localhost only — zero security considerations, no auth, no CORS
9
+ - No bundler — vanilla HTML/CSS/JS served directly by Express
10
+ - CDN-loaded libraries only (Chart.js)
11
+ - Zero coupling to the core harness — `dashboard/` is a self-contained module
12
+
13
+ ---
14
+
15
+ ## 2. Directory Structure
16
+
17
+ ```
18
+ deepenc-harness/
19
+ dashboard/
20
+ technical-specification.md
21
+ src/
22
+ index.ts # Server entry point (Express setup, listen)
23
+ routes/
24
+ api.ts # All REST API route handlers
25
+ services/
26
+ sessions.ts # Daily file reader, session detail (.bz2) reader
27
+ git.ts # Git log/diff/stats via child_process
28
+ types.ts # Dashboard-specific type definitions
29
+ public/
30
+ index.html # SPA shell
31
+ style.css # All dashboard styles
32
+ app.js # Client-side router, chart rendering, fetch
33
+ ```
34
+
35
+ ---
36
+
37
+ ## 3. CLI Integration
38
+
39
+ Add `dashboard` as a new top-level command alongside `build`, `test`, and `ml`:
40
+
41
+ ```
42
+ deepenc-harness dashboard [options]
43
+
44
+ Options:
45
+ --port <n> HTTP server port (default: 3000)
46
+ --sessions <path> Path to sessions directory (default: ../sessions)
47
+ --host <addr> Bind address (default: 127.0.0.1)
48
+ ```
49
+
50
+ ### Implementation approach in `src/index.ts`
51
+
52
+ The existing CLI parser (`parseArgs`) handles `build`, `test`, and `ml`. A new branch for `dashboard` imports and starts the Express server:
53
+
54
+ ```typescript
55
+ // In src/index.ts parseArgs()
56
+ if (arg === 'dashboard' && !command) {
57
+ command = 'dashboard';
58
+ continue;
59
+ }
60
+
61
+ // In main():
62
+ } else if (command === 'dashboard') {
63
+ const { startDashboard } = await import('../dashboard/src/index.js');
64
+ startDashboard(port, sessionsPath, host);
65
+ }
66
+ ```
67
+
68
+ The dashboard module is imported lazily (dynamic `import()`) so its dependencies (express, etc.) are only loaded when the dashboard command is used. This keeps the core CLI fast.
69
+
70
+ Alternatively, to keep the dashboard fully isolated, the dashboard can be a separate npm workspace or even a standalone script. But for simplicity, lazy dynamic import from the core harness is sufficient.
71
+
72
+ ---
73
+
74
+ ## 4. HTTP Server (Express)
75
+
76
+ ### Dependencies
77
+
78
+ ```json
79
+ {
80
+ "dependencies": {
81
+ "express": "^5.1.0"
82
+ },
83
+ "devDependencies": {
84
+ "@types/express": "^5.0.0"
85
+ }
86
+ }
87
+ ```
88
+
89
+ Using Express 5.x (latest for Node 18+ ESM support). Only `express` is needed as a runtime dependency — `@types/express` is a dev dependency.
90
+
91
+ ### Server Setup (`dashboard/src/index.ts`)
92
+
93
+ ```
94
+ Express static middleware → dashboard/public/
95
+ REST API routes → /api/*
96
+ SPA catch-all → index.html for unknown routes (hash-based routing)
97
+ ```
98
+
99
+ Key server configuration:
100
+ - Static files from `dashboard/public/` served at `/`
101
+ - JSON response compression via Express built-in compression (or `compression` middleware)
102
+ - No CORS headers needed (localhost only)
103
+ - Error middleware returning JSON for API, HTML for static (or just JSON everywhere since SPA)
104
+ - Graceful shutdown on SIGINT/SIGTERM
105
+
106
+ ### Port resolution
107
+
108
+ 1. Check `--port` CLI arg
109
+ 2. Fallback to `DEEPENC_DASHBOARD_PORT` env var
110
+ 3. Default to `3000`
111
+
112
+ Print the URL on startup:
113
+ ```
114
+ => deepenc-harness dashboard running at http://127.0.0.1:3000
115
+ ```
116
+
117
+ ---
118
+
119
+ ## 5. Data Layer
120
+
121
+ ### 5.1 Sessions Directory Layout
122
+
123
+ ```
124
+ ../sessions/
125
+ daily/
126
+ YYYY-MM-DD.json # Daily summary + session breakdown
127
+ YYYY-MM-DD-<slug>-<id>.json.bz2 # Full session transcript (bzip2)
128
+ YYYY-MM-DD-<slug>-<id>.md # Human-readable session summary
129
+ YYYY-MM-DD-<slug>-<id>.sha256 # Integrity hash
130
+ ```
131
+
132
+ ### 5.2 Daily File Schema (two formats exist)
133
+
134
+ Format A (wrapped in `dashboard` key):
135
+ ```typescript
136
+ interface DailyFileFormatA {
137
+ dashboard: {
138
+ metadata: { generated_at: string; audited: boolean; audit_note: string };
139
+ daily_summary: DailySummary;
140
+ session_breakdown: SessionBreakdownEntry[];
141
+ };
142
+ }
143
+ ```
144
+
145
+ Format B (flat):
146
+ ```typescript
147
+ interface DailyFileFormatB {
148
+ date: string;
149
+ total_prompter_time_hours: number;
150
+ total_sme_time_hours: number;
151
+ ai_multiplier: number;
152
+ total_sessions: number;
153
+ top_subject_areas: SubjectArea[];
154
+ session_breakdown: SessionBreakdownEntry[];
155
+ }
156
+ ```
157
+
158
+ Both are normalized into a single `NormalizedDay` type on read.
159
+
160
+ ### 5.3 Types (`dashboard/src/types.ts`)
161
+
162
+ ```typescript
163
+ export interface NormalizedDay {
164
+ date: string;
165
+ metadata?: { generated_at: string; audited: boolean; audit_note: string };
166
+ total_prompter_time_hours: number;
167
+ total_sme_time_hours: number;
168
+ ai_multiplier: number;
169
+ total_sessions: number;
170
+ top_subject_areas: SubjectArea[];
171
+ session_breakdown: SessionBreakdownEntry[];
172
+ }
173
+
174
+ export interface SubjectArea {
175
+ name: string;
176
+ prompter_time_hours: number;
177
+ sme_time_hours: number;
178
+ ai_multiplier: number;
179
+ }
180
+
181
+ export interface SessionBreakdownEntry {
182
+ session_id: string;
183
+ duration_minutes: number;
184
+ prompter_time_minutes: number;
185
+ sme_time_minutes: number;
186
+ top_component_summary: string;
187
+ tags: string[];
188
+ human_confidence: "high" | "medium" | "low";
189
+ }
190
+
191
+ export interface SessionDetail {
192
+ info: {
193
+ id: string;
194
+ slug: string;
195
+ title: string;
196
+ agent: string;
197
+ model: { id: string; providerID: string };
198
+ summary: { additions: number; deletions: number; files: number };
199
+ time: { created: number; updated: number };
200
+ };
201
+ messages: SessionMessage[];
202
+ }
203
+
204
+ export interface SessionMessage {
205
+ info: {
206
+ role: string;
207
+ time: { created: number };
208
+ agent: string;
209
+ model: { providerID: string; modelID: string };
210
+ summary: { diffs: Array<{ path: string; type: string; lines: Record<string, number> }> };
211
+ id: string;
212
+ };
213
+ parts: Array<{ type: string; text?: string }>;
214
+ }
215
+
216
+ export interface GitLogEntry {
217
+ commit: string;
218
+ author: string;
219
+ date: string;
220
+ message: string;
221
+ files_changed: number;
222
+ insertions: number;
223
+ deletions: number;
224
+ }
225
+
226
+ export interface GitStats {
227
+ total_commits: number;
228
+ total_files_changed: number;
229
+ total_insertions: number;
230
+ total_deletions: number;
231
+ per_date: Record<string, { commits: number; insertions: number; deletions: number }>;
232
+ }
233
+ ```
234
+
235
+ ### 5.4 Session Data Service (`dashboard/src/services/sessions.ts`)
236
+
237
+ **Reading daily files:**
238
+ - `listDays(): Promise<string[]>` — readdir on `../sessions/daily/`, filter `*.json`, extract `YYYY-MM-DD`
239
+ - `getDay(date: string): Promise<NormalizedDay>` — read JSON, normalize from either Format A or Format B, cache result
240
+
241
+ **Reading session detail:**
242
+ - `getSession(sessionId: string): Promise<SessionDetail>` — find `{sessionId}.json.bz2` in `../sessions/`, decompress with `brotli`/`bzip2` via `child_process` or a pure-JS bzip2 library, parse JSON
243
+
244
+ **Search:**
245
+ - `search(query: string): Promise<SearchResult[]>` — iterate across all daily files and session details, match against `top_component_summary`, `tags`, and message text
246
+
247
+ ### 5.5 Git Service (`dashboard/src/services/git.ts`)
248
+
249
+ All Git operations shell out via `child_process.execFile` to `git` in the project root (`../` or `process.cwd()`):
250
+
251
+ - `getGitLog(since?: string, until?: string): Promise<GitLogEntry[]>` — `git log --oneline --stat --since=... --until=...`
252
+ - `getGitStats(): Promise<GitStats>` — `git shortlog -sn` and `git diff --stat` aggregated by day
253
+ - `getCommitDiff(commit: string): Promise<string>` — `git show <commit>` for full diff
254
+
255
+ ### 5.6 Caching
256
+
257
+ Simple in-memory Map-based cache in `dashboard/src/services/cache.ts`:
258
+ - TTL-based expiry (default 60s for daily files, 300s for session details, no cache for git to keep it live)
259
+ - Manual invalidation when a new daily file appears (detected via fs.watch on `../sessions/daily/`)
260
+
261
+ ---
262
+
263
+ ## 6. REST API Endpoints
264
+
265
+ ### 6.1 Sessions & Daily
266
+
267
+ | Method | Path | Description | Response |
268
+ |--------|------|-------------|----------|
269
+ | GET | `/api/days` | List available daily dates | `{ days: string[] }` |
270
+ | GET | `/api/days/:date` | Full normalized day payload | `NormalizedDay` |
271
+ | GET | `/api/days/latest` | Most recent day | `NormalizedDay` |
272
+ | GET | `/api/sessions` | All sessions across all days (flat, paginated) | `{ sessions: SessionBreakdownEntry[], total: number }` |
273
+ | GET | `/api/sessions/:sessionId` | Session detail + transcript | `{ summary: SessionBreakdownEntry, detail: SessionDetail }` |
274
+ | GET | `/api/stats` | Cross-day aggregate statistics | `{ total_days, total_sessions, total_prompter_time, total_sme_time, avg_multiplier, per_day: NormalizedDay[] }` |
275
+ | GET | `/api/search?q=string` | Full-text search | `{ results: SearchResult[] }` |
276
+
277
+ ### 6.2 Git
278
+
279
+ | Method | Path | Description | Response |
280
+ |--------|------|-------------|----------|
281
+ | GET | `/api/git/log?since=YYYY-MM-DD&until=YYYY-MM-DD` | Git commit log | `{ commits: GitLogEntry[] }` |
282
+ | GET | `/api/git/stats` | Aggregated git contribution stats | `GitStats` |
283
+ | GET | `/api/git/commit/:hash` | Single commit diff | `{ diff: string }` |
284
+
285
+ ### 6.3 Health
286
+
287
+ | Method | Path | Description | Response |
288
+ |--------|------|-------------|----------|
289
+ | GET | `/api/health` | Liveness check | `{ status: "ok", days_count: number, sessions_count: number }` |
290
+
291
+ ---
292
+
293
+ ## 7. Frontend (Vanilla HTML/CSS/JS)
294
+
295
+ ### 7.1 Architecture
296
+
297
+ Single-page application using hash-based routing:
298
+
299
+ ```
300
+ #/ → Overview dashboard
301
+ #/daily/2026-05-11 → Single day detail
302
+ #/session/<id> → Session transcript reader
303
+ #/git → Git timeline overlay
304
+ #/search?q=... → Search results
305
+ ```
306
+
307
+ Routing implemented in `app.js` via `hashchange` event. No framework — pure DOM manipulation.
308
+
309
+ ### 7.2 Pages
310
+
311
+ **Overview Dashboard (`#/`):**
312
+ - Summary cards: total sessions, total prompter hours, total SME hours, avg AI multiplier
313
+ - AI multiplier trend chart (line chart, Chart.js)
314
+ - Top subject areas bar chart (top 15 by SME hours)
315
+ - Session count per day bar chart
316
+ - Latest sessions table (10 most recent)
317
+
318
+ **Daily Detail (`#/daily/:date`):**
319
+ - Date header with aggregate numbers
320
+ - Session breakdown table (sortable by duration, prompter time, SME time)
321
+ - Pie chart of session time distribution
322
+ - Tag cloud (top tags by frequency)
323
+ - Subject area breakdown bar chart
324
+
325
+ **Session Detail (`#/session/:id`):**
326
+ - Session metadata card (title, agent, model, duration, file stats)
327
+ - Tag badges
328
+ - Summary description
329
+ - Expandable message transcript
330
+ - File diff list (from `messages[].info.summary.diffs`)
331
+ - Link to git commit if applicable
332
+
333
+ **Git Timeline (`#/git`):**
334
+ - Commits per day bar chart overlaid on session time
335
+ - Commit list with expandable diffs
336
+
337
+ **Search (`#/search?q=...`):**
338
+ - Results grouped by session
339
+ - Highlighted matching text in summaries
340
+
341
+ ### 7.3 Chart.js Integration
342
+
343
+ Loaded from CDN:
344
+ ```html
345
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
346
+ ```
347
+
348
+ Charts used:
349
+ - **Line chart** — AI multiplier trend, SME time trend
350
+ - **Bar chart** — top subject areas, session count per day, daily prompter/SME comparison
351
+ - **Pie/doughnut** — session time distribution, tag composition
352
+ - **Stacked bar** — prompter vs SME time per session
353
+
354
+ Chart instances are destroyed and recreated on page navigation to avoid canvas conflicts.
355
+
356
+ ### 7.4 Styling
357
+
358
+ - Dark theme (matching a development tool aesthetic)
359
+ - CSS custom properties for theming
360
+ - Grid/flexbox layout
361
+ - Responsive within reason (desktop-first, localhost)
362
+ - No CSS framework — lightweight, minimal styles
363
+
364
+ ### 7.5 Client-Side Data Fetching
365
+
366
+ All API calls via `fetch()`. Helper function:
367
+ ```javascript
368
+ async function api(path) {
369
+ const res = await fetch(path);
370
+ if (!res.ok) throw new Error(`API error: ${res.status}`);
371
+ return res.json();
372
+ }
373
+ ```
374
+
375
+ ---
376
+
377
+ ## 8. Implementation Plan
378
+
379
+ ### Phase 1: Scaffolding
380
+ 1. Create `dashboard/` directory structure
381
+ 2. Add `express` and `@types/express` to root `package.json` (or workspace)
382
+ 3. Implement `dashboard/src/index.ts` — Express server with static + route registration
383
+ 4. Integrate `dashboard` command into `src/index.ts` CLI parser
384
+
385
+ ### Phase 2: Data Layer
386
+ 5. Implement `dashboard/src/types.ts`
387
+ 6. Implement `dashboard/src/services/sessions.ts` — daily file reader + normalizer + bz2 reader
388
+ 7. Implement `dashboard/src/services/git.ts` — git log/stats via child_process
389
+ 8. Implement `dashboard/src/services/cache.ts` — in-memory TTL cache
390
+
391
+ ### Phase 3: API
392
+ 9. Implement `dashboard/src/routes/api.ts` — all endpoints
393
+
394
+ ### Phase 4: Frontend
395
+ 10. Create `dashboard/public/index.html` — SPA shell, hash router, page container
396
+ 11. Create `dashboard/public/style.css` — dark theme, layout, components
397
+ 12. Create `dashboard/public/app.js` — routing, API client, Chart.js rendering, all pages
398
+
399
+ ### Phase 5: Polish
400
+ 13. Start-up banner with URL
401
+ 14. Graceful shutdown
402
+ 15. Error handling for missing sessions directory, malformed JSON, bz2 read failures
403
+ 16. `fs.watch` on sessions directory for auto-refresh (optional — SSE push to client)
404
+
405
+ ---
406
+
407
+ ## 9. Non-Goals / Future Considerations
408
+
409
+ - **Authentication**: Not needed — localhost only.
410
+ - **Bundling**: Not needed — vanilla JS with CDN Chart.js is sufficient.
411
+ - **WebSockets**: Nice-to-have for live reload when new session data appears. Initial implementation uses manual refresh. SSE could be added later.
412
+ - **CSS framework**: Not needed — custom styles are minimal and keep zero dependencies.
413
+ - **Testing**: The dashboard is a read-only visualization tool. Manual testing is sufficient for initial release. If automated tests are desired, use supertest for API + Playwright for E2E.
414
+ - **npm workspace**: Not needed initially. Dashboard lives in `dashboard/` and is imported lazily. If the dependency graph becomes complex, it can be promoted to a workspace.
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env bash
2
+ # Curl-based API tests for the deepenc-harness dashboard
3
+ # Usage: bash dashboard/test-api.sh [port]
4
+ set -euo pipefail
5
+
6
+ PORT="${1:-3098}"
7
+ BASE="http://127.0.0.1:$PORT"
8
+ PASS=0
9
+ FAIL=0
10
+
11
+ ok() { PASS=$((PASS+1)); echo " PASS: $1"; }
12
+ fail() { FAIL=$((FAIL+1)); echo " FAIL: $1"; }
13
+
14
+ check_json() {
15
+ local desc="$1" url="$2" expected="$3"
16
+ local body
17
+ body=$(curl -sf "$url" 2>/dev/null) || { fail "$desc (curl failed)"; return; }
18
+ if echo "$body" | python3 -c "
19
+ import sys, json
20
+ try:
21
+ d = json.load(sys.stdin)
22
+ exec('''
23
+ $expected
24
+ ''')
25
+ sys.exit(0)
26
+ except Exception as e:
27
+ print(' expected:', e)
28
+ sys.exit(1)
29
+ " 2>/dev/null; then
30
+ ok "$desc"
31
+ else
32
+ fail "$desc"
33
+ fi
34
+ }
35
+
36
+ check_status() {
37
+ local desc="$1" url="$2" expected_status="$3"
38
+ local status
39
+ status=$(curl -s -o /dev/null -w "%{http_code}" "$url" 2>/dev/null) || { fail "$desc (curl failed)"; return; }
40
+ if [ "$status" = "$expected_status" ]; then
41
+ ok "$desc"
42
+ else
43
+ fail "$desc (expected $expected_status, got $status)"
44
+ fi
45
+ }
46
+
47
+ echo "Starting dashboard server on port $PORT..."
48
+ node lib/index.js dashboard --port "$PORT" > /tmp/dash-test.log 2>&1 &
49
+ SERVER_PID=$!
50
+ trap "kill $SERVER_PID 2>/dev/null; exit 1" INT TERM
51
+
52
+ sleep 2
53
+
54
+ echo ""
55
+ echo "=== API Tests ==="
56
+ echo ""
57
+
58
+ check_json "Health endpoint returns ok" \
59
+ "$BASE/api/health" \
60
+ "assert d['status'] == 'ok'"
61
+
62
+ check_json "Days endpoint lists days" \
63
+ "$BASE/api/days" \
64
+ "assert len(d['days']) >= 2; assert '2026-05-11' in d['days']"
65
+
66
+ check_json "Latest day returns correct date" \
67
+ "$BASE/api/days/latest" \
68
+ "assert d['date'] == '2026-05-12'"
69
+
70
+ check_json "Specific day returns correct data" \
71
+ "$BASE/api/days/2026-05-11" \
72
+ "assert d['total_sessions'] == 10; assert d['total_prompter_time_hours'] == 18.8"
73
+
74
+ check_status "Missing day returns 404" \
75
+ "$BASE/api/days/2099-01-01" 404
76
+
77
+ check_json "Sessions endpoint returns list" \
78
+ "$BASE/api/sessions" \
79
+ "assert d['total'] >= 18; assert len(d['sessions']) > 0"
80
+
81
+ check_json "Session detail returns summary + detail" \
82
+ "$BASE/api/sessions/2026-05-12-asm-optimizer-dq-implementation" \
83
+ "assert d['summary'] is not None; assert d['detail'] is not None"
84
+
85
+ check_status "Missing session returns 404" \
86
+ "$BASE/api/sessions/nonexistent-session" 404
87
+
88
+ check_json "Stats returns cross-day aggregates" \
89
+ "$BASE/api/stats" \
90
+ "assert d['total_days'] == 2; assert d['total_sessions'] == 18"
91
+
92
+ check_json "Search by summary returns results" \
93
+ "$BASE/api/search?q=SIMD" \
94
+ "assert len(d['results']) > 0"
95
+
96
+ check_json "Search with no match returns empty" \
97
+ "$BASE/api/search?q=zzz_nonexistent_zzz" \
98
+ "assert len(d['results']) == 0"
99
+
100
+ check_json "Search by transcript content returns results" \
101
+ "$BASE/api/search?q=AVX2" \
102
+ "assert len(d['results']) > 0"
103
+
104
+ check_json "Git log returns commits" \
105
+ "$BASE/api/git/log" \
106
+ "assert len(d['commits']) > 0"
107
+
108
+ check_json "Git stats returns per_date" \
109
+ "$BASE/api/git/stats" \
110
+ "assert d['total_commits'] > 0; assert 'per_date' in d"
111
+
112
+ check_json "Git commit diff returns content" \
113
+ "$BASE/api/git/commit/HEAD" \
114
+ "assert len(d['diff']) > 100"
115
+
116
+ check_status "Missing commit returns 404" \
117
+ "$BASE/api/git/commit/0000000000000000000000000000000000000000" 404
118
+
119
+ echo ""
120
+ echo "=== Results ==="
121
+ echo "Passed: $PASS"
122
+ echo "Failed: $FAIL"
123
+
124
+ kill $SERVER_PID 2>/dev/null
125
+ wait $SERVER_PID 2>/dev/null || true
126
+
127
+ [ "$FAIL" -eq 0 ] && exit 0 || exit 1
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "declaration": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "skipLibCheck": true,
13
+ "isolatedModules": true
14
+ },
15
+ "include": ["src/**/*.ts"]
16
+ }
package/lib/util/paths.js CHANGED
@@ -1,11 +1,19 @@
1
1
  import { fileURLToPath } from 'node:url'
2
2
  import { dirname, resolve } from 'node:path'
3
+ import { existsSync } from 'node:fs'
3
4
 
4
5
  const __dirname = dirname(fileURLToPath(import.meta.url))
5
6
  export const PKG_ROOT = resolve(__dirname, '../..')
6
7
 
7
8
  export function resolveScript(path) {
8
- return resolve(PKG_ROOT, 'scripts', path)
9
+ const base = resolve(PKG_ROOT, 'scripts', path)
10
+ if (existsSync(base)) return base
11
+ if (existsSync(base + '.js')) return base + '.js'
12
+ if (existsSync(base + '.mjs')) return base + '.mjs'
13
+ if (existsSync(base + '.sh')) return base + '.sh'
14
+ const asDir = resolve(base, 'index.js')
15
+ if (existsSync(asDir)) return asDir
16
+ return base
9
17
  }
10
18
 
11
19
  export function resolveSkillScript(skillName, path) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opensassi/opencode",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Agent skill harness for opencode — bootstrap, system-design, git workflow, profiling, and more",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,6 +15,7 @@
15
15
  "lib/",
16
16
  "skills/",
17
17
  "scripts/",
18
+ "dashboard/",
18
19
  "AGENTS.md",
19
20
  "skills-index.json",
20
21
  "!scripts/FlameGraph/",
@@ -28,12 +29,19 @@
28
29
  "validate-all": "node scripts/extract-artifacts.js --all && node scripts/test-artifacts.js",
29
30
  "verify-animation": "node scripts/verify-artifact.js",
30
31
  "check-artifacts": "node scripts/check-artifacts.js",
32
+ "build:dashboard": "tsc -p dashboard/tsconfig.json",
33
+ "test:dashboard": "npx playwright test --config dashboard/playwright.config.ts",
31
34
  "start": "bin/opencode.js"
32
35
  },
33
36
  "engines": {
34
37
  "node": ">=22"
35
38
  },
39
+ "dependencies": {
40
+ "express": "^5.2.1"
41
+ },
36
42
  "devDependencies": {
43
+ "@playwright/test": "^1.52.0",
44
+ "@types/express": "^5.0.0",
37
45
  "playwright": "^1.60.0",
38
46
  "sharp": "^0.34.5"
39
47
  }
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import { startDashboard } from '../dashboard/dist/index.js'
3
+
4
+ const args = process.argv.slice(2)
5
+ const portIdx = args.indexOf('--port')
6
+ const hostIdx = args.indexOf('--host')
7
+ const sessionsIdx = args.indexOf('--sessions')
8
+ const repoIdx = args.indexOf('--repo')
9
+ const gitSinceIdx = args.indexOf('--git-since')
10
+
11
+ startDashboard({
12
+ port: portIdx !== -1 ? parseInt(args[portIdx + 1], 10) : undefined,
13
+ host: hostIdx !== -1 ? args[hostIdx + 1] : undefined,
14
+ sessionsDir: sessionsIdx !== -1 ? args[sessionsIdx + 1] : undefined,
15
+ repoDir: repoIdx !== -1 ? args[repoIdx + 1] : undefined,
16
+ gitSince: gitSinceIdx !== -1 ? args[gitSinceIdx + 1] : undefined,
17
+ })