@hienlh/ppm 0.5.21 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/bun.lock +45 -0
  2. package/dist/web/assets/{api-client-BxCvlogn.js → api-client-ANLU-Irq.js} +1 -1
  3. package/dist/web/assets/chat-tab-C24nbKz1.js +7 -0
  4. package/dist/web/assets/{code-editor-kXJmlnIt.js → code-editor-DjIL6ta3.js} +1 -1
  5. package/dist/web/assets/{diff-viewer-CwMGJLkZ.js → diff-viewer-BnvcXY3g.js} +1 -1
  6. package/dist/web/assets/{git-graph-HUZNEwuR.js → git-graph-iAf_zaqe.js} +1 -1
  7. package/dist/web/assets/index-BwLVvoev.js +21 -0
  8. package/dist/web/assets/index-CP_2zE5O.css +2 -0
  9. package/dist/web/assets/{input-Bzyi1GeB.js → input-DV4tynJq.js} +1 -1
  10. package/dist/web/assets/{jsx-runtime-Bzk8w7Zh.js → jsx-runtime-B4BJKQ1u.js} +1 -1
  11. package/dist/web/assets/{markdown-renderer-DhYu0Drk.js → markdown-renderer-CIfiE3o8.js} +2 -2
  12. package/dist/web/assets/react-WvgCEYPV.js +1 -0
  13. package/dist/web/assets/{rotate-ccw-ZqeedZLA.js → rotate-ccw-BesidNnx.js} +1 -1
  14. package/dist/web/assets/settings-store-BGF8--S9.js +1 -0
  15. package/dist/web/assets/settings-tab-B_QwULcp.js +1 -0
  16. package/dist/web/assets/sqlite-viewer-DpGb3i2g.js +16 -0
  17. package/dist/web/assets/tab-store-L0a7ao4c.js +1 -0
  18. package/dist/web/assets/{terminal-tab-DhPMvT7b.js → terminal-tab-4-DINw_B.js} +1 -1
  19. package/dist/web/assets/{use-monaco-theme-BFv4d2_j.js → use-monaco-theme-RFoGvnp0.js} +2 -2
  20. package/dist/web/index.html +9 -8
  21. package/dist/web/sw.js +1 -1
  22. package/docs/codebase-summary.md +96 -61
  23. package/docs/deployment-guide.md +16 -14
  24. package/docs/design-guidelines.md +5 -2
  25. package/docs/project-overview-pdr.md +20 -17
  26. package/docs/project-roadmap.md +35 -23
  27. package/docs/system-architecture.md +27 -18
  28. package/package.json +4 -1
  29. package/src/cli/commands/init.ts +7 -2
  30. package/src/cli/commands/restart.ts +6 -0
  31. package/src/index.ts +9 -1
  32. package/src/providers/claude-agent-sdk.ts +59 -28
  33. package/src/server/index.ts +10 -2
  34. package/src/server/routes/chat.ts +19 -0
  35. package/src/server/routes/project-scoped.ts +2 -0
  36. package/src/server/routes/sqlite.ts +75 -0
  37. package/src/server/ws/chat.ts +33 -1
  38. package/src/services/config.service.ts +182 -58
  39. package/src/services/db.service.ts +303 -0
  40. package/src/services/push-notification.service.ts +23 -37
  41. package/src/services/session-log.service.ts +12 -24
  42. package/src/services/sqlite.service.ts +144 -0
  43. package/src/web/components/chat/chat-history-bar.tsx +68 -8
  44. package/src/web/components/chat/chat-tab.tsx +10 -1
  45. package/src/web/components/chat/file-picker.tsx +1 -1
  46. package/src/web/components/chat/slash-command-picker.tsx +1 -1
  47. package/src/web/components/explorer/file-tree.tsx +3 -1
  48. package/src/web/components/layout/draggable-tab.tsx +50 -4
  49. package/src/web/components/layout/editor-panel.tsx +1 -0
  50. package/src/web/components/layout/mobile-nav.tsx +2 -2
  51. package/src/web/components/layout/tab-bar.tsx +16 -1
  52. package/src/web/components/layout/tab-content.tsx +5 -0
  53. package/src/web/components/sqlite/sqlite-data-grid.tsx +165 -0
  54. package/src/web/components/sqlite/sqlite-query-editor.tsx +97 -0
  55. package/src/web/components/sqlite/sqlite-table-list.tsx +48 -0
  56. package/src/web/components/sqlite/sqlite-viewer.tsx +117 -0
  57. package/src/web/components/sqlite/use-sqlite.ts +97 -0
  58. package/src/web/hooks/use-chat.ts +12 -0
  59. package/src/web/stores/tab-store.ts +1 -0
  60. package/dist/web/assets/chat-tab-ClNqZsi6.js +0 -7
  61. package/dist/web/assets/index-B1ga7VY4.js +0 -21
  62. package/dist/web/assets/index-c5tJni8Z.css +0 -2
  63. package/dist/web/assets/settings-store-DikslxSJ.js +0 -1
  64. package/dist/web/assets/settings-tab-Dt3jaLUC.js +0 -1
  65. package/dist/web/assets/tab-store-BNgVKR5w.js +0 -1
  66. /package/dist/web/assets/{utils-EM9hC5pN.js → utils-C2KxHr1H.js} +0 -0
