@hienlh/ppm 0.5.20 → 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.
- package/CHANGELOG.md +6 -0
- package/bun.lock +45 -0
- package/dist/web/assets/{api-client-BxCvlogn.js → api-client-ANLU-Irq.js} +1 -1
- package/dist/web/assets/chat-tab-C24nbKz1.js +7 -0
- package/dist/web/assets/{code-editor-0YVgeS1c.js → code-editor-DjIL6ta3.js} +1 -1
- package/dist/web/assets/{diff-viewer-CtEmKn4e.js → diff-viewer-BnvcXY3g.js} +1 -1
- package/dist/web/assets/{git-graph-DycoowxO.js → git-graph-iAf_zaqe.js} +1 -1
- package/dist/web/assets/index-BwLVvoev.js +21 -0
- package/dist/web/assets/index-CP_2zE5O.css +2 -0
- package/dist/web/assets/{input-Bzyi1GeB.js → input-DV4tynJq.js} +1 -1
- package/dist/web/assets/{jsx-runtime-Bzk8w7Zh.js → jsx-runtime-B4BJKQ1u.js} +1 -1
- package/dist/web/assets/{markdown-renderer-LHjvxp5Q.js → markdown-renderer-CIfiE3o8.js} +2 -2
- package/dist/web/assets/react-WvgCEYPV.js +1 -0
- package/dist/web/assets/{rotate-ccw-ZqeedZLA.js → rotate-ccw-BesidNnx.js} +1 -1
- package/dist/web/assets/settings-store-BGF8--S9.js +1 -0
- package/dist/web/assets/settings-tab-B_QwULcp.js +1 -0
- package/dist/web/assets/sqlite-viewer-DpGb3i2g.js +16 -0
- package/dist/web/assets/tab-store-L0a7ao4c.js +1 -0
- package/dist/web/assets/{terminal-tab-B2QEABNU.js → terminal-tab-4-DINw_B.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-BFv4d2_j.js → use-monaco-theme-RFoGvnp0.js} +2 -2
- package/dist/web/index.html +9 -8
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +96 -61
- package/docs/deployment-guide.md +16 -14
- package/docs/design-guidelines.md +5 -2
- package/docs/project-overview-pdr.md +20 -17
- package/docs/project-roadmap.md +35 -23
- package/docs/system-architecture.md +27 -18
- package/package.json +4 -1
- package/src/cli/commands/init.ts +7 -2
- package/src/cli/commands/restart.ts +6 -0
- package/src/index.ts +9 -1
- package/src/providers/claude-agent-sdk.ts +59 -28
- package/src/server/index.ts +10 -2
- package/src/server/routes/chat.ts +19 -0
- package/src/server/routes/project-scoped.ts +2 -0
- package/src/server/routes/sqlite.ts +75 -0
- package/src/server/routes/tunnel.ts +17 -2
- package/src/server/ws/chat.ts +33 -1
- package/src/services/config.service.ts +182 -58
- package/src/services/db.service.ts +303 -0
- package/src/services/push-notification.service.ts +23 -37
- package/src/services/session-log.service.ts +12 -24
- package/src/services/sqlite.service.ts +144 -0
- package/src/web/components/chat/chat-history-bar.tsx +68 -8
- package/src/web/components/chat/chat-tab.tsx +10 -1
- package/src/web/components/chat/file-picker.tsx +1 -1
- package/src/web/components/chat/slash-command-picker.tsx +1 -1
- package/src/web/components/explorer/file-tree.tsx +3 -1
- package/src/web/components/layout/draggable-tab.tsx +50 -4
- package/src/web/components/layout/editor-panel.tsx +1 -0
- package/src/web/components/layout/mobile-nav.tsx +2 -2
- package/src/web/components/layout/project-bar.tsx +40 -17
- package/src/web/components/layout/tab-bar.tsx +16 -1
- package/src/web/components/layout/tab-content.tsx +5 -0
- package/src/web/components/sqlite/sqlite-data-grid.tsx +165 -0
- package/src/web/components/sqlite/sqlite-query-editor.tsx +97 -0
- package/src/web/components/sqlite/sqlite-table-list.tsx +48 -0
- package/src/web/components/sqlite/sqlite-viewer.tsx +117 -0
- package/src/web/components/sqlite/use-sqlite.ts +97 -0
- package/src/web/hooks/use-chat.ts +12 -0
- package/src/web/stores/tab-store.ts +1 -0
- package/dist/web/assets/chat-tab-D_LO6cRM.js +0 -7
- package/dist/web/assets/index-82E_pIrH.css +0 -2
- package/dist/web/assets/index-y49eIXuR.js +0 -21
- package/dist/web/assets/settings-store-DikslxSJ.js +0 -1
- package/dist/web/assets/settings-tab-Dt9Sv1zx.js +0 -1
- package/dist/web/assets/tab-store-BNgVKR5w.js +0 -1
- /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
|
-
//
|
|
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
|
|
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** —
|
|
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
|
|
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:
|
|
66
|
-
- **Why:**
|
|
67
|
-
- **Trade-off:**
|
|
68
|
-
- **Impact:**
|
|
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:
|
|
81
|
-
- **Why:**
|
|
82
|
-
- **Trade-off:**
|
|
83
|
-
- **Impact:**
|
|
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** |
|
|
194
|
-
| **v3** | Planned | Collaborative editing, plugin architecture | Q2
|
|
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
|
|
197
|
-
- **Daemon Mode as Default:** `ppm start`
|
|
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
|
|
package/docs/project-roadmap.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# PPM Project Roadmap & Status
|
|
2
2
|
|
|
3
|
-
**Last Updated:** March
|
|
3
|
+
**Last Updated:** March 19, 2026
|
|
4
4
|
|
|
5
|
-
## Current Version: v2.0 (
|
|
5
|
+
## Current Version: v2.0 (Complete v0.5.21)
|
|
6
6
|
|
|
7
|
-
### Overall Progress:
|
|
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 (
|
|
156
|
+
### Phase 10: Testing ✅ In Progress (65%)
|
|
153
157
|
|
|
154
|
-
#### Unit Tests (
|
|
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 (
|
|
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:**
|
|
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
|
-
- [
|
|
228
|
-
- [
|
|
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
|
-
**
|
|
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** |
|
|
359
|
-
| **v2.1** | Planned |
|
|
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 │
|
|
38
|
-
│ │ │ │ @anthropic/SDK (prim) │
|
|
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
|
-
│ │
|
|
46
|
-
│ │ (
|
|
47
|
-
│ │ (
|
|
48
|
-
│ │ (
|
|
49
|
-
│ │
|
|
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
|
-
│ •
|
|
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
|
|
145
|
-
| **
|
|
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
|
|
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
|
|
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:**
|
|
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** —
|
|
201
|
-
- **
|
|
202
|
-
- **
|
|
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.
|
|
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",
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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}`);
|
package/src/server/index.ts
CHANGED
|
@@ -485,13 +485,15 @@ export async function startServer(options: {
|
|
|
485
485
|
}
|
|
486
486
|
console.log();
|
|
487
487
|
|
|
488
|
-
// Graceful shutdown — stop server + tunnel
|
|
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);
|