specrails-hub 1.54.1 → 1.54.3

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 (69) hide show
  1. package/README.md +78 -266
  2. package/client/dist/assets/{ActivityFeedPage-DzDSZ3Oa.js → ActivityFeedPage-Bst7JQBD.js} +1 -1
  3. package/client/dist/assets/AgentsPage-CQSsFPUf.js +86 -0
  4. package/client/dist/assets/AnalyticsPage-BXSGt62I.js +1 -0
  5. package/client/dist/assets/{BarChart-BEDxM3GM.js → BarChart-D0Js4qm-.js} +2 -2
  6. package/client/dist/assets/DocsDialog-Dx-XFezQ.js +11 -0
  7. package/client/dist/assets/DocsPage-DBe0KLC6.js +11 -0
  8. package/client/dist/assets/{ExportDropdown-C7gsLiLV.js → ExportDropdown-CHJbkux7.js} +1 -1
  9. package/client/dist/assets/HubAnalyticsPage-CExwOxYs.js +1 -0
  10. package/client/dist/assets/{IntegrationsPage-CfggPfcY.js → IntegrationsPage-Cq0TXYaZ.js} +2 -2
  11. package/client/dist/assets/JobDetailPage-C00ccK2a.js +16 -0
  12. package/client/dist/assets/JobsPage-BgrKbNeS.js +1 -0
  13. package/client/dist/assets/{dist-js-Cgr0qnSW.js → dist-js-BNqVf1xQ.js} +1 -1
  14. package/client/dist/assets/{dist-js-DTole_8A.js → dist-js-CEpsPzWv.js} +1 -1
  15. package/client/dist/assets/index-KaHNgvWR.css +2 -0
  16. package/client/dist/assets/index-NFgJciDY.js +140 -0
  17. package/client/dist/assets/{lib-DimzYFtz.js → lib-CKFRx_1H.js} +2 -2
  18. package/client/dist/assets/{useProjectCache-CttYIAMn.js → useProjectCache-bmnxPDIP.js} +1 -1
  19. package/client/dist/index.html +2 -2
  20. package/docs/README.md +51 -0
  21. package/docs/cli.md +178 -0
  22. package/docs/creating-specs.md +176 -0
  23. package/docs/customizing.md +173 -0
  24. package/docs/getting-started.md +138 -0
  25. package/docs/internals/README.md +22 -0
  26. package/docs/internals/api-reference.md +461 -0
  27. package/docs/{engineering → internals}/architecture.md +1 -1
  28. package/docs/{engineering → internals}/configuration.md +13 -10
  29. package/docs/{product → internals}/openspec-workflow.md +2 -2
  30. package/docs/running-pipelines.md +211 -0
  31. package/docs/terminal.md +132 -0
  32. package/docs/tracking-cost.md +125 -0
  33. package/package.json +1 -1
  34. package/server/dist/ai-invocations.js +2 -0
  35. package/server/dist/chat-manager.js +42 -13
  36. package/server/dist/context-budget.js +113 -0
  37. package/server/dist/context-scope.js +272 -0
  38. package/server/dist/contract-refine-runner.js +485 -0
  39. package/server/dist/db.js +23 -13
  40. package/server/dist/docs-router.js +111 -31
  41. package/server/dist/explore-contract-refine.js +415 -0
  42. package/server/dist/explore-smash.js +450 -0
  43. package/server/dist/project-router.js +431 -21
  44. package/server/dist/smash-runner.js +638 -0
  45. package/server/dist/spending.js +63 -4
  46. package/server/dist/ticket-store.js +62 -2
  47. package/client/dist/assets/AgentsPage-NYn1AGPs.js +0 -86
  48. package/client/dist/assets/AnalyticsPage-DiiXVQAu.js +0 -1
  49. package/client/dist/assets/DocsDialog-flmuTVOr.js +0 -11
  50. package/client/dist/assets/DocsPage-C7r5RiZM.js +0 -11
  51. package/client/dist/assets/HubAnalyticsPage-Ca6I4pd4.js +0 -1
  52. package/client/dist/assets/JobDetailPage-BAlePf8f.js +0 -16
  53. package/client/dist/assets/JobsPage-Ut47fmBM.js +0 -1
  54. package/client/dist/assets/index-CkcoghGL.css +0 -2
  55. package/client/dist/assets/index-_3ZOPMZV.js +0 -133
  56. package/docs/engineering/api-reference.md +0 -267
  57. package/docs/engineering/rfcs/RFC-002-specrails-tech-api-v1.md +0 -167
  58. package/docs/engineering/rfcs/RFC-003-core-hub-sync.md +0 -227
  59. package/docs/engineering/spikes/specrails-hub-capabilities-spike.md +0 -299
  60. package/docs/general/getting-started.md +0 -129
  61. package/docs/general/platform-overview.md +0 -107
  62. package/docs/product/features.md +0 -162
  63. package/docs/product/tickets.md +0 -170
  64. package/docs/product/workflows.md +0 -107
  65. package/docs/terminal-panel.md +0 -75
  66. /package/docs/{operations/runbook.md → internals/operations-runbook.md} +0 -0
  67. /package/docs/{profiles-quick-start.md → internals/profiles.md} +0 -0
  68. /package/docs/{macos.md → platforms/macos.md} +0 -0
  69. /package/docs/{windows.md → platforms/windows.md} +0 -0
