@hienlh/ppm 0.6.3 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/web/assets/api-client-BHpHp5Lz.js +1 -0
- package/dist/web/assets/{chat-tab-DjE_8Csw.js → chat-tab-CDVCDw_H.js} +3 -3
- package/dist/web/assets/{code-editor-witrClmz.js → code-editor-wmS73ejX.js} +1 -1
- package/dist/web/assets/{diff-viewer-DSU--yFW.js → diff-viewer-BsYccTx1.js} +1 -1
- package/dist/web/assets/{git-graph-HpcOYt3G.js → git-graph-BbWb6_Jq.js} +1 -1
- package/dist/web/assets/{index-CcXQ5iQw.js → index-DhuAmTQ1.js} +6 -6
- package/dist/web/assets/index-aIGuIMQ8.css +2 -0
- package/dist/web/assets/keybindings-store-BqgrTQAC.js +1 -0
- package/dist/web/assets/{markdown-renderer-DSw-4oxk.js → markdown-renderer-aPdw9BhU.js} +1 -1
- package/dist/web/assets/postgres-viewer-V4hKmmzV.js +1 -0
- package/dist/web/assets/settings-store-DgOSmeGL.js +1 -0
- package/dist/web/assets/settings-tab-DwsKpk9T.js +1 -0
- package/dist/web/assets/sqlite-viewer-BRsj8GXc.js +1 -0
- package/dist/web/assets/{terminal-tab-CAQvs2wj.js → terminal-tab-3tDV4RCn.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-GX0lrqac.js → use-monaco-theme-Ccqh1RD4.js} +1 -1
- package/dist/web/index.html +4 -4
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +41 -14
- package/docs/project-roadmap.md +31 -6
- package/docs/system-architecture.md +222 -7
- package/package.json +1 -1
- package/src/cli/commands/db-cmd.ts +21 -4
- package/src/server/index.ts +6 -0
- package/src/server/routes/database.ts +259 -0
- package/src/services/database/adapter-registry.ts +13 -0
- package/src/services/database/init-adapters.ts +9 -0
- package/src/services/database/postgres-adapter.ts +42 -0
- package/src/services/database/readonly-check.ts +17 -0
- package/src/services/database/sqlite-adapter.ts +55 -0
- package/src/services/db.service.ts +77 -4
- package/src/services/table-cache.service.ts +75 -0
- package/src/types/database.ts +50 -0
- package/src/web/app.tsx +9 -4
- package/src/web/components/database/connection-color-picker.tsx +67 -0
- package/src/web/components/database/connection-form-dialog.tsx +234 -0
- package/src/web/components/database/connection-list.tsx +208 -0
- package/src/web/components/database/database-sidebar.tsx +100 -0
- package/src/web/components/database/use-connections.ts +99 -0
- package/src/web/components/layout/command-palette.tsx +57 -6
- package/src/web/components/layout/draggable-tab.tsx +13 -2
- package/src/web/components/layout/mobile-drawer.tsx +7 -2
- package/src/web/components/layout/sidebar.tsx +6 -1
- package/src/web/components/postgres/postgres-viewer.tsx +12 -3
- package/src/web/components/postgres/use-postgres.ts +57 -21
- package/src/web/components/sqlite/sqlite-viewer.tsx +27 -3
- package/src/web/components/sqlite/use-sqlite.ts +21 -12
- package/src/web/lib/api-client.ts +7 -1
- package/src/web/lib/color-utils.ts +23 -0
- package/src/web/stores/settings-store.ts +2 -2
- package/dist/web/assets/api-client-D0pZeYY8.js +0 -1
- package/dist/web/assets/index-DyEgsogR.css +0 -2
- package/dist/web/assets/keybindings-store-C_KQKrsc.js +0 -1
- package/dist/web/assets/postgres-viewer-BnkGPi0L.js +0 -1
- package/dist/web/assets/settings-store-B5g1Gis-.js +0 -1
- package/dist/web/assets/settings-tab-DpQdg9OW.js +0 -1
- package/dist/web/assets/sqlite-viewer-JZvegGV-.js +0 -1
package/docs/codebase-summary.md
CHANGED
|
@@ -9,7 +9,7 @@ ppm/
|
|
|
9
9
|
├── src/
|
|
10
10
|
│ ├── index.ts # CLI entry point (Commander.js program)
|
|
11
11
|
│ ├── cli/
|
|
12
|
-
│ │ ├── commands/ # CLI command implementations (
|
|
12
|
+
│ │ ├── commands/ # CLI command implementations (14 files, 1700 LOC)
|
|
13
13
|
│ │ │ ├── start.ts # Start server (background by default, --foreground/-f, --share/-s for tunnel)
|
|
14
14
|
│ │ │ ├── stop.ts # Stop daemon (reads status.json or ppm.pid, graceful shutdown)
|
|
15
15
|
│ │ │ ├── restart.ts # Restart daemon (keeps tunnel alive)
|
|
@@ -21,7 +21,8 @@ ppm/
|
|
|
21
21
|
│ │ │ ├── projects.ts # Add/remove/list/scan projects
|
|
22
22
|
│ │ │ ├── config-cmd.ts # View/set config values
|
|
23
23
|
│ │ │ ├── git-cmd.ts # Git operations (status, diff, log, commit)
|
|
24
|
-
│ │ │
|
|
24
|
+
│ │ │ ├── chat-cmd.ts # Chat CLI (send messages, manage sessions)
|
|
25
|
+
│ │ │ └── db-cmd.ts # Database CLI (list, query, manage connections)
|
|
25
26
|
│ │ └── utils/
|
|
26
27
|
│ │ └── project-resolver.ts # Resolve project name -> path
|
|
27
28
|
│ ├── server/
|
|
@@ -35,6 +36,7 @@ ppm/
|
|
|
35
36
|
│ │ │ ├── chat.ts # GET/POST/DELETE sessions, GET messages, usage, slash-items
|
|
36
37
|
│ │ │ ├── git.ts # GET status, diff, log, graph; POST commit, stage, discard
|
|
37
38
|
│ │ │ ├── files.ts # GET tree, read, diff; PUT write; POST mkdir, delete
|
|
39
|
+
│ │ │ ├── database.ts # GET/POST/PUT/DELETE /api/db/connections (CRUD), query execution
|
|
38
40
|
│ │ │ └── static.ts # Serve dist/web/index.html (frontend)
|
|
39
41
|
│ │ ├── helpers/
|
|
40
42
|
│ │ │ └── resolve-project.ts # Helper to resolve project from request params
|
|
@@ -46,10 +48,10 @@ ppm/
|
|
|
46
48
|
│ │ ├── claude-agent-sdk.ts # Primary: SDK integration, tool approval, Windows CLI fallback, .env poisoning mitigation
|
|
47
49
|
│ │ ├── mock-provider.ts # Test provider (ignores config)
|
|
48
50
|
│ │ └── registry.ts # ProviderRegistry (singleton, router to active provider)
|
|
49
|
-
│ ├── services/ # Business logic (
|
|
51
|
+
│ ├── services/ # Business logic (18 files, 3100 LOC)
|
|
50
52
|
│ │ ├── chat.service.ts # Session lifecycle, message streaming
|
|
51
53
|
│ │ ├── config.service.ts # Config loading (YAML→SQLite migration)
|
|
52
|
-
│ │ ├── db.service.ts # SQLite persistence (schema
|
|
54
|
+
│ │ ├── db.service.ts # SQLite persistence (schema v3, WAL mode, 8 tables, connection CRUD)
|
|
53
55
|
│ │ ├── project.service.ts # Project CRUD, scanning, resolution
|
|
54
56
|
│ │ ├── file.service.ts # File ops with path validation
|
|
55
57
|
│ │ ├── git.service.ts # Git operations (status, diff, log, graph)
|
|
@@ -60,11 +62,19 @@ ppm/
|
|
|
60
62
|
│ │ ├── slash-items.service.ts # /slash command detection & completion
|
|
61
63
|
│ │ ├── git-dirs.service.ts # Cached git directory discovery
|
|
62
64
|
│ │ ├── cloudflared.service.ts # Download cloudflared binary (platform-specific)
|
|
63
|
-
│ │
|
|
64
|
-
│ ├──
|
|
65
|
+
│ │ ├── tunnel.service.ts # Cloudflare Quick Tunnel lifecycle
|
|
66
|
+
│ │ ├── table-cache.service.ts # Table metadata cache & search for DB connections
|
|
67
|
+
│ │ └── database/ # Database adapters & registry
|
|
68
|
+
│ │ ├── adapter-registry.ts # DatabaseAdapter registry (extensible)
|
|
69
|
+
│ │ ├── sqlite-adapter.ts # SQLite connection, query execution
|
|
70
|
+
│ │ ├── postgres-adapter.ts # PostgreSQL connection, query execution
|
|
71
|
+
│ │ ├── init-adapters.ts # Initialize adapters at server start
|
|
72
|
+
│ │ └── readonly-check.ts # isReadOnlyQuery() safety regex (CTE-safe)
|
|
73
|
+
│ ├── types/ # TypeScript interfaces (7 files, 450 LOC)
|
|
65
74
|
│ │ ├── api.ts # ApiResponse envelope, WebSocket message types
|
|
66
75
|
│ │ ├── chat.ts # Session, Message, ChatEvent types
|
|
67
76
|
│ │ ├── config.ts # Config schema
|
|
77
|
+
│ │ ├── database.ts # DatabaseAdapter, DbConnectionConfig, DbTableInfo, etc.
|
|
68
78
|
│ │ ├── git.ts # GitStatus, GitDiff, GitCommit types
|
|
69
79
|
│ │ ├── project.ts # Project interface
|
|
70
80
|
│ │ └── terminal.ts # Terminal types
|
|
@@ -88,7 +98,7 @@ ppm/
|
|
|
88
98
|
│ │ ├── use-health-check.ts # Detect server crashes/restarts via health endpoint
|
|
89
99
|
│ │ ├── use-usage.ts # Fetch token usage from backend
|
|
90
100
|
│ │ └── use-push-notification.ts # Web push notifications via Service Worker
|
|
91
|
-
│ ├── lib/ # Utilities (
|
|
101
|
+
│ ├── lib/ # Utilities (11 files)
|
|
92
102
|
│ │ ├── api-client.ts # Fetch wrapper with auth token, envelope unwrapping
|
|
93
103
|
│ │ ├── api-settings.ts # AI settings API client (GET/PUT /api/settings/ai)
|
|
94
104
|
│ │ ├── ws-client.ts # WebSocket with exponential backoff + Cloudflare handshake
|
|
@@ -96,6 +106,7 @@ ppm/
|
|
|
96
106
|
│ │ ├── project-avatar.ts # Smart project initials (collision resolution)
|
|
97
107
|
│ │ ├── project-palette.ts # 12-color palette for project avatars
|
|
98
108
|
│ │ ├── use-monaco-theme.ts # Sync Monaco Editor theme with app theme
|
|
109
|
+
│ │ ├── color-utils.ts # WCAG color contrast helper
|
|
99
110
|
│ │ └── utils.ts # Helpers (cn, randomId, basename, etc.)
|
|
100
111
|
│ ├── styles/
|
|
101
112
|
│ │ └── globals.css # Tailwind directives, custom CSS
|
|
@@ -130,21 +141,33 @@ ppm/
|
|
|
130
141
|
│ │ ├── editor-panel.tsx # Wrapper for tab content within a panel
|
|
131
142
|
│ │ ├── project-bar.tsx # 52px sidebar with project avatars, share popover
|
|
132
143
|
│ │ ├── project-bottom-sheet.tsx # Mobile project switcher
|
|
133
|
-
│ │ ├── sidebar.tsx # Left sidebar (Explorer/Git/Settings tabs)
|
|
134
|
-
│ │ ├── tab-bar.tsx # Tab bar with icons
|
|
135
|
-
│ │ ├── draggable-tab.tsx # Draggable tab with context menu, rename
|
|
144
|
+
│ │ ├── sidebar.tsx # Left sidebar (Explorer/Git/Database/Settings tabs)
|
|
145
|
+
│ │ ├── tab-bar.tsx # Tab bar with icons, connection color display
|
|
146
|
+
│ │ ├── draggable-tab.tsx # Draggable tab with context menu, rename, connection color
|
|
136
147
|
│ │ ├── tab-content.tsx # Router for tab content
|
|
137
148
|
│ │ ├── split-drop-overlay.tsx # Drop zone for tab splitting
|
|
138
|
-
│ │ ├── command-palette.tsx # Global command palette (Shift+Shift)
|
|
149
|
+
│ │ ├── command-palette.tsx # Global command palette (Shift+Shift, DB table search)
|
|
139
150
|
│ │ ├── add-project-form.tsx # Modal form to add projects
|
|
140
151
|
│ │ ├── mobile-nav.tsx # Bottom navigation for mobile
|
|
141
152
|
│ │ └── mobile-drawer.tsx # Mobile overlay drawer
|
|
153
|
+
│ ├── database/ # Database management (5 files, 300+ LOC)
|
|
154
|
+
│ │ ├── database-sidebar.tsx # Sidebar tab container (connection list, form)
|
|
155
|
+
│ │ ├── connection-list.tsx # Connections list with actions, color badges
|
|
156
|
+
│ │ ├── connection-form-dialog.tsx # Create/edit connection form (SQLite/Postgres)
|
|
157
|
+
│ │ ├── connection-color-picker.tsx # WCAG contrast-aware color picker
|
|
158
|
+
│ │ └── use-connections.ts # Hook for connection CRUD operations
|
|
142
159
|
│ ├── projects/ # Project management (339 LOC, 2 files)
|
|
143
160
|
│ ├── settings/ # Settings panel (theme + AI provider config UI)
|
|
144
161
|
│ ├── terminal/ # xterm.js wrapper (143 LOC, 2 files)
|
|
145
162
|
│ ├── shared/ # Shared components (2 files)
|
|
146
163
|
│ │ ├── markdown-renderer.tsx # Render Markdown with syntax highlighting
|
|
147
164
|
│ │ └── bug-report-popup.tsx # Global bug report popup
|
|
165
|
+
│ ├── sqlite/ # SQLite viewer (unified connectionId API mode)
|
|
166
|
+
│ │ ├── sqlite-viewer.tsx # Display table data, execute queries
|
|
167
|
+
│ │ └── use-sqlite.ts # Hook for SQLite operations via /api/db routes
|
|
168
|
+
│ ├── postgres/ # PostgreSQL viewer (unified connectionId API mode)
|
|
169
|
+
│ │ ├── postgres-viewer.tsx # Display table data, execute queries
|
|
170
|
+
│ │ └── use-postgres.ts # Hook for Postgres operations via /api/db routes
|
|
148
171
|
│ └── ui/ # Radix + shadcn primitives (14 files)
|
|
149
172
|
│ └── button, input, label, dialog, dropdown-menu, select, tabs, tooltip, etc.
|
|
150
173
|
├── tests/
|
|
@@ -201,11 +224,11 @@ ppm/
|
|
|
201
224
|
- **Pattern:** Project-scoped routing via ProviderRegistry
|
|
202
225
|
|
|
203
226
|
### Service Layer (src/services/)
|
|
204
|
-
- **Responsibility:** Business logic, data operations, infrastructure (tunneling)
|
|
227
|
+
- **Responsibility:** Business logic, data operations, infrastructure (tunneling, database connections)
|
|
205
228
|
- **Services:**
|
|
206
229
|
- **ChatService** — Session lifecycle, message queueing, streaming
|
|
207
230
|
- **ConfigService** — Config loading (YAML→SQLite migration)
|
|
208
|
-
- **DbService** — SQLite persistence (
|
|
231
|
+
- **DbService** — SQLite persistence (8 tables, WAL mode, schema v3, connection CRUD, table cache)
|
|
209
232
|
- **GitService** — Git commands via simple-git
|
|
210
233
|
- **FileService** — File ops with path validation
|
|
211
234
|
- **ProjectService** — Project CRUD, scanning, resolution
|
|
@@ -215,7 +238,11 @@ ppm/
|
|
|
215
238
|
- **SessionLogService** — Audit logs with sensitive data redaction
|
|
216
239
|
- **CloudflaredService** — Download/cache cloudflared binary (platform-aware)
|
|
217
240
|
- **TunnelService** — Spawn tunnel, extract URL, cleanup on exit
|
|
218
|
-
- **
|
|
241
|
+
- **TableCacheService** — Cache table metadata across connections, search tables by name
|
|
242
|
+
- **DatabaseAdapterRegistry** — Register/retrieve DatabaseAdapter implementations (extensible pattern)
|
|
243
|
+
- **SQLiteAdapter** — SQLite connection/query execution with readonly checks
|
|
244
|
+
- **PostgresAdapter** — PostgreSQL connection/query execution with readonly checks
|
|
245
|
+
- **Pattern:** Singleton services, dependency injection via imports, adapter registry for extensibility
|
|
219
246
|
|
|
220
247
|
### Provider Layer (src/providers/)
|
|
221
248
|
- **Responsibility:** AI model abstraction, config-driven initialization
|
package/docs/project-roadmap.md
CHANGED
|
@@ -142,7 +142,31 @@ Multi-project, project-scoped API refactor with improved UX, Monaco Editor, auto
|
|
|
142
142
|
|
|
143
143
|
---
|
|
144
144
|
|
|
145
|
-
### Phase 9:
|
|
145
|
+
### Phase 9: Database Management ✅ Complete (260319)
|
|
146
|
+
|
|
147
|
+
**Features:**
|
|
148
|
+
- Unified database viewer for SQLite & PostgreSQL
|
|
149
|
+
- DatabaseAdapter extensible pattern
|
|
150
|
+
- Connection CRUD (create, edit, delete, color-code)
|
|
151
|
+
- Query execution with readonly safety
|
|
152
|
+
- Table browser with pagination & schema inspection
|
|
153
|
+
- CLI support (ppm db commands)
|
|
154
|
+
- Credentials stored securely in SQLite (never exposed in API)
|
|
155
|
+
- readonly=true by default (safe-by-default)
|
|
156
|
+
|
|
157
|
+
**Latest Work (260319):**
|
|
158
|
+
- SQLiteAdapter & PostgresAdapter implementations
|
|
159
|
+
- /api/db routes with connection sanitization
|
|
160
|
+
- Database sidebar UI with connection form, color picker
|
|
161
|
+
- TableCacheService for metadata caching & search
|
|
162
|
+
- isReadOnlyQuery() CTE-safe safety checks
|
|
163
|
+
- CLI db-cmd for database management
|
|
164
|
+
|
|
165
|
+
**Status:** Complete, fully integrated with v0.6.3
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
### Phase 10: PWA & Build ✅ Complete
|
|
146
170
|
- Vite build configuration
|
|
147
171
|
- Service worker (vite-plugin-pwa)
|
|
148
172
|
- Offline support (cached assets)
|
|
@@ -153,7 +177,7 @@ Multi-project, project-scoped API refactor with improved UX, Monaco Editor, auto
|
|
|
153
177
|
|
|
154
178
|
---
|
|
155
179
|
|
|
156
|
-
### Phase
|
|
180
|
+
### Phase 11: Testing ✅ In Progress (65%)
|
|
157
181
|
|
|
158
182
|
#### Unit Tests (50% complete)
|
|
159
183
|
- [x] Mock provider tests
|
|
@@ -229,17 +253,18 @@ Multi-project, project-scoped API refactor with improved UX, Monaco Editor, auto
|
|
|
229
253
|
- [x] URL sync for bookmarking/sharing
|
|
230
254
|
- [x] Project Switcher Bar (52px sidebar, avatars, colors, reordering) (260317)
|
|
231
255
|
- [x] Keep-alive workspace switching (preserve xterm DOM) (260317)
|
|
232
|
-
- [x] Sidebar tab system (Explorer/Git/History) (260317)
|
|
256
|
+
- [x] Sidebar tab system (Explorer/Git/History/Database tabs) (260317, 260319)
|
|
233
257
|
- [x] Monaco Editor migration (CodeMirror → Monaco, fully removed) (260317-260319)
|
|
234
258
|
- [x] Project color customization (12-color palette + custom hex) (260317)
|
|
235
259
|
- [x] Auto-generate chat session titles from SDK summary (260319)
|
|
236
260
|
- [x] Inline session rename UI (260319)
|
|
237
|
-
- [x]
|
|
261
|
+
- [x] Database Management (SQLite/PostgreSQL, adapters, UI, CLI) (260319)
|
|
262
|
+
- [x] SQLite migration (db.service.ts, backward YAML compat, connection tables v3) (260319)
|
|
238
263
|
- [ ] Complete test coverage (65% complete)
|
|
239
264
|
- [x] Documentation updates (260319)
|
|
240
265
|
- [ ] Security audit (planned)
|
|
241
266
|
|
|
242
|
-
**Release Status:** v0.
|
|
267
|
+
**Release Status:** v0.6.3 released, v2.0 + database management complete
|
|
243
268
|
|
|
244
269
|
---
|
|
245
270
|
|
|
@@ -366,7 +391,7 @@ Multi-project, project-scoped API refactor with improved UX, Monaco Editor, auto
|
|
|
366
391
|
| Version | Status | Features | Target Date |
|
|
367
392
|
|---------|--------|----------|-------------|
|
|
368
393
|
| **v1.0** | Released | Single project, basic chat, terminal | Feb 28, 2025 |
|
|
369
|
-
| **v2.0** | Complete (v0.
|
|
394
|
+
| **v2.0** | Complete (v0.6.3) | Multi-project, project-scoped API, improved UX, Monaco Editor, auto-title, database management | Mar 19, 2026 |
|
|
370
395
|
| **v2.1** | Planned | Complete test coverage, SQLite finalization, bug fixes | Apr 15, 2026 |
|
|
371
396
|
| **v3.0** | Planned | Collaborative editing, custom tools, plugins | Jun 30, 2026 |
|
|
372
397
|
| **v4.0** | Planned | Cloud sync, advanced git, profiling UI | Sep 30, 2026 |
|
|
@@ -18,16 +18,18 @@
|
|
|
18
18
|
│ │ Hono HTTP Framework (Port 8080) │ │
|
|
19
19
|
│ ├────────────────────────────────────────────────────────────────┤ │
|
|
20
20
|
│ │ Routes (src/server/routes/) │ │
|
|
21
|
-
│ │ ┌──────────────────┐ ┌──────────────────┐
|
|
22
|
-
│ │ │ /api/projects │ │ /api/project/:n/ │ │ /api/
|
|
23
|
-
│ │ │ (CRUD projects) │ │ (scoped routes) │ │
|
|
24
|
-
│ │ └──────────────────┘ └──────────────────┘
|
|
21
|
+
│ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐ │ │
|
|
22
|
+
│ │ │ /api/projects │ │ /api/project/:n/ │ │ /api/db/* │ │ │
|
|
23
|
+
│ │ │ (CRUD projects) │ │ (scoped routes) │ │ (connections)│ │ │
|
|
24
|
+
│ │ └──────────────────┘ └──────────────────┘ └──────────────┘ │ │
|
|
25
25
|
│ ├────────────────────────────────────────────────────────────────┤ │
|
|
26
26
|
│ │ Services (src/services/) │ │
|
|
27
27
|
│ │ ┌───────────────────────────────────────────────────────────┐│ │
|
|
28
28
|
│ │ │ ChatService │ GitService │ FileService │ TerminalService ││ │
|
|
29
29
|
│ │ │ (streaming │ (simple- │ (read/write │ (PTY/shell) ││ │
|
|
30
30
|
│ │ │ messages) │ git) │ files) │ (Bun.spawn) ││ │
|
|
31
|
+
│ │ │ TableCache │ DbService │ DatabaseAdapterRegistry ││ │
|
|
32
|
+
│ │ │ (metadata) │ (SQLite) │ (SQLite, PostgreSQL adapters) ││ │
|
|
31
33
|
│ │ └───────────────────────────────────────────────────────────┘│ │
|
|
32
34
|
│ ├────────────────────────────────────────────────────────────────┤ │
|
|
33
35
|
│ │ Providers (src/providers/) │ │
|
|
@@ -45,8 +47,10 @@
|
|
|
45
47
|
│ │ SQLite DB │ │ Git Repos │ │ Session Storage │ │
|
|
46
48
|
│ │ (config, projs) │ │ (local disk) │ │ (SQLite + SDK) │ │
|
|
47
49
|
│ │ (session map) │ │ │ │ (session_map, │ │
|
|
48
|
-
│ │ (push subs, │ │
|
|
49
|
-
│ │ usage, logs) │ │
|
|
50
|
+
│ │ (push subs, │ │ Connections: │ │ session_logs, │ │
|
|
51
|
+
│ │ usage, logs) │ │ • SQLite files │ │ usage_history) │ │
|
|
52
|
+
│ │ (connections) │ │ • PostgreSQL svr │ │ (connections) │ │
|
|
53
|
+
│ │ (table metadata) │ │ via connStr │ │ │ │
|
|
50
54
|
│ └──────────────────┘ └──────────────────┘ └─────────────────┘ │
|
|
51
55
|
└──────────────────────────────────────────────────────────────────────┘
|
|
52
56
|
↓↑
|
|
@@ -119,6 +123,15 @@ POST /api/project/:name/git/commit → Commit
|
|
|
119
123
|
GET /api/project/:name/files/tree → Directory tree
|
|
120
124
|
GET /api/project/:name/files/raw → File content
|
|
121
125
|
PUT /api/project/:name/files/write → Write file
|
|
126
|
+
GET /api/db/connections → List all connections
|
|
127
|
+
POST /api/db/connections → Create connection (SQLite/PostgreSQL)
|
|
128
|
+
GET /api/db/connections/:id → Get connection (sanitized)
|
|
129
|
+
PUT /api/db/connections/:id → Update connection (toggle readonly, UI-only)
|
|
130
|
+
DELETE /api/db/connections/:id → Delete connection
|
|
131
|
+
GET /api/db/connections/:id/tables → List tables (with sync)
|
|
132
|
+
GET /api/db/connections/:id/tables/:table → Get table schema + data
|
|
133
|
+
POST /api/db/connections/:id/query → Execute query (readonly checked)
|
|
134
|
+
PATCH /api/db/connections/:id/cell → Update cell value (single)
|
|
122
135
|
WS /ws/project/:name/chat/:sessionId → Chat streaming
|
|
123
136
|
WS /ws/project/:name/terminal/:id → Terminal I/O
|
|
124
137
|
```
|
|
@@ -140,7 +153,8 @@ WS /ws/project/:name/terminal/:id → Terminal I/O
|
|
|
140
153
|
|---------|---------|-------------|
|
|
141
154
|
| **ChatService** | Session management, message streaming | createSession, streamMessage, getHistory |
|
|
142
155
|
| **ConfigService** | Config loading (YAML→SQLite migration) | load, save, getToken |
|
|
143
|
-
| **DbService** | SQLite persistence (
|
|
156
|
+
| **DbService** | SQLite persistence (8 tables, WAL, connections CRUD) | getDb, openTestDb, getConnections, insertConnection, updateConnection, deleteConnection, getTableCache |
|
|
157
|
+
| **TableCacheService** | Cache table metadata, search tables | syncTables, searchTables, invalidateCache |
|
|
144
158
|
| **GitService** | Git command execution | status, diff, commit, stage, branch |
|
|
145
159
|
| **FileService** | File operations with validation | read, write, tree, delete, mkdir |
|
|
146
160
|
| **TerminalService** | PTY lifecycle, shell spawning | spawn, write, kill |
|
|
@@ -151,6 +165,9 @@ WS /ws/project/:name/terminal/:id → Terminal I/O
|
|
|
151
165
|
| **ProviderRegistry** | AI provider routing | getDefault, send (delegates) |
|
|
152
166
|
| **CloudflaredService** | Download cloudflared binary | ensureCloudflared, getCloudflaredPath |
|
|
153
167
|
| **TunnelService** | Cloudflare Quick Tunnel lifecycle | startTunnel, stopTunnel, getTunnelUrl |
|
|
168
|
+
| **DatabaseAdapterRegistry** | Register/retrieve DB adapters (extensible) | registerAdapter, getAdapter |
|
|
169
|
+
| **SQLiteAdapter** | SQLite connection, query execution, readonly checks | testConnection, getTables, getTableSchema, getTableData, executeQuery, updateCell |
|
|
170
|
+
| **PostgresAdapter** | PostgreSQL connection, query execution, readonly checks | testConnection, getTables, getTableSchema, getTableData, executeQuery, updateCell |
|
|
154
171
|
|
|
155
172
|
**Key Files:** `src/services/*.service.ts`
|
|
156
173
|
|
|
@@ -544,6 +561,204 @@ UI updates: "src/index.ts" moves from "Unstaged" to "Staged"
|
|
|
544
561
|
|
|
545
562
|
---
|
|
546
563
|
|
|
564
|
+
## Database Management (v2.0+)
|
|
565
|
+
|
|
566
|
+
### Architecture Overview
|
|
567
|
+
|
|
568
|
+
PPM now supports managing external databases (SQLite & PostgreSQL) through a unified adapter pattern:
|
|
569
|
+
|
|
570
|
+
```
|
|
571
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
572
|
+
│ Web UI (React) │
|
|
573
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
574
|
+
│ │ Database Sidebar │ │
|
|
575
|
+
│ │ • Connection List (with color badges) │ │
|
|
576
|
+
│ │ • Create/Edit Connection Form │ │
|
|
577
|
+
│ │ • Color Picker (WCAG contrast-aware) │ │
|
|
578
|
+
│ │ • Query Execution UI │ │
|
|
579
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
580
|
+
└─────────────────┬───────────────────────────────────────────────┘
|
|
581
|
+
│ HTTP REST / WebSocket
|
|
582
|
+
┌─────────────────┴───────────────────────────────────────────────┐
|
|
583
|
+
│ PPM Server (Hono) │
|
|
584
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
585
|
+
│ │ /api/db Routes │ │
|
|
586
|
+
│ │ • GET /connections → List all connections │ │
|
|
587
|
+
│ │ • POST /connections → Create connection │ │
|
|
588
|
+
│ │ • GET /connections/:id → Get connection (sanitized) │ │
|
|
589
|
+
│ │ • PUT /connections/:id → Update (readonly toggle) │ │
|
|
590
|
+
│ │ • DELETE /connections/:id → Remove connection │ │
|
|
591
|
+
│ │ • GET /connections/:id/tables → List + sync tables │ │
|
|
592
|
+
│ │ • GET /connections/:id/tables/:tbl → Schema + data │ │
|
|
593
|
+
│ │ • POST /connections/:id/query → Execute query │ │
|
|
594
|
+
│ │ • PATCH /connections/:id/cell → Update cell │ │
|
|
595
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
596
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
597
|
+
│ │ Service Layer │ │
|
|
598
|
+
│ │ • DbService (connection CRUD, caching) │ │
|
|
599
|
+
│ │ • TableCacheService (metadata cache, search) │ │
|
|
600
|
+
│ │ • DatabaseAdapterRegistry (extensible) │ │
|
|
601
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
602
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
603
|
+
│ │ Adapters (Pluggable Pattern) │ │
|
|
604
|
+
│ │ • SQLiteAdapter → Uses `bun:sqlite` for local files │ │
|
|
605
|
+
│ │ • PostgresAdapter → Uses postgres driver for servers │ │
|
|
606
|
+
│ │ • isReadOnlyQuery() → Safety check (CTE-safe regex) │ │
|
|
607
|
+
│ │ • readonly=1 by default (safe-by-default) │ │
|
|
608
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
609
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
610
|
+
↓↑
|
|
611
|
+
┌────────────────────────────────────────────┐
|
|
612
|
+
│ External Databases │
|
|
613
|
+
│ • SQLite files (path: /path/to/db.db) │
|
|
614
|
+
│ • PostgreSQL servers (connStr: postgres://)│
|
|
615
|
+
└────────────────────────────────────────────┘
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### DatabaseAdapter Pattern (Extensible)
|
|
619
|
+
|
|
620
|
+
**Interface** (`src/types/database.ts`):
|
|
621
|
+
```typescript
|
|
622
|
+
interface DatabaseAdapter {
|
|
623
|
+
testConnection(config: DbConnectionConfig): Promise<{ ok: boolean; error?: string }>;
|
|
624
|
+
getTables(config: DbConnectionConfig): Promise<DbTableInfo[]>;
|
|
625
|
+
getTableSchema(config: DbConnectionConfig, table: string, schema?: string): Promise<DbColumnInfo[]>;
|
|
626
|
+
getTableData(config: DbConnectionConfig, table: string, opts: {...}): Promise<DbPagedData>;
|
|
627
|
+
executeQuery(config: DbConnectionConfig, sql: string): Promise<DbQueryResult>;
|
|
628
|
+
updateCell(config: DbConnectionConfig, table: string, opts: {...}): Promise<void>;
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
**Implementations:**
|
|
633
|
+
1. **SQLiteAdapter** — Local file-based SQLite via `bun:sqlite`
|
|
634
|
+
- testConnection: Opens file, runs pragma check
|
|
635
|
+
- Supports: SELECT, INSERT, UPDATE, DELETE (if writable), CREATE TABLE
|
|
636
|
+
|
|
637
|
+
2. **PostgresAdapter** — Remote PostgreSQL servers via postgres driver
|
|
638
|
+
- testConnection: Attempts connection with credentials
|
|
639
|
+
- Supports: Full SQL except DDL on readonly connections
|
|
640
|
+
|
|
641
|
+
**Registry Pattern** (`src/services/database/adapter-registry.ts`):
|
|
642
|
+
```typescript
|
|
643
|
+
registerAdapter("sqlite", new SQLiteAdapter());
|
|
644
|
+
registerAdapter("postgres", new PostgresAdapter());
|
|
645
|
+
// Can be extended: registerAdapter("mysql", new MysqlAdapter());
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
### Security Design
|
|
649
|
+
|
|
650
|
+
**Readonly by Default:**
|
|
651
|
+
- All connections created with `readonly = true` in database
|
|
652
|
+
- Default: read-only query execution (safe-by-default)
|
|
653
|
+
- Web UI toggle: Switch to writable (admin decision only)
|
|
654
|
+
- CLI: Cannot disable readonly via command-line (browser only)
|
|
655
|
+
|
|
656
|
+
**Readonly Query Detection:**
|
|
657
|
+
```typescript
|
|
658
|
+
// isReadOnlyQuery() in src/services/database/readonly-check.ts
|
|
659
|
+
// Checks for: SELECT, PRAGMA, EXPLAIN, WITH (CTE)
|
|
660
|
+
// Rejects: INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, etc.
|
|
661
|
+
// CTE-safe: Handles "WITH AS SELECT" (wraps CTE result check)
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
**Credential Handling:**
|
|
665
|
+
- Connection credentials stored in SQLite `connections` table as `connection_config` JSON
|
|
666
|
+
- **NEVER** returned in API responses (stripped by `sanitizeConn()` in routes)
|
|
667
|
+
- Only used internally by adapters when executing queries
|
|
668
|
+
- Frontend never sees passwords/connection strings
|
|
669
|
+
|
|
670
|
+
**API Security:**
|
|
671
|
+
- All `/api/db` requests require valid auth token (middleware checked)
|
|
672
|
+
- Connection IDs are numeric (no enumeration risk)
|
|
673
|
+
- Connection color is user-specific (cosmetic only, not sensitive)
|
|
674
|
+
|
|
675
|
+
### Data Flow: Query Execution
|
|
676
|
+
|
|
677
|
+
```
|
|
678
|
+
User opens Database tab
|
|
679
|
+
↓
|
|
680
|
+
DatabaseSidebar fetches: GET /api/db/connections
|
|
681
|
+
↓
|
|
682
|
+
ConnectionList displays (sanitized, no credentials)
|
|
683
|
+
↓
|
|
684
|
+
User clicks connection → GET /api/db/connections/:id/tables
|
|
685
|
+
↓
|
|
686
|
+
DbService.getConnections() reads from SQLite
|
|
687
|
+
↓
|
|
688
|
+
TableCacheService.syncTables() calls adapter.getTables()
|
|
689
|
+
↓
|
|
690
|
+
SQLiteAdapter/PostgresAdapter queries database
|
|
691
|
+
↓
|
|
692
|
+
Results cached in table_metadata table
|
|
693
|
+
↓
|
|
694
|
+
UI displays table list + schema
|
|
695
|
+
↓
|
|
696
|
+
User selects table → GET /api/db/connections/:id/tables/:table
|
|
697
|
+
↓
|
|
698
|
+
Adapter.getTableData() executes paginated query
|
|
699
|
+
↓
|
|
700
|
+
Results returned: { columns, rows, total, page, limit }
|
|
701
|
+
↓
|
|
702
|
+
UI renders table grid with pagination
|
|
703
|
+
↓
|
|
704
|
+
User executes custom query → POST /api/db/connections/:id/query
|
|
705
|
+
↓
|
|
706
|
+
isReadOnlyQuery() checks SQL (rejects writes if readonly=true)
|
|
707
|
+
↓
|
|
708
|
+
Adapter.executeQuery() runs SQL
|
|
709
|
+
↓
|
|
710
|
+
Results returned: { columns, rows, rowsAffected, changeType }
|
|
711
|
+
↓
|
|
712
|
+
UI displays results (read-only highlight if mutation was blocked)
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
### Connection Storage
|
|
716
|
+
|
|
717
|
+
**SQLite Schema** (in `~/.ppm/ppm.db`):
|
|
718
|
+
```sql
|
|
719
|
+
CREATE TABLE connections (
|
|
720
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
721
|
+
type TEXT NOT NULL, -- 'sqlite' | 'postgres'
|
|
722
|
+
name TEXT NOT NULL,
|
|
723
|
+
connection_config TEXT NOT NULL, -- JSON: { path, connectionString, ... }
|
|
724
|
+
readonly INTEGER DEFAULT 1, -- 1 = readonly, 0 = writable (UI-only toggle)
|
|
725
|
+
group_name TEXT,
|
|
726
|
+
color TEXT, -- Optional hex color (#3b82f6)
|
|
727
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
728
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
729
|
+
);
|
|
730
|
+
|
|
731
|
+
CREATE TABLE table_metadata (
|
|
732
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
733
|
+
connection_id INTEGER NOT NULL REFERENCES connections(id) ON DELETE CASCADE,
|
|
734
|
+
table_name TEXT NOT NULL,
|
|
735
|
+
schema_name TEXT DEFAULT 'public',
|
|
736
|
+
row_count INTEGER,
|
|
737
|
+
last_synced TEXT,
|
|
738
|
+
UNIQUE(connection_id, table_name, schema_name)
|
|
739
|
+
);
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
### CLI Support (ppm db)
|
|
743
|
+
|
|
744
|
+
**Commands** (`src/cli/commands/db-cmd.ts`):
|
|
745
|
+
```bash
|
|
746
|
+
ppm db connections # List all connections
|
|
747
|
+
ppm db connect # Add new connection (interactive)
|
|
748
|
+
ppm db remove <name> # Delete connection
|
|
749
|
+
ppm db query <name> <sql> # Execute query (respects readonly)
|
|
750
|
+
ppm db tables <name> # List tables
|
|
751
|
+
ppm db schema <name> <table> # Show table schema
|
|
752
|
+
ppm db data <name> <table> # Show table data (paginated)
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
**CLI Safety:**
|
|
756
|
+
- Always respects readonly flag (cannot override via CLI)
|
|
757
|
+
- Uses same adapter/validation as web UI
|
|
758
|
+
- Table formatting for terminal output
|
|
759
|
+
|
|
760
|
+
---
|
|
761
|
+
|
|
547
762
|
## Deployment Architecture
|
|
548
763
|
|
|
549
764
|
### Single-Machine Deployment (Current)
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import { isReadOnlyQuery } from "../../services/database/readonly-check.ts";
|
|
2
3
|
|
|
3
4
|
const C = {
|
|
4
5
|
reset: "\x1b[0m",
|
|
@@ -53,6 +54,12 @@ function parseConfig(row: { type: string; connection_config: string }): { type:
|
|
|
53
54
|
return { type: row.type, ...cfg };
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
/** Mask password in postgres connection string: postgresql://user:pass@host → postgresql://user:***@host */
|
|
58
|
+
function maskPassword(connectionString: string): string {
|
|
59
|
+
return connectionString.replace(/(:\/\/[^:]+:)[^@]+(@)/, "$1***$2");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
56
63
|
export function registerDbCommands(program: Command): void {
|
|
57
64
|
const db = program.command("db").description("Manage database connections and execute queries");
|
|
58
65
|
|
|
@@ -69,11 +76,14 @@ export function registerDbCommands(program: Command): void {
|
|
|
69
76
|
}
|
|
70
77
|
const rows = conns.map((c) => {
|
|
71
78
|
const cfg = parseConfig(c);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
let target = cfg.connectionString ?? cfg.path ?? "-";
|
|
80
|
+
// Mask password in postgres connection strings
|
|
81
|
+
if (cfg.connectionString) target = maskPassword(target);
|
|
82
|
+
const display = target.length > 70 ? target.slice(0, 67) + "..." : target;
|
|
83
|
+
const ro = c.readonly ? `${C.yellow}RO${C.reset}` : `${C.green}RW${C.reset}`;
|
|
84
|
+
return [String(c.id), c.name, c.type, c.group_name ?? "-", ro, display];
|
|
75
85
|
});
|
|
76
|
-
printTable(["ID", "Name", "Type", "Group", "Connection"], rows);
|
|
86
|
+
printTable(["ID", "Name", "Type", "Group", "RO", "Connection"], rows);
|
|
77
87
|
} catch (err) {
|
|
78
88
|
console.error(`${C.red}Error:${C.reset}`, (err as Error).message);
|
|
79
89
|
process.exit(1);
|
|
@@ -313,6 +323,13 @@ export function registerDbCommands(program: Command): void {
|
|
|
313
323
|
}
|
|
314
324
|
const cfg = parseConfig(conn);
|
|
315
325
|
|
|
326
|
+
// Enforce readonly — CLI cannot disable this, only the web UI can toggle it
|
|
327
|
+
if (conn.readonly && !isReadOnlyQuery(sql)) {
|
|
328
|
+
console.error(`${C.red}Error:${C.reset} Connection "${conn.name}" is readonly — only SELECT queries allowed.`);
|
|
329
|
+
console.error(` To allow writes, toggle the readonly switch in the PPM web UI.`);
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
|
|
316
333
|
if (conn.type === "postgres") {
|
|
317
334
|
const { postgresService } = await import("../../services/postgres.service.ts");
|
|
318
335
|
const result = await postgresService.executeQuery(cfg.connectionString!, sql);
|
package/src/server/index.ts
CHANGED
|
@@ -10,6 +10,8 @@ import { tunnelRoutes } from "./routes/tunnel.ts";
|
|
|
10
10
|
import { staticRoutes } from "./routes/static.ts";
|
|
11
11
|
import { projectScopedRouter } from "./routes/project-scoped.ts";
|
|
12
12
|
import { postgresRoutes } from "./routes/postgres.ts";
|
|
13
|
+
import { databaseRoutes } from "./routes/database.ts";
|
|
14
|
+
import { initAdapters } from "../services/database/init-adapters.ts";
|
|
13
15
|
import { terminalWebSocket } from "./ws/terminal.ts";
|
|
14
16
|
import { chatWebSocket } from "./ws/chat.ts";
|
|
15
17
|
import { ok, err } from "../types/api.ts";
|
|
@@ -57,6 +59,9 @@ async function setupLogFile() {
|
|
|
57
59
|
});
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
// Register database adapters at module load time
|
|
63
|
+
initAdapters();
|
|
64
|
+
|
|
60
65
|
export const app = new Hono();
|
|
61
66
|
|
|
62
67
|
// CORS for dev
|
|
@@ -185,6 +190,7 @@ app.route("/api/push", pushRoutes);
|
|
|
185
190
|
app.route("/api/projects", projectRoutes);
|
|
186
191
|
app.route("/api/project/:projectName", projectScopedRouter);
|
|
187
192
|
app.route("/api/postgres", postgresRoutes);
|
|
193
|
+
app.route("/api/db", databaseRoutes);
|
|
188
194
|
|
|
189
195
|
// Static files / SPA fallback (non-API routes)
|
|
190
196
|
app.route("/", staticRoutes);
|