@@ -296,7 +296,7 @@ screens: {
296
296
 
297
297
  ```tsx
298
298
  // src/web/components/editor/code-editor.tsx
299
- // CodeMirror 6 integration with syntax highlighting
299
+ // Monaco Editor integration (@monaco-editor/react)
300
300
 
301
301
  <CodeEditor
302
302
  language="javascript"
@@ -307,10 +307,13 @@ screens: {
307
307
  />
308
308
 
309
309
  // Features:
310
- // - 50+ language support
310
+ // - 50+ language support with IntelliSense
311
311
  // - Real-time syntax highlighting
312
312
  // - Line numbers, code folding
313
313
  // - Find/replace (Ctrl+H)
314
+ // - Word wrap toggle (Alt+Z)
315
+ // - Monaco diff viewer for git diffs
316
+ // - Theme sync with app dark/light mode
314
317
  ```
315
318
 
316
319
  ### Terminal Component
@@ -5,7 +5,7 @@
5
5
  **PPM** (Personal Project Manager) is a full-stack, mobile-first web IDE designed for developers to manage code projects with AI-powered assistance. It combines a responsive web interface, real-time terminal access, AI chat with tool support, and Git integration into a cohesive development environment.
6
6
 
7
7
  Built on the **Bun runtime** for performance, PPM enables developers to:
8
- - Browse and edit project files with CodeMirror syntax highlighting
8
+ - Browse and edit project files with Monaco Editor syntax highlighting
9
9
  - Execute commands via xterm.js terminal with full PTY support
10
10
  - Chat with Claude AI with file attachments and slash commands
11
11
  - View Git status, diffs, and commit graphs in real-time
@@ -25,9 +25,9 @@ Built on the **Bun runtime** for performance, PPM enables developers to:
25
25
  ### Core Features (Implemented v2)
26
26
  - **Project Management** — Create, switch, and manage multiple projects via CLI and web UI
27
27
  - **File Explorer** — Browse directory trees, create/edit/delete files with path traversal protection
28
- - **Code Editor** — CodeMirror 6 with syntax highlighting, line numbers, theme support (dark/light)
28
+ - **Code Editor** — Monaco Editor with syntax highlighting, IntelliSense, diff viewer, theme support (dark/light)
29
29
  - **Terminal** — Full xterm.js with Bun PTY, resize handling, multiple terminal sessions per project
30
- - **AI Chat** — Streaming Claude messages with tool use (file read/write, git commands), file attachments, slash command detection
30
+ - **AI Chat** — Streaming Claude messages with tool use (file read/write, git commands), file attachments, slash commands, auto-generated session titles
31
31
  - **Git Integration** — Status, diffs, commit graphs, branch management, staging/committing
32
32
  - **PWA** — Installable web app with offline support
33
33
  - **Authentication** — Token-based auth with auto-generated tokens in config
@@ -62,10 +62,10 @@ Built on the **Bun runtime** for performance, PPM enables developers to:
62
62
  - **Trade-off:** Larger CSS bundle; mitigated by tree-shaking, critical CSS extraction
63
63
  - **Impact:** Consistent, accessible, maintainable UI with dark/light theme support
64
64
 
65
- ### Editor: CodeMirror 6 + Diff2HTML
66
- - **Why:** Modular, extensible, supports syntax highlighting, merge views, live collaboration
67
- - **Trade-off:** More complex API than Monaco; justified by Bun compatibility and flexibility
68
- - **Impact:** Supports 50+ languages, diffing, real-time file changes
65
+ ### Editor: Monaco Editor (@monaco-editor/react)
66
+ - **Why:** Superior IntelliSense, syntax highlighting, built-in diff viewer, industry-standard code editor
67
+ - **Trade-off:** Larger bundle size; justified by feature richness and developer experience
68
+ - **Impact:** 50+ languages, IntelliSense, word wrap toggle (Alt+Z), Monaco diff viewer
69
69
 
70
70
  ### Terminal: xterm.js + Bun PTY
71
71
  - **Why:** xterm.js is industry-standard terminal emulator; Bun PTY avoids node-pty complexity
@@ -77,10 +77,10 @@ Built on the **Bun runtime** for performance, PPM enables developers to:
77
77
  - **Trade-off:** Anthropic-specific; can swap via provider registry pattern
78
78
  - **Impact:** Rich conversation capabilities, reliable streaming, tool approval flow
79
79
 
80
- ### Database: None (Filesystem-based)
81
- - **Why:** Single-machine design, YAML project registry, stateless server
82
- - **Trade-off:** No persistence across server restarts for chat; mitigated by session IDs in URL
83
- - **Impact:** Zero infrastructure, fast startup, git-friendly config
80
+ ### Database: SQLite (migrating from YAML)
81
+ - **Why:** Richer persistence for sessions, usage tracking, audit logs; single-file DB suits single-machine design
82
+ - **Trade-off:** Added dependency; mitigated by Bun's built-in SQLite support (bun:sqlite)
83
+ - **Impact:** Session mapping, push subscriptions, usage history, config storage with YAML backward compat
84
84
 
85
85
  ### Build: Vite 8.0
86
86
  - **Why:** ESM-native, fast hot reload, TypeScript support, PWA plugin
@@ -190,13 +190,16 @@ ppm stop # Stop daemon
190
190
  | Version | Status | Focus | Date |
191
191
  |---------|--------|-------|------|
192
192
  | **v1** | Complete | Initial prototype (single project, basic chat, terminal) | Feb 2025 |
193
- | **v2** | In Progress | Multi-project, project-scoped APIs, improved UI/UX, daemon mode, --share flag | Mar 2025 |
194
- | **v3** | Planned | Collaborative editing, plugin architecture | Q2 2025 |
193
+ | **v2** | Complete (v0.5.21) | Multi-project, Monaco Editor, auto-title sessions, daemon mode, --share flag, SQLite migration | Mar 2026 |
194
+ | **v3** | Planned | Collaborative editing, plugin architecture | Q2 2026 |
195
195
 
196
- ### v2 Changes (Mar 2025)
197
- - **Daemon Mode as Default:** `ppm start` now runs background daemon by default. `--foreground/-f` flag for debugging.
196
+ ### v2 Changes (Mar 2026)
197
+ - **Daemon Mode as Default:** `ppm start` runs background daemon by default. `--foreground/-f` flag for debugging.
198
198
  - **Public URL Sharing:** `ppm start --share` creates Cloudflare Quick Tunnel public URL. Auto-downloads cloudflared binary.
199
+ - **Monaco Editor:** Migrated from CodeMirror 6 to Monaco Editor with IntelliSense and diff viewer.
200
+ - **Auto-Title Sessions:** Chat sessions auto-generate titles from SDK summary after first message.
201
+ - **SQLite Persistence:** Migrating from YAML to SQLite for config, sessions, usage, push subscriptions.
202
+ - **Web Push Notifications:** Push notification support via Service Worker.
203
+ - **Session Logging:** Audit trail with sensitive data redaction.
199
204
  - **Status File:** `~/.ppm/status.json` (new format) replaces `ppm.pid` with backward compatibility.
200
- - **Auth Warning:** Warns if `--share` used without auth enabled.
201
- - **New Services:** `cloudflared.service.ts` (binary download), `tunnel.service.ts` (tunnel lifecycle).
202
205
 
@@ -1,12 +1,12 @@
1
1
  # PPM Project Roadmap & Status
2
2
 
3
- **Last Updated:** March 17, 2026
3
+ **Last Updated:** March 19, 2026
4
4
 
5
- ## Current Version: v2.0 (In Progress)
5
+ ## Current Version: v2.0 (Complete v0.5.21)
6
6
 
7
- ### Overall Progress: 90%
7
+ ### Overall Progress: 95%
8
8
 
9
- Multi-project, project-scoped API refactor with improved UX.
9
+ Multi-project, project-scoped API refactor with improved UX, Monaco Editor, auto-title chat sessions.
10
10
 
11
11
  ---
12
12
 
@@ -61,22 +61,21 @@ Multi-project, project-scoped API refactor with improved UX.
61
61
 
62
62
  ### Phase 4: File Explorer & Editor ✅ Complete
63
63
  - FileTree component with directory expansion
64
- - Monaco Editor integration with syntax highlighting
64
+ - Monaco Editor integration with syntax highlighting and IntelliSense
65
65
  - File read/write operations
66
66
  - Monaco diff viewer for git diffs
67
67
 
68
- **Status:** Done
69
-
70
- **Latest Work (260315):**
71
- - Chat file attachments (drag-drop, paste)
72
- - File viewer for images/PDFs/markdown
73
- - Better error messages
68
+ **Status:** Done, CodeMirror fully removed (v0.5.17+)
74
69
 
75
70
  **Latest Work (260317):**
76
71
  - Migrated CodeMirror → Monaco Editor (@monaco-editor/react) for better syntax highlighting and diff viewer
77
72
  - Alt+Z keyboard shortcut for word wrap toggle in editor and diff viewer
78
73
  - Improved code completion and IntelliSense
79
74
 
75
+ **Latest Work (260319):**
76
+ - CodeMirror fully removed from codebase
77
+ - Monaco Editor polishing for large file performance
78
+
80
79
  ---
81
80
 
82
81
  ### Phase 5: Web Terminal ✅ Complete
@@ -122,6 +121,11 @@ Multi-project, project-scoped API refactor with improved UX.
122
121
  - Usage badge (token tracking)
123
122
  - Session management (save/load)
124
123
 
124
+ **Latest Work (260319):**
125
+ - Auto-generate session title from SDK summary on first message
126
+ - Inline session rename in chat UI
127
+ - Session history tab persistence
128
+
125
129
  ---
126
130
 
127
131
  ### Phase 8: CLI Commands ✅ Complete
@@ -149,25 +153,29 @@ Multi-project, project-scoped API refactor with improved UX.
149
153
 
150
154
  ---
151
155
 
152
- ### Phase 10: Testing ✅ In Progress (60%)
156
+ ### Phase 10: Testing ✅ In Progress (65%)
153
157
 
154
- #### Unit Tests (40% complete)
158
+ #### Unit Tests (50% complete)
155
159
  - [x] Mock provider tests
156
160
  - [x] ChatService tests
161
+ - [x] ConfigService tests
162
+ - [x] DbService tests (SQLite)
157
163
  - [ ] FileService tests
158
164
  - [ ] GitService tests
159
165
  - [x] Zustand store tests
166
+ - [x] SessionLogService tests (redaction)
160
167
 
161
- #### Integration Tests (30% complete)
168
+ #### Integration Tests (40% complete)
162
169
  - [x] Claude Agent SDK integration
163
170
  - [x] Chat WebSocket flow
171
+ - [x] SQLite migration validation
164
172
  - [ ] Terminal WebSocket flow
165
173
  - [ ] Git operations
166
174
  - [ ] File operations
167
175
 
168
176
  #### E2E Tests (0% — Planned for v3)
169
177
 
170
- **Status:** Partial, needs completion
178
+ **Status:** Making progress, good coverage for core services
171
179
 
172
180
  ---
173
181
 
@@ -222,13 +230,16 @@ Multi-project, project-scoped API refactor with improved UX.
222
230
  - [x] Project Switcher Bar (52px sidebar, avatars, colors, reordering) (260317)
223
231
  - [x] Keep-alive workspace switching (preserve xterm DOM) (260317)
224
232
  - [x] Sidebar tab system (Explorer/Git/History) (260317)
225
- - [x] Monaco Editor migration (CodeMirror → Monaco) (260317)
233
+ - [x] Monaco Editor migration (CodeMirror → Monaco, fully removed) (260317-260319)
226
234
  - [x] Project color customization (12-color palette + custom hex) (260317)
227
- - [ ] Complete test coverage (60% complete)
228
- - [ ] Documentation (in progress)
235
+ - [x] Auto-generate chat session titles from SDK summary (260319)
236
+ - [x] Inline session rename UI (260319)
237
+ - [x] SQLite migration (db.service.ts, backward YAML compat) (in progress)
238
+ - [ ] Complete test coverage (65% complete)
239
+ - [x] Documentation updates (260319)
229
240
  - [ ] Security audit (planned)
230
241
 
231
- **Target Release:** March 31, 2026
242
+ **Release Status:** v0.5.21 released, v2.0 essentially complete
232
243
 
233
244
  ---
234
245
 
@@ -355,8 +366,8 @@ Multi-project, project-scoped API refactor with improved UX.
355
366
  | Version | Status | Features | Target Date |
356
367
  |---------|--------|----------|-------------|
357
368
  | **v1.0** | Released | Single project, basic chat, terminal | Feb 28, 2025 |
358
- | **v2.0** | In Progress | Multi-project, project-scoped API, improved UX | Mar 31, 2026 |
359
- | **v2.1** | Planned | Bug fixes, performance improvements | Apr 15, 2026 |
369
+ | **v2.0** | Complete (v0.5.21) | Multi-project, project-scoped API, improved UX, Monaco Editor, auto-title | Mar 19, 2026 |
370
+ | **v2.1** | Planned | Complete test coverage, SQLite finalization, bug fixes | Apr 15, 2026 |
360
371
  | **v3.0** | Planned | Collaborative editing, custom tools, plugins | Jun 30, 2026 |
361
372
  | **v4.0** | Planned | Cloud sync, advanced git, profiling UI | Sep 30, 2026 |
362
373
 
@@ -392,10 +403,11 @@ Multi-project, project-scoped API refactor with improved UX.
392
403
  | Dependency | Version | Risk | Monitoring |
393
404
  |-----------|---------|------|-----------|
394
405
  | Bun | 1.3.6+ | Medium | Check security advisories weekly |
395
- | Claude Agent SDK | 0.2.76 | Low | Follow Anthropic releases |
406
+ | Claude Agent SDK | 0.2.76+ | Low | Follow Anthropic releases for model updates |
396
407
  | React | 19.2.4 | Low | Monitor for breaking changes |
397
- | TypeScript | 5.9.3 | Low | Plan upgrades quarterly |
408
+ | TypeScript | 5.9.3+ | Low | Plan upgrades quarterly |
398
409
  | xterm.js | 6.0 | Low | Check for terminal rendering bugs |
410
+ | Monaco Editor | 4.7.0+ | Low | Monitor for accessibility improvements |
399
411
 
400
412
  ---
401
413
 
@@ -34,19 +34,19 @@
34
34
  │ │ ┌──────────────────────────────────────────────────────────┐ │ │
35
35
  │ │ │ ProviderRegistry (routes to active AI provider) │ │ │
36
36
  │ │ │ ┌───────────────────────┬──────────────────────────┐ │ │ │
37
- │ │ │ │ claude-agent-sdk │ claude-code-cli (CLI) │ │ │ │
38
- │ │ │ │ @anthropic/SDK (prim) │ Fallback subprocess │ │ │ │
37
+ │ │ │ │ claude-agent-sdk │ mock-provider (test) │ │ │ │
38
+ │ │ │ │ @anthropic/SDK (prim) │ Returns canned resp. │ │ │ │
39
39
  │ │ │ └───────────────────────┴──────────────────────────┘ │ │ │
40
40
  │ │ └──────────────────────────────────────────────────────────┘ │ │
41
41
  │ └────────────────────────────────────────────────────────────────┘ │
42
42
  │ │
43
43
  │ Config & State (src/services/) │
44
44
  │ ┌──────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │
45
- │ │ ppm.yaml │ │ Git Repos │ │ Session Storage │ │
46
- │ │ (projects list) │ │ (local disk) │ │ (in-memory only)│ │
47
- │ │ (auth token) │ │ │ │ │ │
48
- │ │ (AI provider │ │ │ │ │ │
49
- │ │ settings) │ │ │ │ │ │
45
+ │ │ SQLite DB │ │ Git Repos │ │ Session Storage │ │
46
+ │ │ (config, projs) │ │ (local disk) │ │ (SQLite + SDK) │ │
47
+ │ │ (session map) │ │ │ │ (session_map, │ │
48
+ │ │ (push subs, │ │ │ │ session_logs, │ │
49
+ │ │ usage, logs) │ │ │ │ usage_history) │ │
50
50
  │ └──────────────────┘ └──────────────────┘ └─────────────────┘ │
51
51
  └──────────────────────────────────────────────────────────────────────┘
52
52
  ↓↑
@@ -54,7 +54,8 @@
54
54
  │ Filesystem Access (Local Only) │
55
55
  │ • Project directories (git repos) │
56
56
  │ • File read/write operations │
57
- │ • Config file (ppm.yaml)
57
+ │ • SQLite database (~/.ppm/ppm.db)
58
+ │ • Legacy config file (~/.ppm/config.yaml) │
58
59
  └────────────────────────────────────────────────┘
59
60
  ```
60
61
 
@@ -138,11 +139,15 @@ WS /ws/project/:name/terminal/:id → Terminal I/O
138
139
  | Service | Purpose | Key Methods |
139
140
  |---------|---------|-------------|
140
141
  | **ChatService** | Session management, message streaming | createSession, streamMessage, getHistory |
142
+ | **ConfigService** | Config loading (YAML→SQLite migration) | load, save, getToken |
143
+ | **DbService** | SQLite persistence (6 tables, WAL) | getDb, openTestDb, schema migrations |
141
144
  | **GitService** | Git command execution | status, diff, commit, stage, branch |
142
145
  | **FileService** | File operations with validation | read, write, tree, delete, mkdir |
143
146
  | **TerminalService** | PTY lifecycle, shell spawning | spawn, write, kill |
144
- | **ProjectService** | Project registry (YAML) | add, remove, get, list |
145
- | **ConfigService** | Config file management | load, save, getToken |
147
+ | **ProjectService** | Project CRUD, scanning | add, remove, get, list, scan |
148
+ | **ClaudeUsageService** | Token tracking, cost calculation | trackUsage, getUsage |
149
+ | **PushNotificationService** | Web push subscriptions | subscribe, unsubscribe, notify |
150
+ | **SessionLogService** | Audit logs with redaction | logSession, getLog |
146
151
  | **ProviderRegistry** | AI provider routing | getDefault, send (delegates) |
147
152
  | **CloudflaredService** | Download cloudflared binary | ensureCloudflared, getCloudflaredPath |
148
153
  | **TunnelService** | Cloudflare Quick Tunnel lifecycle | startTunnel, stopTunnel, getTunnelUrl |
@@ -170,25 +175,28 @@ interface AIProvider {
170
175
  ```
171
176
 
172
177
  **Implementations:**
173
- - **claude-agent-sdk** (Primary) — @anthropic-ai/claude-agent-sdk, streaming, tool use. Reads model/effort/maxTurns/budget/thinking from `ppm.yaml` AI config. Settings refreshed per query.
178
+ - **claude-agent-sdk** (Primary) — @anthropic-ai/claude-agent-sdk, streaming, tool use. Reads model/effort/maxTurns/budget/thinking from config. Settings refreshed per query. Windows CLI fallback for Bun subprocess pipe issues. .env poisoning mitigation.
174
179
  - **mock-provider** (Testing) — Returns canned responses
175
- - **Note:** CLI provider removed (v2); agent SDK now sole AI provider
180
+ - **Note:** CLI provider removed (v2); agent SDK is sole AI provider with Windows CLI fallback
176
181
 
177
182
  ---
178
183
 
179
- ### Data Access Layer (Filesystem + Git)
180
- **Components:** Direct filesystem access, simple-git wrapper
184
+ ### Data Access Layer (SQLite + Filesystem + Git)
185
+ **Components:** SQLite via bun:sqlite, direct filesystem access, simple-git wrapper
181
186
 
182
187
  **Responsibilities:**
188
+ - Persist config, projects, session maps, usage, logs in SQLite
183
189
  - Read/write project files with path validation
184
190
  - Execute git commands via simple-git
185
191
  - Cache directory listings
186
192
  - Enforce security (no parent directory access)
187
193
 
188
194
  **Key Patterns:**
195
+ - SQLite: WAL mode, foreign keys, lazy init, schema v1 with 6 tables
189
196
  - Path validation: `projectPath/relativePath` only, reject `..`
190
197
  - Caching: Directory trees cached with TTL
191
198
  - Error handling: Descriptive messages (file not found, permission denied)
199
+ - Migration: Automatic YAML→SQLite migration on first run with new db.service
192
200
 
193
201
  ---
194
202
 
@@ -196,10 +204,11 @@ interface AIProvider {
196
204
  **Component:** Zustand stores in browser
197
205
 
198
206
  **Stores:**
199
- - **projectStore** — Active project, project list
200
- - **tabStore** — Open tabs (chat, editor, git, terminal)
201
- - **fileStore** — Selected files, editor content
202
- - **settingsStore** — Auth token, theme preference
207
+ - **projectStore** — Active project, project list, localStorage persistence
208
+ - **tabStore** — Tab facade, delegates to panelStore
209
+ - **panelStore** — Grid layout (rows/columns), panel creation/movement, keep-alive snapshots
210
+ - **fileStore** — File cache
211
+ - **settingsStore** — Theme, sidebar state, git view mode, device name
203
212
 
204
213
  **Pattern:** Selectors for subscriptions (only re-render affected components)
205
214
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.5.21",
3
+ "version": "0.6.0",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -37,8 +37,11 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@anthropic-ai/claude-agent-sdk": "^0.2.76",
40
+ "@codemirror/lang-sql": "^6.10.0",
40
41
  "@inquirer/prompts": "^8.3.0",
41
42
  "@monaco-editor/react": "^4.7.0",
43
+ "@tanstack/react-table": "^8.21.3",
44
+ "@uiw/react-codemirror": "^4.25.8",
42
45
  "@xterm/addon-fit": "^0.11.0",
43
46
  "@xterm/addon-web-links": "^0.12.0",
44
47
  "@xterm/xterm": "^6.0.0",
@@ -4,6 +4,7 @@ import { existsSync } from "node:fs";
4
4
  import { input, confirm, select, password } from "@inquirer/prompts";
5
5
  import { configService } from "../../services/config.service.ts";
6
6
  import { projectService } from "../../services/project.service.ts";
7
+ import { getAllConfig } from "../../services/db.service.ts";
7
8
 
8
9
  const DEFAULT_PORT = 3210;
9
10
 
@@ -18,10 +19,14 @@ export interface InitOptions {
18
19
  }
19
20
 
20
21
  /** Check if config already exists */
22
+ /** Check if config already exists (DB or legacy YAML) */
21
23
  export function hasConfig(): boolean {
24
+ try {
25
+ const dbConfig = getAllConfig();
26
+ if (Object.keys(dbConfig).length > 0) return true;
27
+ } catch {}
22
28
  const globalConfig = resolve(homedir(), ".ppm", "config.yaml");
23
- const localConfig = resolve(process.cwd(), "ppm.yaml");
24
- return existsSync(globalConfig) || existsSync(localConfig);
29
+ return existsSync(globalConfig);
25
30
  }
26
31
 
27
32
  export async function initProject(options: InitOptions = {}) {
@@ -37,6 +37,12 @@ export async function restartServer(options: { config?: string }) {
37
37
  // Brief pause for port release
38
38
  await Bun.sleep(500);
39
39
 
40
+ // Set DB profile before loading config
41
+ const { setDbProfile } = await import("../../services/db.service.ts");
42
+ if (options.config && /dev/i.test(options.config)) {
43
+ setDbProfile("dev");
44
+ }
45
+
40
46
  // Reload config for new server
41
47
  const { configService } = await import("../../services/config.service.ts");
42
48
  configService.load(options.config);
package/src/index.ts CHANGED
@@ -19,8 +19,16 @@ program
19
19
  .option("-f, --foreground", "Run in foreground (default: background daemon)")
20
20
  .option("-d, --daemon", "Run as background daemon (default, kept for compat)")
21
21
  .option("-s, --share", "Share via public URL (Cloudflare tunnel)")
22
- .option("-c, --config <path>", "Path to config file")
22
+ .option("-c, --config <path>", "Path to config file (YAML import into DB)")
23
+ .option("--profile <name>", "DB profile name (e.g. 'dev' → ppm.dev.db)")
23
24
  .action(async (options) => {
25
+ // Set DB profile before any DB access
26
+ const { setDbProfile } = await import("./services/db.service.ts");
27
+ if (options.profile) {
28
+ setDbProfile(options.profile);
29
+ } else if (options.config && /dev/i.test(options.config)) {
30
+ setDbProfile("dev");
31
+ }
24
32
  // Auto-init on first run
25
33
  const { hasConfig, initProject } = await import("./cli/commands/init.ts");
26
34
  if (!hasConfig()) {
@@ -13,33 +13,13 @@ import type {
13
13
  } from "./provider.interface.ts";
14
14
  import { configService } from "../services/config.service.ts";
15
15
  import { updateFromSdkEvent } from "../services/claude-usage.service.ts";
16
+ import { getSessionMapping, setSessionMapping } from "../services/db.service.ts";
16
17
  import { resolve } from "node:path";
17
18
  import { homedir } from "node:os";
18
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
19
-
20
- /** Persistent PPM sessionId → SDK sessionId mapping */
21
- const SESSION_MAP_FILE = resolve(homedir(), ".ppm", "session-map.json");
22
-
23
- function loadSessionMap(): Record<string, string> {
24
- try {
25
- if (existsSync(SESSION_MAP_FILE)) return JSON.parse(readFileSync(SESSION_MAP_FILE, "utf-8"));
26
- } catch {}
27
- return {};
28
- }
29
-
30
- function saveSessionMapping(ppmId: string, sdkId: string): void {
31
- const map = loadSessionMap();
32
- map[ppmId] = sdkId;
33
- try {
34
- const dir = resolve(homedir(), ".ppm");
35
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
36
- writeFileSync(SESSION_MAP_FILE, JSON.stringify(map));
37
- } catch {}
38
- }
19
+ import { readFileSync, existsSync } from "node:fs";
39
20
 
40
21
  function getSdkSessionId(ppmId: string): string {
41
- const map = loadSessionMap();
42
- return map[ppmId] ?? ppmId;
22
+ return getSessionMapping(ppmId) ?? ppmId;
43
23
  }
44
24
 
45
25
  /**
@@ -473,7 +453,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
473
453
  const existingMsgs = await getSessionMessages(sessionId);
474
454
  if (existingMsgs.length > 0) {
475
455
  resumeSessionId = sessionId;
476
- saveSessionMapping(sessionId, sessionId);
456
+ setSessionMapping(sessionId, sessionId);
477
457
  console.log(`[sdk] session ${sessionId} exists on disk (${existingMsgs.length} msgs) — will resume`);
478
458
  }
479
459
  } catch {}
@@ -525,11 +505,58 @@ export class ClaudeAgentSdkProvider implements AIProvider {
525
505
  /** Number of tool_use blocks pending results (top-level tools only, not subagent children) */
526
506
  let pendingToolCount = 0;
527
507
 
508
+ // Retry logic: if SDK returns error_during_execution with 0 turns on first event,
509
+ // it's a transient subprocess failure — retry once before surfacing the error.
510
+ const MAX_RETRIES = 1;
511
+ let retryCount = 0;
512
+
513
+ retryLoop: while (true) {
528
514
  let sdkEventCount = 0;
529
515
  for await (const msg of eventSource) {
530
516
  sdkEventCount++;
531
517
  if (sdkEventCount === 1) {
532
518
  console.log(`[sdk] first event received: type=${(msg as any).type} subtype=${(msg as any).subtype ?? "none"}`);
519
+ // Detect immediate failure: first event is a result with error + 0 turns
520
+ if ((msg as any).type === "result" && (msg as any).subtype === "error_during_execution" && ((msg as any).num_turns ?? 0) === 0 && retryCount < MAX_RETRIES) {
521
+ retryCount++;
522
+ console.warn(`[sdk] transient error on first event — retrying (attempt ${retryCount}/${MAX_RETRIES})`);
523
+ // Re-create query for retry — don't reuse sessionId in case SDK partially created it
524
+ if (!useDirectCli) {
525
+ const q = query({
526
+ prompt: message,
527
+ options: {
528
+ // On retry, let SDK generate a fresh session to avoid conflicts
529
+ sessionId: undefined,
530
+ resume: undefined,
531
+ ...(shouldFork && { forkSession: true }),
532
+ cwd: effectiveCwd,
533
+ systemPrompt: { type: "preset", preset: "claude_code" },
534
+ settingSources: ["user", "project"],
535
+ env: queryEnv,
536
+ settings: { permissions: { allow: [], deny: [] } },
537
+ allowedTools: [
538
+ "Read", "Write", "Edit", "Bash", "Glob", "Grep",
539
+ "WebSearch", "WebFetch", "AskUserQuestion",
540
+ "Agent", "Skill", "TodoWrite", "ToolSearch",
541
+ ],
542
+ permissionMode: "bypassPermissions",
543
+ allowDangerouslySkipPermissions: true,
544
+ ...(providerConfig.model && { model: providerConfig.model }),
545
+ ...(providerConfig.effort && { effort: providerConfig.effort }),
546
+ maxTurns: providerConfig.max_turns ?? 100,
547
+ ...(providerConfig.max_budget_usd && { maxBudgetUsd: providerConfig.max_budget_usd }),
548
+ ...(providerConfig.thinking_budget_tokens != null && {
549
+ thinkingBudgetTokens: providerConfig.thinking_budget_tokens,
550
+ }),
551
+ canUseTool,
552
+ includePartialMessages: true,
553
+ } as any,
554
+ });
555
+ this.activeQueries.set(sessionId, q);
556
+ eventSource = q;
557
+ }
558
+ continue retryLoop;
559
+ }
533
560
  }
534
561
  // Extract parent_tool_use_id from SDK message (present on subagent-scoped messages)
535
562
  const parentId = (msg as any).parent_tool_use_id as string | undefined;
@@ -545,7 +572,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
545
572
  // SDK may assign a different session_id than our UUID
546
573
  if (initMsg.session_id && initMsg.session_id !== sessionId) {
547
574
  // Persist mapping so resume works after server restart
548
- saveSessionMapping(sessionId, initMsg.session_id);
575
+ setSessionMapping(sessionId, initMsg.session_id);
549
576
  // Update our in-memory mapping
550
577
  const oldMeta = this.activeSessions.get(sessionId);
551
578
  if (oldMeta) {
@@ -727,9 +754,12 @@ export class ClaudeAgentSdkProvider implements AIProvider {
727
754
 
728
755
  // Surface non-success subtypes as errors so FE can display them
729
756
  if (subtype && subtype !== "success") {
730
- // Extract error detail from SDK result if available
731
- const sdkError = result.error ?? result.error_message ?? result.message ?? "";
757
+ // Extract error detail from SDK result check multiple possible fields
758
+ const sdkError = result.error ?? result.error_message ?? result.message ?? result.reason ?? "";
732
759
  const sdkDetail = typeof sdkError === "string" ? sdkError : JSON.stringify(sdkError);
760
+ // Log full result for debugging (truncated at 2000 chars)
761
+ console.error(`[sdk] result error: subtype=${subtype} turns=${result.num_turns ?? 0} detail=${sdkDetail || "(none)"}`);
762
+ console.error(`[sdk] result full dump: ${JSON.stringify(result).slice(0, 2000)}`);
733
763
  const errorMessages: Record<string, string> = {
734
764
  error_max_turns: "Agent reached maximum turn limit.",
735
765
  error_max_budget_usd: "Agent reached budget limit.",
@@ -737,7 +767,6 @@ export class ClaudeAgentSdkProvider implements AIProvider {
737
767
  };
738
768
  const baseMsg = errorMessages[subtype] ?? `Agent stopped: ${subtype}`;
739
769
  const fullMsg = sdkDetail ? `${baseMsg}\n${sdkDetail}` : baseMsg;
740
- console.error(`[sdk] result error: subtype=${subtype} turns=${result.num_turns ?? 0} detail=${sdkDetail || "(none)"} raw=${JSON.stringify(result).slice(0, 500)}`);
741
770
  yield {
742
771
  type: "error",
743
772
  message: fullMsg,
@@ -772,6 +801,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
772
801
  if (sdkEventCount === 0) {
773
802
  yield { type: "error", message: "Claude did not respond. Check that 'claude' CLI works in your terminal." };
774
803
  }
804
+ break; // Exit retryLoop — normal completion
805
+ } // end retryLoop
775
806
  } catch (e) {
776
807
  const msg = (e as Error).message ?? String(e);
777
808
  console.error(`[sdk] error: ${msg}`);
@@ -485,13 +485,15 @@ export async function startServer(options: {
485
485
  }
486
486
  console.log();
487
487
 
488
- // Graceful shutdown — stop server + tunnel on exit (especially important on Windows)
488
+ // Graceful shutdown — stop server + tunnel + DB on exit
489
489
  const shutdown = () => {
490
490
  try { server.stop(true); } catch {}
491
491
  try {
492
- // Dynamic import to avoid circular — tunnel may not be loaded
493
492
  import("../services/tunnel.service.ts").then(({ tunnelService }) => tunnelService.stopTunnel()).catch(() => {});
494
493
  } catch {}
494
+ try {
495
+ import("../services/db.service.ts").then(({ closeDb }) => closeDb()).catch(() => {});
496
+ } catch {}
495
497
  };
496
498
  process.on("SIGINT", () => { shutdown(); process.exit(0); });
497
499
  process.on("SIGTERM", () => { shutdown(); process.exit(0); });
@@ -505,6 +507,12 @@ if (process.argv.includes("__serve__")) {
505
507
  const host = process.argv[idx + 2] ?? "0.0.0.0";
506
508
  const configPath = process.argv[idx + 3] || undefined;
507
509
 
510
+ // Set DB profile for daemon child (detect "dev" from config path)
511
+ const { setDbProfile } = await import("../services/db.service.ts");
512
+ if (configPath && /dev/i.test(configPath)) {
513
+ setDbProfile("dev");
514
+ }
515
+
508
516
  configService.load(configPath);
509
517
  await setupLogFile();
510
518
 
@@ -4,6 +4,7 @@ import { mkdirSync, existsSync } from "node:fs";
4
4
  import { tmpdir } from "node:os";
5
5
  import { chatService } from "../../services/chat.service.ts";
6
6
  import { providerRegistry } from "../../providers/registry.ts";
7
+ import { renameSession as sdkRenameSession } from "@anthropic-ai/claude-agent-sdk";
7
8
  import { listSlashItems } from "../../services/slash-items.service.ts";
8
9
  import { getCachedUsage, refreshUsageNow } from "../../services/claude-usage.service.ts";
9
10
  import { getSessionLog } from "../../services/session-log.service.ts";
@@ -106,6 +107,24 @@ chatRoutes.delete("/sessions/:id", async (c) => {
106
107
  }
107
108
  });
108
109
 
110
+ /** PATCH /chat/sessions/:id — rename a session */
111
+ chatRoutes.patch("/sessions/:id", async (c) => {
112
+ try {
113
+ const id = c.req.param("id");
114
+ const body = await c.req.json<{ title?: string }>();
115
+ if (!body.title?.trim()) return c.json(err("title is required"), 400);
116
+ const title = body.title.trim();
117
+ // Persist to SDK so Claude Code CLI also sees the custom title
118
+ await sdkRenameSession(id, title);
119
+ // Also update in-memory session
120
+ const session = chatService.getSession(id);
121
+ if (session) session.title = title;
122
+ return c.json(ok({ id, title }));
123
+ } catch (e) {
124
+ return c.json(err((e as Error).message), 500);
125
+ }
126
+ });
127
+
109
128
  /** POST /chat/sessions/:id/fork — fork session into a new one (for rewind/branch) */
110
129
  chatRoutes.post("/sessions/:id/fork", async (c) => {
111
130
  try {
@@ -3,6 +3,7 @@ import { resolveProjectPath } from "../helpers/resolve-project.ts";
3
3
  import { chatRoutes } from "./chat.ts";
4
4
  import { gitRoutes } from "./git.ts";
5
5
  import { fileRoutes } from "./files.ts";
6
+ import { sqliteRoutes } from "./sqlite.ts";
6
7
 
7
8
  type Env = { Variables: { projectPath: string; projectName: string } };
8
9
 
@@ -25,3 +26,4 @@ projectScopedRouter.use("*", async (c, next) => {
25
26
  projectScopedRouter.route("/chat", chatRoutes);
26
27
  projectScopedRouter.route("/git", gitRoutes);
27
28
  projectScopedRouter.route("/files", fileRoutes);
29
+ projectScopedRouter.route("/sqlite", sqliteRoutes);