package/README.md CHANGED
@@ -1,313 +1,125 @@
1
- # specrails hub
1
+ # specrails-hub
2
2
 
3
- A local dashboard and CLI for managing all your [specrails-core](https://github.com/fjpulidop/specrails-core) projects from a single interface. Visualizes the AI pipeline phases (Architect, Developer, Reviewer, Ship), streams logs in real-time, and lets you launch commands from the browser, terminal, or native desktop app.
3
+ **The local dashboard for shipping software with AI agents.**
4
4
 
5
- ## Features
5
+ specrails-hub turns "I'll let Claude do it" into a workflow you can see, steer, and trust. Draft specs in conversation with Claude, drag them onto execution rails, and watch the pipeline ship — all from one window, on your laptop, with every cost tracked.
6
6
 
7
- - **Multi-project hub** register multiple specrails projects and switch between them with browser-style tabs
8
- - **Live pipeline visualization** — see Architect, Developer, Reviewer, and Ship phases update in real-time
9
- - **Streaming logs** — all `claude` CLI output streamed via WebSocket to the browser
10
- - **Ticket panel** — visual interface for local tickets with List, Kanban, and Post-it views; real-time sync with CLI agents
11
- - **Command launcher** — organized into Discovery (propose-spec, auto-propose specs, auto-select specs) and Delivery (implement, batch-implement) sections; other commands available in a collapsible group
12
- - **Analytics** — cost, duration, token usage, and throughput metrics per project
13
- - **Conversations** — full-page chat interface with Claude, scoped per project
14
- - **Native desktop app (macOS/Windows/Linux via Tauri)** — installable app that bundles the server; macOS uses native traffic lights with a custom titlebar, Windows/Linux use a custom frameless titlebar
15
- - **Demo mode for offline previews** — run the UI with fixture data, no server required
16
- - **Persistent spec generation state across refreshes** — in-progress spec generation survives page reloads and project switches
17
- - **`specrails-hub` CLI** — terminal bridge that auto-routes commands to the correct project
7
+ It's a local-first companion to [specrails-core](https://github.com/fjpulidop/specrails-core): one window for all your projects, one place to manage specs and pipelines, one place to see what AI cost you this week.
18
8
 
19
- ## Prerequisites
20
-
21
- - Node.js 20+ (specrails-core ≥ 4.2.0 dependency)
22
- - `claude` CLI on your PATH ([Claude Code](https://claude.com/claude-code))
23
- - At least one project with specrails-core installed (`npx specrails-core@latest init`)
24
- - **Windows users:** see [docs/windows.md](docs/windows.md) for Windows 10/11 specifics (PowerShell ExecutionPolicy, SmartScreen warning, ARM64 emulation)
9
+ > 100% local. Single user. No accounts. No telemetry leaving your machine. Your code stays on your laptop unless **you** spawn an agent against it.
25
10
 
26
- ## Installation
11
+ ## What you can do with it
27
12
 
28
- ```bash
29
- npm install -g specrails-hub
30
- ```
13
+ - **Draft a spec by talking to Claude** — open Explore, describe what you want; the live draft updates each turn. Save it as a draft and come back later, or commit when it looks right.
14
+ - **Generate a spec in one shot** — Quick mode for when you already know what you want. Optionally enrich it with a "Contract Layer" of names, shapes, invariants, and a file touch list.
15
+ - **Drag specs onto execution rails** — each rail is an independent lane. Run multiple specs in parallel, with different agent profiles per rail.
16
+ - **Compare two specs side by side** — drag any spec modal to the edge of the screen; a picker of your todo specs appears on the other side. Pick one and they live next to each other. Tablet-style.
17
+ - **Split a big epic** — SMASH a parent spec into a family of sub-specs in one click; the children carry short summaries on their cards.
18
+ - **Refine a spec in place** — *Continue Editing* reopens any draft / todo / backlog spec in Explore, with the original conversation resumed if there was one.
19
+ - **Track every AI cost** — every Claude CLI invocation across rails, Quick spec, Explore turns, and AI edits is recorded. The Analytics page shows your burn rate, top tickets, and lets you export CSV.
20
+ - **Customise everything** — three themes, font sizes, terminal preferences, agent profiles, plugin integrations (Serena bundled today).
31
21
 
32
- ## Quick Start
22
+ ## How specrails-hub looks
33
23
 
34
- ```bash
35
- # Start the hub server
36
- specrails-hub start
37
-
38
- # Register a project
39
- specrails-hub add /path/to/your/project
40
-
41
- # Open in browser
42
- open http://localhost:4200
43
24
  ```
44
-
45
- On first launch with no projects, you'll see a welcome screen with an "Add your first project" button.
46
-
47
- ## Desktop App
48
-
49
- ```bash
50
- npm run tauri dev # Run desktop app in development mode
51
- npm run tauri build # Build production desktop app
52
- ```
53
-
54
- macOS: native traffic lights with a custom drag region and centered search pill. Windows/Linux: custom frameless titlebar with SR icon, app name, and window controls.
55
-
56
- ## Architecture
57
-
58
- ```
59
- ~/.specrails/
60
- hub.sqlite # project registry (name, path, slug)
61
- manager.pid # server PID for clean shutdown
62
- projects/
63
- my-app/jobs.sqlite # isolated DB per project (jobs, events, chat)
64
- api-srv/jobs.sqlite
25
+ ┌──────────┬───────────────────────────────────────────────────┐
26
+ │ │ Dashboard · Jobs · Analytics · Agents · ⚙ │
27
+ │ Arc │ ──────────────────────────────────────────────── │
28
+ │ side- │ │
29
+ │ bar │ SpecsBoard │ Rails │
30
+ │ │ (your specs) │ (execution lanes) │
31
+ projects │ │ │
32
+ │ │ #1 Login flow ● │ ▶ Rail 1 #1 #2 │
33
+ │ + Add │ #2 Webhook retry │ [profile: default] │
34
+ │ │ #3 Cost limits ● │ │
35
+ │ │ │ ▶ Rail 2 running │
36
+ │ │ │ [profile: budget] │
37
+ │ ⚙ │ │ │
38
+ └──────────┴───────────────────────────────────────────────────┘
39
+ ⌥ Terminal panel (Cmd+J)
65
40
  ```
66
41
 
67
- A single Express process (port 4200) manages all projects. Each project gets its own:
68
-
69
- - **SQLite database** — jobs, events, chat conversations
70
- - **QueueManager** — independent job queue (sequential within a project, parallel across projects)
71
- - **ChatManager** — isolated Claude conversations
72
-
73
- ```
74
- ┌─────────────────────────────────────────────────────┐
75
- │ Express Server (port 4200) │
76
- │ │
77
- │ ProjectRegistry │
78
- │ ├── Project A → { db, queue, chat, cwd } │
79
- │ ├── Project B → { db, queue, chat, cwd } │
80
- │ └── Project C → { db, queue, chat, cwd } │
81
- │ │
82
- │ Routes: │
83
- │ /api/hub/* → hub-level operations │
84
- │ /api/projects/:id/* → project-scoped actions │
85
- └─────────────────────────────────────────────────────┘
86
- ```
42
+ ## Quick start
87
43
 
88
- ### Three-layer monorepo
44
+ ```bash
45
+ # 1. Install
46
+ npm install -g specrails-hub
89
47
 
90
- ```
91
- specrails-hub/
92
- ├── server/ → Express + WebSocket + SQLite (TypeScript, CommonJS)
93
- ├── client/ → React + Vite + Tailwind v4 (TypeScript, ESM)
94
- ├── cli/ → specrails-hub CLI bridge (TypeScript, CommonJS)
95
- └── src-tauri/ → Tauri desktop shell (Rust + bundled server sidecar)
96
- ```
48
+ # 2. Start the hub
49
+ specrails-hub start
97
50
 
98
- ## UI Overview
51
+ # 3. Add a project from the CLI…
52
+ specrails-hub add /path/to/your/project
99
53
 
100
- ```
101
- ┌───────────────────────────────────────────────────────┐
102
- │ specrails hub [my-app ●] [api-srv] [dashboard] [+]│
103
- │ Home Analytics Conversations ⚙ │
104
- │───────────────────────────────────────────────────────│
105
- │ │
106
- │ Command grid, recent jobs, pipeline phases │
107
- │ │
108
- └───────────────────────────────────────────────────────┘
54
+ # …or click "+ Add project" in the dashboard sidebar at
55
+ # http://127.0.0.1:4200
109
56
  ```
110
57
 
111
- - **Tabs** one per project, green dot when a job is active
112
- - **Home** — command grid (Discovery and Delivery sections), ticket panel, recent jobs, pipeline phase indicators
113
- - **Tickets** — List, Kanban, and Post-it views of local tickets; real-time sync with CLI agents
114
- - **Analytics** — cost and token metrics
115
- - **Conversations** — Claude chat sessions scoped to the project
116
- - **Settings** (gear icon) — global hub configuration, registered projects
58
+ If the project doesn't have specrails-core yet, the setup wizard walks you through installing it. One flow, no tier picker. Total time: about a minute on a warm cache.
117
59
 
118
- ## CLI: `specrails-hub`
60
+ **Prefer a desktop app?** Download a signed build for macOS or Windows from `https://specrails.dev/downloads/specrails-hub/latest/`. The desktop app bundles the server, so you don't need a separate `start` command.
119
61
 
120
- ### Hub management
62
+ ## Prerequisites
121
63
 
122
- | Command | Description |
123
- |---------|-------------|
124
- | `specrails-hub start [--port N]` | Start the hub server (default port 4200) |
125
- | `specrails-hub stop` | Stop the hub server |
126
- | `specrails-hub status` | Show hub state and registered projects |
127
- | `specrails-hub list` | List all registered projects |
128
- | `specrails-hub add <path>` | Register a project |
129
- | `specrails-hub remove <id>` | Unregister a project |
64
+ - **Node.js 20+**
65
+ - **`claude` CLI** ([Claude Code](https://claude.com/claude-code))
66
+ - **`git`**
67
+ - (Optional) **`uv`** if you want to use the Serena plugin
130
68
 
131
- ### Running commands
69
+ Set `ANTHROPIC_API_KEY` in your shell so the Claude CLI can authenticate. On macOS, the desktop app handles Homebrew/Volta/nvm paths automatically — see [docs/platforms/macos.md](docs/platforms/macos.md).
132
70
 
133
- ```bash
134
- cd ~/repos/my-app
135
- specrails-hub implement #42 # auto-detects project from CWD
136
- specrails-hub product-backlog # routes to the correct project
137
- specrails-hub "any raw prompt" # passes directly to claude
138
- ```
71
+ ## Documentation
139
72
 
140
- `specrails-hub` detects which project you're in by matching your current directory against registered projects. If the hub isn't running, it falls back to invoking `claude` directly.
73
+ User guides:
141
74
 
142
- ### Options
75
+ | Guide | What it covers |
76
+ |-------|----------------|
77
+ | [Getting started](docs/getting-started.md) | Install, register a project, run your first pipeline |
78
+ | [Creating specs](docs/creating-specs.md) | Quick vs Explore, drafts, SMASH, Compare, Continue Editing |
79
+ | [Running pipelines](docs/running-pipelines.md) | Rails, jobs, agent profiles, plugins |
80
+ | [Tracking cost](docs/tracking-cost.md) | Analytics page, exports, per-ticket spending |
81
+ | [Customising the hub](docs/customizing.md) | Themes, settings, telemetry, kill switches |
82
+ | [Terminal panel](docs/terminal.md) | Keyboard shortcuts, shell integration, drag-and-drop |
83
+ | [CLI reference](docs/cli.md) | Every command grouped by task |
143
84
 
144
- | Flag | Description |
145
- |------|-------------|
146
- | `--port <n>` | Override default port (4200) |
147
- | `--status` | Print hub/manager state |
148
- | `--jobs` | Print recent job history |
149
- | `--help` | Show usage |
85
+ Platform notes:
150
86
 
151
- ### Output
87
+ - [macOS](docs/platforms/macos.md) — GUI-launch PATH, broken-symlink detection
88
+ - [Windows](docs/platforms/windows.md) — installer formats, SmartScreen, ConPTY
152
89
 
153
- ```
154
- [specrails-hub] running: /sr:implement #42
155
- [specrails-hub] routing via hub → project my-app (a1b2c3d4)
156
- ... (live claude output) ...
157
- [specrails-hub] done duration: 4m32s cost: $0.08 tokens: 12,400 exit: 0
158
- ```
90
+ Contributing or extending:
159
91
 
160
- ## API
161
-
162
- ### Hub routes
163
-
164
- | Method | Path | Description |
165
- |--------|------|-------------|
166
- | GET | `/api/hub/state` | Hub version, project count, uptime |
167
- | GET | `/api/hub/projects` | List registered projects |
168
- | POST | `/api/hub/projects` | Register a project (`{ path }`) |
169
- | DELETE | `/api/hub/projects/:id` | Unregister a project |
170
- | GET | `/api/hub/resolve?path=<p>` | Find project by filesystem path |
171
- | GET | `/api/hub/settings` | Global settings |
172
- | PUT | `/api/hub/settings` | Update global settings |
173
-
174
- ### Project-scoped routes
175
-
176
- All under `/api/projects/:projectId/`:
177
-
178
- | Method | Path | Description |
179
- |--------|------|-------------|
180
- | POST | `/spawn` | Launch a command |
181
- | GET | `/jobs` | Job history |
182
- | GET | `/jobs/:id` | Job detail |
183
- | GET | `/analytics` | Cost and usage metrics |
184
- | GET | `/config` | Available commands |
185
- | POST | `/chat/conversations` | Create chat conversation |
186
- | GET | `/chat/conversations` | List conversations |
187
- | POST | `/hooks/events` | Pipeline phase notifications |
188
- | GET | `/tickets` | List tickets (`?status=`, `?label=`, `?q=` filters supported) |
189
- | GET | `/tickets/:id` | Get ticket by ID |
190
- | POST | `/tickets` | Create ticket |
191
- | PATCH | `/tickets/:id` | Update ticket fields |
192
- | DELETE | `/tickets/:id` | Delete ticket |
193
- | GET | `/integration-contract` | Read project integration-contract.json |
92
+ - [`docs/internals/`](docs/internals/) — architecture, REST reference, operations runbook, OpenSpec workflow, profile internals
194
93
 
195
94
  ## Development
196
95
 
197
96
  ```bash
198
97
  git clone https://github.com/fjpulidop/specrails-hub.git
199
98
  cd specrails-hub
200
- npm install # install root (server + CLI) dependencies
201
- cd client && npm install && cd .. # install client dependencies separately
202
- npm run dev # starts server (4200) + client (4201) concurrently
99
+ npm install # root deps (server + CLI)
100
+ cd client && npm install && cd .. # client deps (separate tree)
101
+ npm run dev # server (4200) + client (4201)
203
102
  ```
204
103
 
205
- > **Note:** This repo has two separate `node_modules` trees — one at the root (server + CLI) and one inside `client/` (Vite + React). Both `npm install` calls are required. If you see `sh: tsc: command not found` during `npm run build`, it means one of them is missing.
206
-
207
104
  | Script | Description |
208
105
  |--------|-------------|
209
- | `npm run dev` | Start server + client with hot reload |
210
- | `npm run dev:server` | Server only (tsx watch) |
211
- | `npm run dev:client` | Client only (Vite) |
212
- | `npm run build` | Production build (server + client + CLI) |
213
- | `npm run typecheck` | TypeScript check (server + client) |
214
- | `npm test` | Run tests (vitest) |
215
-
216
- ### Project structure
217
-
218
- ```
219
- specrails-hub/
220
- ├── server/
221
- │ ├── index.ts # hub entry point
222
- │ ├── hub-db.ts # hub SQLite (project registry)
223
- │ ├── project-registry.ts # per-project context manager
224
- │ ├── hub-router.ts # /api/hub/* routes
225
- │ ├── project-router.ts # /api/projects/:id/* routes (includes ticket endpoints)
226
- │ ├── ticket-store.ts # local-tickets.json read/write with file locking
227
- │ ├── ticket-watcher.ts # chokidar watcher → WebSocket broadcast
228
- │ ├── db.ts # per-project SQLite (jobs, events, chat)
229
- │ ├── queue-manager.ts # job queue per project
230
- │ ├── chat-manager.ts # Claude chat per project
231
- │ ├── config.ts # command discovery
232
- │ ├── hooks.ts # pipeline event handler
233
- │ ├── analytics.ts # metrics aggregation
234
- │ └── types.ts # shared TypeScript types (includes ticket WS messages)
235
- ├── client/
236
- │ └── src/
237
- │ ├── App.tsx
238
- │ ├── components/
239
- │ │ ├── TabBar.tsx # project tabs
240
- │ │ ├── AddProjectDialog.tsx # register project modal
241
- │ │ ├── WelcomeScreen.tsx # zero-state
242
- │ │ ├── ProjectLayout.tsx # per-project wrapper
243
- │ │ ├── ProjectNavbar.tsx # Home/Analytics/Conversations nav
244
- │ │ ├── CommandGrid.tsx # command launcher
245
- │ │ ├── TitleBar.tsx # custom titlebar (Windows/Linux frameless)
246
- │ │ ├── ArcSidebar.tsx # collapsible Arc-style sidebar
247
- │ │ ├── ProjectRightSidebar.tsx # project-level right panel
248
- │ │ ├── TicketsSection.tsx # ticket panel container (view mode toggle)
249
- │ │ ├── TicketListView.tsx # sortable table view
250
- │ │ ├── TicketGridView.tsx # kanban drag-and-drop view
251
- │ │ ├── TicketPostItView.tsx # sticky-note grid view
252
- │ │ ├── TicketDetailModal.tsx # ticket editor modal
253
- │ │ ├── CreateTicketModal.tsx # new ticket form
254
- │ │ ├── TicketStatusIndicator.tsx # status dot, badge, border
255
- │ │ ├── TicketContextMenu.tsx # right-click menu
256
- │ │ └── ...
257
- │ ├── hooks/
258
- │ │ ├── useHub.tsx # hub state context
259
- │ │ ├── useSpecGenTracker.tsx # spec generation state (persists via localStorage)
260
- │ │ ├── useTickets.ts # ticket CRUD + WS subscription + toast/glow
261
- │ │ ├── useChat.ts # chat operations
262
- │ │ ├── usePipeline.ts # pipeline phases
263
- │ │ └── useSharedWebSocket.tsx
264
- │ ├── pages/
265
- │ │ ├── DashboardPage.tsx
266
- │ │ ├── AnalyticsPage.tsx
267
- │ │ ├── ConversationsPage.tsx
268
- │ │ ├── GlobalSettingsPage.tsx
269
- │ │ └── JobDetailPage.tsx
270
- │ └── lib/
271
- │ ├── api.ts # dynamic API base routing
272
- │ ├── pending-specs.ts # spec generation state persistence
273
- │ └── route-memory.ts # per-project route save/restore
274
- ├── src-tauri/ # Tauri desktop shell
275
- ├── scripts/
276
- │ ├── build-sidecar.mjs # build bundled server binary for Tauri
277
- │ └── generate-icons.mjs # regenerate all icon sizes from SVG
278
- ├── cli/
279
- │ └── specrails-hub.ts # CLI bridge
280
- ├── package.json
281
- ├── tsconfig.json
282
- └── vitest.config.ts
283
- ```
284
-
285
- ## WebSocket
286
-
287
- The server broadcasts events over a single WebSocket connection. All project-scoped messages include a `projectId` field — the client filters by active project.
106
+ | `npm run dev` | Server + client with hot reload |
107
+ | `npm run build` | Production build |
108
+ | `npm test` | vitest |
109
+ | `npm run tauri dev` | Run desktop app in dev mode |
288
110
 
289
- | Message type | Scope | Description |
290
- |-------------|-------|-------------|
291
- | `init` | project | Job started |
292
- | `log` | project | Streaming log line |
293
- | `phase` | project | Pipeline phase transition |
294
- | `queue_update` | project | Queue state change |
295
- | `ticket_created` | project | New ticket created (via API or CLI) |
296
- | `ticket_updated` | project | Ticket updated; if `ticket.id === 0`, signals a full external file change |
297
- | `ticket_deleted` | project | Ticket deleted |
298
- | `hub.project_added` | hub | New project registered |
299
- | `hub.project_removed` | hub | Project unregistered |
111
+ CI gates coverage hard: 70 % global, 80 % server, 80 % client. Local runs must clear the same bars.
300
112
 
301
- ## Security
113
+ ## Security model
302
114
 
303
- - Binds to `127.0.0.1` (loopback only) **do not expose to a network**
304
- - No authentication (single-user local tool)
305
- - All SQL operations use parameterized queries
306
- - Project paths validated as existing directories on registration
115
+ - Binds to `127.0.0.1` only. **Do not expose to a network.**
116
+ - No authentication (single-user local tool).
117
+ - Parameterised SQL throughout.
118
+ - Reserved paths in your project (`.mcp.json`, `.specrails/plugins/state.json`, `.specrails/profiles/.user-preferred.json`) are mutated surgically — read, modify owned keys, atomic rename — so adding plugin N+1 never disturbs plugin N's state.
307
119
 
308
120
  ## Support
309
121
 
310
- If specrails-hub saves you time, you can donate on [Ko-fi](https://ko-fi.com/D1D81Y002C) to support ongoing development.
122
+ If specrails-hub saves you time, you can buy me a coffee on [Ko-fi](https://ko-fi.com/D1D81Y002C). It funds development of the open-source ecosystem.
311
123
 
312
124
  [![Donate on Ko-fi](https://img.shields.io/badge/Donate-Ko--fi-FF5E5B?logo=kofi&logoColor=white&style=flat-square)](https://ko-fi.com/D1D81Y002C)
313
125
 
@@ -1 +1 @@
1
- import{r as e}from"./chunk-CilyBKbf.js";import{Ft as t,L as n,X as r,dt as i,ft as a,gt as o,ht as s,it as c,mt as l,pt as u,rt as d}from"./index-_3ZOPMZV.js";var f=a(`ban`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M4.929 4.929 19.07 19.071`,key:`196cmz`}]]),p=e(t(),1);function m(e){let t=new Set;return e.filter(e=>{let n=`${e.type}:${e.jobId}`;return t.has(n)?!1:(t.add(n),!0)})}function h({activeProjectId:e,limit:t=50}){let[n,r]=(0,p.useState)([]),[i,a]=(0,p.useState)(!1),[s,c]=(0,p.useState)(!0),u=(0,p.useRef)(e);(0,p.useEffect)(()=>{u.current=e},[e]);let{registerHandler:d,unregisterHandler:f}=l();(0,p.useEffect)(()=>{if(!e){r([]),c(!0);return}let n=!1;a(!0),r([]),c(!0);async function i(){let e=o();try{let i=await fetch(`${e}/activity?limit=${t}`);if(!i.ok||n)return;let a=await i.json();if(n)return;r(a),c(a.length===t)}catch{}finally{n||a(!1)}}return i(),()=>{n=!0}},[e,t]);let h=(0,p.useCallback)(async()=>{if(i||!s)return;a(!0);let e=o();try{r(n=>{let i=n[n.length-1];if(!i)return n;let o=encodeURIComponent(i.timestamp);return fetch(`${e}/activity?limit=${t}&before=${o}`).then(e=>e.json()).then(e=>{r(t=>m([...t,...e])),c(e.length===t),a(!1)}).catch(()=>a(!1)),n})}catch{a(!1)}},[i,s,t]),g=(0,p.useCallback)(e=>{let t=e,n=u.current;if(!(!t||typeof t.type!=`string`)&&!(t.projectId&&t.projectId!==n)){if(t.type===`queue`&&Array.isArray(t.jobs)){let e=[];for(let n of t.jobs){let t=n.status===`completed`?`job_completed`:n.status===`failed`?`job_failed`:n.status===`canceled`?`job_canceled`:`job_started`;e.push({id:`${t}:${n.id}`,type:t,jobId:n.id,jobCommand:n.command??``,timestamp:n.startedAt??new Date().toISOString(),summary:`${t.replace(`_`,` `)}: ${n.command??``}`,costUsd:null})}e.length>0&&r(t=>m([...e,...t]))}if(t.type===`phase`&&t.phase&&t.state&&t.timestamp){let e={id:`phase:${t.phase}:${t.state}:${t.timestamp}`,type:`job_started`,jobId:`phase-${t.phase}`,jobCommand:`Phase: ${t.phase} → ${t.state}`,timestamp:t.timestamp,summary:`Phase ${t.phase} is ${t.state}`,costUsd:null};r(t=>m([e,...t]))}}},[]);return(0,p.useLayoutEffect)(()=>(d(`activity`,g),()=>f(`activity`)),[g,d,f]),{items:n,loading:i,hasMore:s,loadMore:h}}var g=s();function _(e){let t=Date.now()-new Date(e).getTime(),n=Math.floor(t/1e3);if(n<60)return`${n}s ago`;let r=Math.floor(n/60);if(r<60)return`${r}m ago`;let i=Math.floor(r/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}function v({type:e}){switch(e){case`job_completed`:return(0,g.jsx)(c,{className:`w-4 h-4 text-green-500 flex-shrink-0`});case`job_failed`:return(0,g.jsx)(d,{className:`w-4 h-4 text-red-500 flex-shrink-0`});case`job_canceled`:return(0,g.jsx)(f,{className:`w-4 h-4 text-muted-foreground flex-shrink-0`});default:return(0,g.jsx)(n,{className:`w-4 h-4 text-blue-500 flex-shrink-0`})}}function y(e){switch(e){case`job_completed`:return`Completed`;case`job_failed`:return`Failed`;case`job_canceled`:return`Canceled`;default:return`Started`}}function b(e){switch(e){case`job_completed`:return`text-green-500`;case`job_failed`:return`text-red-500`;case`job_canceled`:return`text-muted-foreground`;default:return`text-blue-500`}}function x(){let{activeProjectId:e}=u(),{items:t,loading:n,hasMore:a,loadMore:o}=h({activeProjectId:e}),s=(0,p.useRef)(null);return(0,p.useEffect)(()=>{let e=s.current;if(!e)return;let t=new IntersectionObserver(e=>{e[0].isIntersecting&&a&&!n&&o()},{threshold:.1});return t.observe(e),()=>t.disconnect()},[a,n,o]),(0,g.jsxs)(`div`,{className:`flex flex-col h-full overflow-hidden`,children:[(0,g.jsxs)(`div`,{className:`flex items-center gap-2 px-4 py-3 border-b border-border bg-background/50`,children:[(0,g.jsx)(i,{className:`w-4 h-4 text-muted-foreground`}),(0,g.jsx)(`h1`,{className:`text-sm font-medium`,children:`Activity`})]}),(0,g.jsxs)(`div`,{className:`flex-1 overflow-y-auto`,children:[n&&t.length===0?(0,g.jsx)(`div`,{className:`flex items-center justify-center h-32`,children:(0,g.jsx)(r,{className:`w-4 h-4 animate-spin text-muted-foreground`})}):t.length===0?(0,g.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-48 gap-2 text-muted-foreground`,children:[(0,g.jsx)(i,{className:`w-8 h-8 opacity-40`}),(0,g.jsx)(`p`,{className:`text-sm`,children:`No activity yet`}),(0,g.jsx)(`p`,{className:`text-xs opacity-70`,children:`Job events will appear here when jobs run`})]}):(0,g.jsx)(`ul`,{className:`divide-y divide-border/50`,children:t.map(e=>(0,g.jsxs)(`li`,{className:`flex items-center gap-3 px-4 py-2.5 hover:bg-accent/30 transition-colors`,children:[(0,g.jsx)(v,{type:e.type}),(0,g.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,g.jsx)(`p`,{className:`text-xs text-foreground truncate`,title:e.jobCommand,children:e.jobCommand}),(0,g.jsxs)(`div`,{className:`flex items-center gap-2 mt-0.5`,children:[(0,g.jsx)(`span`,{className:`text-xs font-medium ${b(e.type)}`,children:y(e.type)}),e.costUsd!=null&&(0,g.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[`$`,e.costUsd.toFixed(4)]})]})]}),(0,g.jsx)(`span`,{className:`text-xs text-muted-foreground flex-shrink-0 tabular-nums`,children:_(e.timestamp)})]},`${e.type}:${e.jobId}`))}),(0,g.jsx)(`div`,{ref:s,className:`h-1`}),n&&t.length>0&&(0,g.jsx)(`div`,{className:`flex justify-center py-3`,children:(0,g.jsx)(r,{className:`w-4 h-4 animate-spin text-muted-foreground`})}),!a&&t.length>0&&(0,g.jsx)(`p`,{className:`text-center text-xs text-muted-foreground py-3`,children:`All activity loaded`})]})]})}export{x as default};
1
+ import{r as e}from"./chunk-CilyBKbf.js";import{L as t,St as n,Vt as r,Z as i,_t as a,bt as o,ot as s,st as c,vt as l,xt as u,yt as d}from"./index-NFgJciDY.js";var f=l(`ban`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M4.929 4.929 19.07 19.071`,key:`196cmz`}]]),p=e(r(),1);function m(e){let t=new Set;return e.filter(e=>{let n=`${e.type}:${e.jobId}`;return t.has(n)?!1:(t.add(n),!0)})}function h({activeProjectId:e,limit:t=50}){let[r,i]=(0,p.useState)([]),[a,s]=(0,p.useState)(!1),[c,l]=(0,p.useState)(!0),u=(0,p.useRef)(e);(0,p.useEffect)(()=>{u.current=e},[e]);let{registerHandler:d,unregisterHandler:f}=o();(0,p.useEffect)(()=>{if(!e){i([]),l(!0);return}let r=!1;s(!0),i([]),l(!0);async function a(){let e=n();try{let n=await fetch(`${e}/activity?limit=${t}`);if(!n.ok||r)return;let a=await n.json();if(r)return;i(a),l(a.length===t)}catch{}finally{r||s(!1)}}return a(),()=>{r=!0}},[e,t]);let h=(0,p.useCallback)(async()=>{if(a||!c)return;s(!0);let e=n();try{i(n=>{let r=n[n.length-1];if(!r)return n;let a=encodeURIComponent(r.timestamp);return fetch(`${e}/activity?limit=${t}&before=${a}`).then(e=>e.json()).then(e=>{i(t=>m([...t,...e])),l(e.length===t),s(!1)}).catch(()=>s(!1)),n})}catch{s(!1)}},[a,c,t]),g=(0,p.useCallback)(e=>{let t=e,n=u.current;if(!(!t||typeof t.type!=`string`)&&!(t.projectId&&t.projectId!==n)){if(t.type===`queue`&&Array.isArray(t.jobs)){let e=[];for(let n of t.jobs){let t=n.status===`completed`?`job_completed`:n.status===`failed`?`job_failed`:n.status===`canceled`?`job_canceled`:`job_started`;e.push({id:`${t}:${n.id}`,type:t,jobId:n.id,jobCommand:n.command??``,timestamp:n.startedAt??new Date().toISOString(),summary:`${t.replace(`_`,` `)}: ${n.command??``}`,costUsd:null})}e.length>0&&i(t=>m([...e,...t]))}if(t.type===`phase`&&t.phase&&t.state&&t.timestamp){let e={id:`phase:${t.phase}:${t.state}:${t.timestamp}`,type:`job_started`,jobId:`phase-${t.phase}`,jobCommand:`Phase: ${t.phase} → ${t.state}`,timestamp:t.timestamp,summary:`Phase ${t.phase} is ${t.state}`,costUsd:null};i(t=>m([e,...t]))}}},[]);return(0,p.useLayoutEffect)(()=>(d(`activity`,g),()=>f(`activity`)),[g,d,f]),{items:r,loading:a,hasMore:c,loadMore:h}}var g=u();function _(e){let t=Date.now()-new Date(e).getTime(),n=Math.floor(t/1e3);if(n<60)return`${n}s ago`;let r=Math.floor(n/60);if(r<60)return`${r}m ago`;let i=Math.floor(r/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}function v({type:e}){switch(e){case`job_completed`:return(0,g.jsx)(c,{className:`w-4 h-4 text-green-500 flex-shrink-0`});case`job_failed`:return(0,g.jsx)(s,{className:`w-4 h-4 text-red-500 flex-shrink-0`});case`job_canceled`:return(0,g.jsx)(f,{className:`w-4 h-4 text-muted-foreground flex-shrink-0`});default:return(0,g.jsx)(t,{className:`w-4 h-4 text-blue-500 flex-shrink-0`})}}function y(e){switch(e){case`job_completed`:return`Completed`;case`job_failed`:return`Failed`;case`job_canceled`:return`Canceled`;default:return`Started`}}function b(e){switch(e){case`job_completed`:return`text-green-500`;case`job_failed`:return`text-red-500`;case`job_canceled`:return`text-muted-foreground`;default:return`text-blue-500`}}function x(){let{activeProjectId:e}=d(),{items:t,loading:n,hasMore:r,loadMore:o}=h({activeProjectId:e}),s=(0,p.useRef)(null);return(0,p.useEffect)(()=>{let e=s.current;if(!e)return;let t=new IntersectionObserver(e=>{e[0].isIntersecting&&r&&!n&&o()},{threshold:.1});return t.observe(e),()=>t.disconnect()},[r,n,o]),(0,g.jsxs)(`div`,{className:`flex flex-col h-full overflow-hidden`,children:[(0,g.jsxs)(`div`,{className:`flex items-center gap-2 px-4 py-3 border-b border-border bg-background/50`,children:[(0,g.jsx)(a,{className:`w-4 h-4 text-muted-foreground`}),(0,g.jsx)(`h1`,{className:`text-sm font-medium`,children:`Activity`})]}),(0,g.jsxs)(`div`,{className:`flex-1 overflow-y-auto`,children:[n&&t.length===0?(0,g.jsx)(`div`,{className:`flex items-center justify-center h-32`,children:(0,g.jsx)(i,{className:`w-4 h-4 animate-spin text-muted-foreground`})}):t.length===0?(0,g.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-48 gap-2 text-muted-foreground`,children:[(0,g.jsx)(a,{className:`w-8 h-8 opacity-40`}),(0,g.jsx)(`p`,{className:`text-sm`,children:`No activity yet`}),(0,g.jsx)(`p`,{className:`text-xs opacity-70`,children:`Job events will appear here when jobs run`})]}):(0,g.jsx)(`ul`,{className:`divide-y divide-border/50`,children:t.map(e=>(0,g.jsxs)(`li`,{className:`flex items-center gap-3 px-4 py-2.5 hover:bg-accent/30 transition-colors`,children:[(0,g.jsx)(v,{type:e.type}),(0,g.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,g.jsx)(`p`,{className:`text-xs text-foreground truncate`,title:e.jobCommand,children:e.jobCommand}),(0,g.jsxs)(`div`,{className:`flex items-center gap-2 mt-0.5`,children:[(0,g.jsx)(`span`,{className:`text-xs font-medium ${b(e.type)}`,children:y(e.type)}),e.costUsd!=null&&(0,g.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[`$`,e.costUsd.toFixed(4)]})]})]}),(0,g.jsx)(`span`,{className:`text-xs text-muted-foreground flex-shrink-0 tabular-nums`,children:_(e.timestamp)})]},`${e.type}:${e.jobId}`))}),(0,g.jsx)(`div`,{ref:s,className:`h-1`}),n&&t.length>0&&(0,g.jsx)(`div`,{className:`flex justify-center py-3`,children:(0,g.jsx)(i,{className:`w-4 h-4 animate-spin text-muted-foreground`})}),!r&&t.length>0&&(0,g.jsx)(`p`,{className:`text-center text-xs text-muted-foreground py-3`,children:`All activity loaded`})]})]})}export{x as default};