@hienlh/ppm 0.1.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/.claude/agent-memory/tester/MEMORY.md +3 -0
- package/.claude/agent-memory/tester/project-ppm-test-conventions.md +32 -0
- package/.env.example +1 -0
- package/.github/workflows/release.yml +46 -0
- package/README.md +349 -0
- package/bun.lock +1217 -0
- package/components.json +21 -0
- package/docs/code-standards.md +574 -0
- package/docs/codebase-summary.md +294 -0
- package/docs/deployment-guide.md +631 -0
- package/docs/design-guidelines.md +661 -0
- package/docs/project-overview-pdr.md +142 -0
- package/docs/project-roadmap.md +400 -0
- package/docs/system-architecture.md +459 -0
- package/package.json +68 -0
- package/plans/260314-2009-ppm-implementation/phase-01-project-skeleton.md +81 -0
- package/plans/260314-2009-ppm-implementation/phase-02-backend-core.md +148 -0
- package/plans/260314-2009-ppm-implementation/phase-03-frontend-shell.md +256 -0
- package/plans/260314-2009-ppm-implementation/phase-04-file-explorer-editor.md +120 -0
- package/plans/260314-2009-ppm-implementation/phase-05-web-terminal.md +174 -0
- package/plans/260314-2009-ppm-implementation/phase-06-git-integration.md +244 -0
- package/plans/260314-2009-ppm-implementation/phase-07-ai-chat.md +242 -0
- package/plans/260314-2009-ppm-implementation/phase-08-cli-commands.md +143 -0
- package/plans/260314-2009-ppm-implementation/phase-09-pwa-build-deploy.md +209 -0
- package/plans/260314-2009-ppm-implementation/phase-10-testing.md +311 -0
- package/plans/260314-2009-ppm-implementation/plan.md +202 -0
- package/plans/260315-0356-project-scoped-api-refactor/phase-01-backend-project-router.md +145 -0
- package/plans/260315-0356-project-scoped-api-refactor/phase-02-frontend-api-migration.md +107 -0
- package/plans/260315-0356-project-scoped-api-refactor/phase-03-per-project-tabs.md +100 -0
- package/plans/260315-0356-project-scoped-api-refactor/phase-04-websocket-migration.md +66 -0
- package/plans/260315-0356-project-scoped-api-refactor/plan.md +87 -0
- package/plans/reports/brainstorm-260314-1938-final-techstack.md +342 -0
- package/plans/reports/docs-manager-260315-1314-documentation-creation.md +386 -0
- package/plans/reports/fullstack-developer-260314-2252-phase-02-backend-core.md +57 -0
- package/plans/reports/fullstack-developer-260314-2253-phase-03-frontend-shell.md +70 -0
- package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-api-terminal-ws.md +49 -0
- package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-explorer-editor-terminal.md +52 -0
- package/plans/reports/fullstack-developer-260314-2307-ai-chat-phase7.md +58 -0
- package/plans/reports/fullstack-developer-260314-2307-phase-06-git-integration.md +33 -0
- package/plans/reports/research-260314-1911-ppm-tech-stack.md +318 -0
- package/plans/reports/research-260314-1930-claude-code-integration.md +293 -0
- package/plans/reports/researcher-260314-2232-node-pty-bun-crash-analysis.md +305 -0
- package/plans/reports/researcher-260314-2232-ui-style.md +942 -0
- package/plans/reports/researcher-260315-0300-opcode-claude-interaction.md +745 -0
- package/plans/reports/researcher-260315-0303-opcode-deep-analysis.md +742 -0
- package/plans/reports/researcher-260315-0305-claude-agent-sdk-github-research.md +423 -0
- package/plans/reports/tester-260314-2053-initial-test-suite.md +81 -0
- package/ppm.example.yaml +14 -0
- package/repomix-output.xml +23745 -0
- package/scripts/build.ts +13 -0
- package/src/cli/commands/chat-cmd.ts +259 -0
- package/src/cli/commands/config-cmd.ts +121 -0
- package/src/cli/commands/git-cmd.ts +315 -0
- package/src/cli/commands/init.ts +57 -0
- package/src/cli/commands/open.ts +19 -0
- package/src/cli/commands/projects.ts +100 -0
- package/src/cli/commands/start.ts +3 -0
- package/src/cli/commands/stop.ts +33 -0
- package/src/cli/utils/project-resolver.ts +27 -0
- package/src/index.ts +59 -0
- package/src/providers/claude-agent-sdk.ts +499 -0
- package/src/providers/claude-binary-finder.ts +256 -0
- package/src/providers/claude-code-cli.ts +413 -0
- package/src/providers/claude-process-registry.ts +106 -0
- package/src/providers/mock-provider.ts +171 -0
- package/src/providers/provider.interface.ts +10 -0
- package/src/providers/registry.ts +45 -0
- package/src/server/helpers/resolve-project.ts +22 -0
- package/src/server/index.ts +181 -0
- package/src/server/middleware/auth.ts +30 -0
- package/src/server/routes/chat.ts +153 -0
- package/src/server/routes/files.ts +168 -0
- package/src/server/routes/git.ts +261 -0
- package/src/server/routes/project-scoped.ts +27 -0
- package/src/server/routes/projects.ts +57 -0
- package/src/server/routes/static.ts +26 -0
- package/src/server/ws/chat.ts +130 -0
- package/src/server/ws/terminal.ts +89 -0
- package/src/services/chat.service.ts +110 -0
- package/src/services/claude-usage.service.ts +113 -0
- package/src/services/config.service.ts +90 -0
- package/src/services/file.service.ts +261 -0
- package/src/services/git-dirs.service.ts +112 -0
- package/src/services/git.service.ts +372 -0
- package/src/services/project.service.ts +107 -0
- package/src/services/slash-items.service.ts +184 -0
- package/src/services/terminal.service.ts +212 -0
- package/src/types/api.ts +37 -0
- package/src/types/chat.ts +92 -0
- package/src/types/config.ts +41 -0
- package/src/types/git.ts +50 -0
- package/src/types/project.ts +18 -0
- package/src/types/terminal.ts +20 -0
- package/src/web/app.tsx +168 -0
- package/src/web/components/auth/login-screen.tsx +88 -0
- package/src/web/components/chat/attachment-chips.tsx +55 -0
- package/src/web/components/chat/chat-placeholder.tsx +10 -0
- package/src/web/components/chat/chat-tab.tsx +301 -0
- package/src/web/components/chat/file-picker.tsx +126 -0
- package/src/web/components/chat/message-input.tsx +420 -0
- package/src/web/components/chat/message-list.tsx +838 -0
- package/src/web/components/chat/session-picker.tsx +139 -0
- package/src/web/components/chat/slash-command-picker.tsx +135 -0
- package/src/web/components/chat/usage-badge.tsx +186 -0
- package/src/web/components/editor/code-editor.tsx +329 -0
- package/src/web/components/editor/diff-viewer.tsx +276 -0
- package/src/web/components/editor/editor-placeholder.tsx +10 -0
- package/src/web/components/explorer/file-actions.tsx +191 -0
- package/src/web/components/explorer/file-tree.tsx +298 -0
- package/src/web/components/git/git-graph.tsx +727 -0
- package/src/web/components/git/git-placeholder.tsx +55 -0
- package/src/web/components/git/git-status-panel.tsx +850 -0
- package/src/web/components/layout/mobile-drawer.tsx +137 -0
- package/src/web/components/layout/mobile-nav.tsx +103 -0
- package/src/web/components/layout/sidebar.tsx +90 -0
- package/src/web/components/layout/tab-bar.tsx +152 -0
- package/src/web/components/layout/tab-content.tsx +85 -0
- package/src/web/components/projects/dir-suggest.tsx +152 -0
- package/src/web/components/projects/project-list.tsx +187 -0
- package/src/web/components/settings/settings-tab.tsx +57 -0
- package/src/web/components/terminal/terminal-placeholder.tsx +10 -0
- package/src/web/components/terminal/terminal-tab.tsx +133 -0
- package/src/web/components/ui/button.tsx +64 -0
- package/src/web/components/ui/context-menu.tsx +250 -0
- package/src/web/components/ui/dialog.tsx +156 -0
- package/src/web/components/ui/dropdown-menu.tsx +257 -0
- package/src/web/components/ui/input.tsx +21 -0
- package/src/web/components/ui/scroll-area.tsx +56 -0
- package/src/web/components/ui/separator.tsx +26 -0
- package/src/web/components/ui/sonner.tsx +40 -0
- package/src/web/components/ui/tabs.tsx +91 -0
- package/src/web/components/ui/tooltip.tsx +57 -0
- package/src/web/hooks/use-chat.ts +420 -0
- package/src/web/hooks/use-terminal.ts +182 -0
- package/src/web/hooks/use-url-sync.ts +66 -0
- package/src/web/hooks/use-websocket.ts +48 -0
- package/src/web/index.html +16 -0
- package/src/web/lib/api-client.ts +90 -0
- package/src/web/lib/file-support.ts +68 -0
- package/src/web/lib/utils.ts +6 -0
- package/src/web/lib/ws-client.ts +100 -0
- package/src/web/main.tsx +10 -0
- package/src/web/public/icon-192.svg +5 -0
- package/src/web/public/icon-512.svg +5 -0
- package/src/web/stores/file-store.ts +81 -0
- package/src/web/stores/project-store.ts +50 -0
- package/src/web/stores/settings-store.ts +65 -0
- package/src/web/stores/tab-store.ts +187 -0
- package/src/web/styles/globals.css +227 -0
- package/src/web/vite-env.d.ts +1 -0
- package/tests/integration/api/chat-routes.test.ts +95 -0
- package/tests/integration/claude-agent-sdk-integration.test.ts +228 -0
- package/tests/integration/ws/chat-websocket.test.ts +312 -0
- package/tests/test-setup.ts +5 -0
- package/tests/unit/providers/claude-agent-sdk.test.ts +339 -0
- package/tests/unit/providers/mock-provider.test.ts +143 -0
- package/tests/unit/services/chat-service.test.ts +100 -0
- package/tsconfig.json +32 -0
- package/vite.config.ts +62 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
# PPM System Architecture
|
|
2
|
+
|
|
3
|
+
## High-Level Architecture
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
┌──────────────────────────────────────────────────────────────────────┐
|
|
7
|
+
│ User Devices │
|
|
8
|
+
│ ┌─────────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │
|
|
9
|
+
│ │ Desktop/Tab │ │ Mobile/iPad │ │ Terminal (CLI mode) │ │
|
|
10
|
+
│ │ Web Browser │ │ Web Browser │ │ STDIN → ppm chat │ │
|
|
11
|
+
│ └────────┬────────┘ └──────┬───────┘ └────────────┬─────────────┘ │
|
|
12
|
+
│ │ │ │ │
|
|
13
|
+
│ └───────────────────┼────────────────────────┘ │
|
|
14
|
+
│ │ HTTP/WebSocket │
|
|
15
|
+
├──────────────────────────────┼────────────────────────────────────────┤
|
|
16
|
+
│ PPM Server (Bun) │
|
|
17
|
+
│ ┌────────────────────────────────────────────────────────────────┐ │
|
|
18
|
+
│ │ Hono HTTP Framework (Port 8080) │ │
|
|
19
|
+
│ ├────────────────────────────────────────────────────────────────┤ │
|
|
20
|
+
│ │ Routes (src/server/routes/) │ │
|
|
21
|
+
│ │ ┌──────────────────┐ ┌──────────────────┐ ┌─────────────┐ │ │
|
|
22
|
+
│ │ │ /api/projects │ │ /api/project/:n/ │ │ /api/health │ │ │
|
|
23
|
+
│ │ │ (CRUD projects) │ │ (scoped routes) │ │ (status) │ │ │
|
|
24
|
+
│ │ └──────────────────┘ └──────────────────┘ └─────────────┘ │ │
|
|
25
|
+
│ ├────────────────────────────────────────────────────────────────┤ │
|
|
26
|
+
│ │ Services (src/services/) │ │
|
|
27
|
+
│ │ ┌───────────────────────────────────────────────────────────┐│ │
|
|
28
|
+
│ │ │ ChatService │ GitService │ FileService │ TerminalService ││ │
|
|
29
|
+
│ │ │ (streaming │ (simple- │ (read/write │ (PTY/shell) ││ │
|
|
30
|
+
│ │ │ messages) │ git) │ files) │ (Bun.spawn) ││ │
|
|
31
|
+
│ │ └───────────────────────────────────────────────────────────┘│ │
|
|
32
|
+
│ ├────────────────────────────────────────────────────────────────┤ │
|
|
33
|
+
│ │ Providers (src/providers/) │ │
|
|
34
|
+
│ │ ┌──────────────────────────────────────────────────────────┐ │ │
|
|
35
|
+
│ │ │ ProviderRegistry (routes to active AI provider) │ │ │
|
|
36
|
+
│ │ │ ┌───────────────────────┬──────────────────────────┐ │ │ │
|
|
37
|
+
│ │ │ │ claude-agent-sdk │ claude-code-cli (CLI) │ │ │ │
|
|
38
|
+
│ │ │ │ @anthropic/SDK (prim) │ Fallback subprocess │ │ │ │
|
|
39
|
+
│ │ │ └───────────────────────┴──────────────────────────┘ │ │ │
|
|
40
|
+
│ │ └──────────────────────────────────────────────────────────┘ │ │
|
|
41
|
+
│ └────────────────────────────────────────────────────────────────┘ │
|
|
42
|
+
│ │
|
|
43
|
+
│ Config & State (src/services/) │
|
|
44
|
+
│ ┌──────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │
|
|
45
|
+
│ │ ppm.yaml │ │ Git Repos │ │ Session Storage │ │
|
|
46
|
+
│ │ (projects list) │ │ (local disk) │ │ (in-memory only)│ │
|
|
47
|
+
│ │ (auth token) │ │ │ │ │ │
|
|
48
|
+
│ └──────────────────┘ └──────────────────┘ └─────────────────┘ │
|
|
49
|
+
└──────────────────────────────────────────────────────────────────────┘
|
|
50
|
+
↓↑
|
|
51
|
+
┌────────────────────────────────────────────────┐
|
|
52
|
+
│ Filesystem Access (Local Only) │
|
|
53
|
+
│ • Project directories (git repos) │
|
|
54
|
+
│ • File read/write operations │
|
|
55
|
+
│ • Config file (ppm.yaml) │
|
|
56
|
+
└────────────────────────────────────────────────┘
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Layer Descriptions
|
|
60
|
+
|
|
61
|
+
### Presentation Layer (Browser/CLI)
|
|
62
|
+
**Components:** React frontend + CLI commands
|
|
63
|
+
|
|
64
|
+
**Responsibilities:**
|
|
65
|
+
- Render UI for file explorer, editor, terminal, chat
|
|
66
|
+
- Capture user input (text, file uploads, terminal commands)
|
|
67
|
+
- Display streaming responses, terminal output
|
|
68
|
+
- Handle authentication (token in localStorage)
|
|
69
|
+
|
|
70
|
+
**Key Files:**
|
|
71
|
+
- `src/web/app.tsx` — Root React component
|
|
72
|
+
- `src/web/components/` — UI components
|
|
73
|
+
- `src/cli/commands/` — CLI command handlers
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### HTTP API Layer (Hono)
|
|
78
|
+
**Component:** Hono framework, request routing
|
|
79
|
+
|
|
80
|
+
**Responsibilities:**
|
|
81
|
+
- Parse HTTP requests, validate tokens
|
|
82
|
+
- Route to correct handler (projects, chat, git, files)
|
|
83
|
+
- Format responses in `ApiResponse` envelope
|
|
84
|
+
- Handle WebSocket upgrades
|
|
85
|
+
|
|
86
|
+
**Key Files:**
|
|
87
|
+
- `src/server/index.ts` — Server setup, middleware chain
|
|
88
|
+
- `src/server/routes/projects.ts` — Project CRUD
|
|
89
|
+
- `src/server/routes/project-scoped.ts` — Mount per-project routes
|
|
90
|
+
- `src/server/middleware/auth.ts` — Token validation
|
|
91
|
+
|
|
92
|
+
**Routes:**
|
|
93
|
+
```
|
|
94
|
+
GET /api/health → Health check
|
|
95
|
+
GET /api/auth/check → Verify auth token
|
|
96
|
+
POST /api/projects → Create project
|
|
97
|
+
GET /api/projects → List projects
|
|
98
|
+
DELETE /api/projects/:name → Delete project
|
|
99
|
+
GET /api/project/:name/chat/sessions → List sessions
|
|
100
|
+
POST /api/project/:name/chat/sessions → Create session
|
|
101
|
+
GET /api/project/:name/chat/sessions/:id/messages → Get history
|
|
102
|
+
DELETE /api/project/:name/chat/sessions/:id → Delete session
|
|
103
|
+
GET /api/project/:name/git/status → Git status
|
|
104
|
+
GET /api/project/:name/git/diff → Diff
|
|
105
|
+
POST /api/project/:name/git/stage → Stage file
|
|
106
|
+
POST /api/project/:name/git/commit → Commit
|
|
107
|
+
GET /api/project/:name/files/tree → Directory tree
|
|
108
|
+
GET /api/project/:name/files/raw → File content
|
|
109
|
+
PUT /api/project/:name/files/write → Write file
|
|
110
|
+
WS /ws/project/:name/chat/:sessionId → Chat streaming
|
|
111
|
+
WS /ws/project/:name/terminal/:id → Terminal I/O
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### Service Layer (Business Logic)
|
|
117
|
+
**Components:** Singleton service modules
|
|
118
|
+
|
|
119
|
+
**Responsibilities:**
|
|
120
|
+
- Implement core business logic (chat, git, files, terminal)
|
|
121
|
+
- Manage dependencies (file paths, command execution)
|
|
122
|
+
- Coordinate between providers and data sources
|
|
123
|
+
- Validate input and propagate errors
|
|
124
|
+
|
|
125
|
+
**Services:**
|
|
126
|
+
|
|
127
|
+
| Service | Purpose | Key Methods |
|
|
128
|
+
|---------|---------|-------------|
|
|
129
|
+
| **ChatService** | Session management, message streaming | createSession, streamMessage, getHistory |
|
|
130
|
+
| **GitService** | Git command execution | status, diff, commit, stage, branch |
|
|
131
|
+
| **FileService** | File operations with validation | read, write, tree, delete, mkdir |
|
|
132
|
+
| **TerminalService** | PTY lifecycle, shell spawning | spawn, write, kill |
|
|
133
|
+
| **ProjectService** | Project registry (YAML) | add, remove, get, list |
|
|
134
|
+
| **ConfigService** | Config file management | load, save, getToken |
|
|
135
|
+
| **ProviderRegistry** | AI provider routing | getDefault, send (delegates) |
|
|
136
|
+
|
|
137
|
+
**Key Files:** `src/services/*.service.ts`
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### Provider Layer (AI Adapters)
|
|
142
|
+
**Component:** Provider interface + implementations
|
|
143
|
+
|
|
144
|
+
**Responsibilities:**
|
|
145
|
+
- Abstract AI model differences behind common interface
|
|
146
|
+
- Stream responses as async generators
|
|
147
|
+
- Handle tool use and approval flows
|
|
148
|
+
- Track token usage
|
|
149
|
+
|
|
150
|
+
**Interface (src/providers/provider.interface.ts):**
|
|
151
|
+
```typescript
|
|
152
|
+
interface AIProvider {
|
|
153
|
+
createSession(): Promise<Session>;
|
|
154
|
+
sendMessage(sessionId: string, message: string, context?: FileContext[]): AsyncIterable<ChatEvent>;
|
|
155
|
+
onToolApproval(sessionId: string, requestId: string, approved: boolean, data?: unknown): Promise<void>;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Implementations:**
|
|
160
|
+
- **claude-agent-sdk** (Primary) — @anthropic-ai/claude-agent-sdk, streaming, tool use
|
|
161
|
+
- **claude-code-cli** (Fallback) — Claude CLI subprocess, for offline environments
|
|
162
|
+
- **mock-provider** (Testing) — Returns canned responses
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
### Data Access Layer (Filesystem + Git)
|
|
167
|
+
**Components:** Direct filesystem access, simple-git wrapper
|
|
168
|
+
|
|
169
|
+
**Responsibilities:**
|
|
170
|
+
- Read/write project files with path validation
|
|
171
|
+
- Execute git commands via simple-git
|
|
172
|
+
- Cache directory listings
|
|
173
|
+
- Enforce security (no parent directory access)
|
|
174
|
+
|
|
175
|
+
**Key Patterns:**
|
|
176
|
+
- Path validation: `projectPath/relativePath` only, reject `..`
|
|
177
|
+
- Caching: Directory trees cached with TTL
|
|
178
|
+
- Error handling: Descriptive messages (file not found, permission denied)
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
### State Management (Frontend)
|
|
183
|
+
**Component:** Zustand stores in browser
|
|
184
|
+
|
|
185
|
+
**Stores:**
|
|
186
|
+
- **projectStore** — Active project, project list
|
|
187
|
+
- **tabStore** — Open tabs (chat, editor, git, terminal)
|
|
188
|
+
- **fileStore** — Selected files, editor content
|
|
189
|
+
- **settingsStore** — Auth token, theme preference
|
|
190
|
+
|
|
191
|
+
**Pattern:** Selectors for subscriptions (only re-render affected components)
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
const messages = chatStore((s) => s.messages); // Subscribe to messages only
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Communication Protocols
|
|
200
|
+
|
|
201
|
+
### REST API (Request/Response)
|
|
202
|
+
**Protocol:** HTTP/1.1 with JSON
|
|
203
|
+
|
|
204
|
+
**Pattern:**
|
|
205
|
+
1. Client sends request with auth token header
|
|
206
|
+
2. Server validates token (middleware)
|
|
207
|
+
3. Service processes request
|
|
208
|
+
4. Response formatted as `ApiResponse<T>` envelope
|
|
209
|
+
5. HTTP status set (200, 400, 404, 500)
|
|
210
|
+
|
|
211
|
+
**Example:**
|
|
212
|
+
```
|
|
213
|
+
POST /api/project/my-project/chat/sessions/abc/messages HTTP/1.1
|
|
214
|
+
Authorization: Bearer <token>
|
|
215
|
+
Content-Type: application/json
|
|
216
|
+
|
|
217
|
+
{ "content": "What does this code do?" }
|
|
218
|
+
|
|
219
|
+
HTTP/1.1 200 OK
|
|
220
|
+
{
|
|
221
|
+
"ok": true,
|
|
222
|
+
"data": {
|
|
223
|
+
"messageId": "msg-123",
|
|
224
|
+
"sessionId": "abc"
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
### WebSocket (Streaming)
|
|
232
|
+
**Protocol:** WebSocket over HTTP/1.1
|
|
233
|
+
|
|
234
|
+
**Chat Streaming Flow:**
|
|
235
|
+
1. Client connects: `WS /ws/project/:name/chat/:sessionId`
|
|
236
|
+
2. Client sends: `{ type: "message", content: "..." }`
|
|
237
|
+
3. Server streams messages:
|
|
238
|
+
- `{ type: "text", content: "..." }` (incremental)
|
|
239
|
+
- `{ type: "tool_use", tool: "file_read", input: {...} }`
|
|
240
|
+
- `{ type: "approval_request", requestId, tool, input }`
|
|
241
|
+
- `{ type: "done", sessionId }`
|
|
242
|
+
4. Client approves tool: `{ type: "approval_response", requestId, approved: true }`
|
|
243
|
+
|
|
244
|
+
**Terminal I/O Flow:**
|
|
245
|
+
1. Client connects: `WS /ws/project/:name/terminal/:id`
|
|
246
|
+
2. Client sends: `{ type: "input", data: "ls\n" }`
|
|
247
|
+
3. Server sends: `{ type: "output", data: "file1 file2\n" }`
|
|
248
|
+
4. Client sends: `{ type: "resize", cols: 80, rows: 24 }`
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Authentication Flow
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
User opens http://localhost:8080
|
|
256
|
+
↓
|
|
257
|
+
App checks localStorage for auth token
|
|
258
|
+
↓
|
|
259
|
+
If no token:
|
|
260
|
+
→ LoginScreen shown (prompt for token)
|
|
261
|
+
→ GET /api/auth/check to validate token
|
|
262
|
+
↓
|
|
263
|
+
If valid token:
|
|
264
|
+
→ Store in localStorage
|
|
265
|
+
→ Load projects: GET /api/projects
|
|
266
|
+
→ Main UI rendered
|
|
267
|
+
↓
|
|
268
|
+
For each API request:
|
|
269
|
+
→ Include "Authorization: Bearer <token>" header
|
|
270
|
+
→ Middleware validates token
|
|
271
|
+
→ If invalid → 401 Unauthorized
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Token Management:**
|
|
275
|
+
- Generated on `ppm init` → stored in `ppm.yaml`
|
|
276
|
+
- Sent from CLI via `-c <config>` flag
|
|
277
|
+
- Stored in browser localStorage for session persistence
|
|
278
|
+
- No expiry (single-user, local environment)
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Chat Streaming Flow
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
User types: "Debug this function"
|
|
286
|
+
↓
|
|
287
|
+
MessageInput.tsx calls useChat.sendMessage()
|
|
288
|
+
↓
|
|
289
|
+
useChat opens WebSocket: WS /ws/project/:name/chat/:sessionId
|
|
290
|
+
↓
|
|
291
|
+
Sends: { type: "message", content: "Debug..." }
|
|
292
|
+
↓
|
|
293
|
+
Server routes to ChatService.streamMessage()
|
|
294
|
+
↓
|
|
295
|
+
ChatService calls provider.sendMessage() (async generator)
|
|
296
|
+
↓
|
|
297
|
+
Provider (Claude SDK) streams response:
|
|
298
|
+
1. Yields: { type: "text", content: "Here's what..." }
|
|
299
|
+
2. Yields: { type: "text", content: " happens..." }
|
|
300
|
+
3. Yields: { type: "tool_use", tool: "read_file", input: {...} }
|
|
301
|
+
↓
|
|
302
|
+
ChatService wraps as WebSocket messages:
|
|
303
|
+
{ type: "text", content: "Here's what..." }
|
|
304
|
+
{ type: "text", content: " happens..." }
|
|
305
|
+
{ type: "tool_use", tool: "read_file", input: {...} }
|
|
306
|
+
{ type: "approval_request", requestId, tool, input }
|
|
307
|
+
↓
|
|
308
|
+
Client receives, displays message incrementally
|
|
309
|
+
↓
|
|
310
|
+
User sees tool approval prompt, clicks "Approve"
|
|
311
|
+
↓
|
|
312
|
+
Client sends: { type: "approval_response", requestId, approved: true }
|
|
313
|
+
↓
|
|
314
|
+
ChatService.onToolApproval() executes tool (file_read, git commands, etc.)
|
|
315
|
+
↓
|
|
316
|
+
Provider continues streaming with tool result
|
|
317
|
+
↓
|
|
318
|
+
Final response streamed, then: { type: "done", sessionId }
|
|
319
|
+
↓
|
|
320
|
+
useChat closes WebSocket, saves message to store
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Terminal Flow
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
User clicks Terminal tab
|
|
329
|
+
↓
|
|
330
|
+
TerminalTab.tsx mounts
|
|
331
|
+
↓
|
|
332
|
+
useTerminal hook opens WebSocket: WS /ws/project/:name/terminal/:id
|
|
333
|
+
↓
|
|
334
|
+
TerminalService.spawn() creates PTY (Bun.spawn)
|
|
335
|
+
↓
|
|
336
|
+
xterm.js renders terminal emulator
|
|
337
|
+
↓
|
|
338
|
+
User types: "npm test"
|
|
339
|
+
↓
|
|
340
|
+
xterm.js captures key event
|
|
341
|
+
↓
|
|
342
|
+
Sends via WebSocket: { type: "input", data: "npm test\n" }
|
|
343
|
+
↓
|
|
344
|
+
TerminalService.write(pty, "npm test\n")
|
|
345
|
+
↓
|
|
346
|
+
npm process spawned inside PTY
|
|
347
|
+
↓
|
|
348
|
+
Output captured: "PASS: all tests\n"
|
|
349
|
+
↓
|
|
350
|
+
TerminalService sends: { type: "output", data: "PASS: all tests\n" }
|
|
351
|
+
↓
|
|
352
|
+
xterm.js renders output
|
|
353
|
+
↓
|
|
354
|
+
User resizes window → xterm.js resizes terminal
|
|
355
|
+
↓
|
|
356
|
+
Sends: { type: "resize", cols: 120, rows: 40 }
|
|
357
|
+
↓
|
|
358
|
+
TerminalService calls pty.resize()
|
|
359
|
+
↓
|
|
360
|
+
Shell (bash/zsh) receives SIGWINCH signal
|
|
361
|
+
↓
|
|
362
|
+
Terminal state updated
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Git Integration Flow
|
|
368
|
+
|
|
369
|
+
```
|
|
370
|
+
User right-clicks file in FileTree
|
|
371
|
+
↓
|
|
372
|
+
Context menu shows "Stage" option
|
|
373
|
+
↓
|
|
374
|
+
User clicks "Stage"
|
|
375
|
+
↓
|
|
376
|
+
FileActions.tsx calls POST /api/project/:name/git/stage
|
|
377
|
+
↓
|
|
378
|
+
Sends: { path: "src/index.ts" }
|
|
379
|
+
↓
|
|
380
|
+
GitService.stage(projectPath, "src/index.ts")
|
|
381
|
+
↓
|
|
382
|
+
Executes: git add src/index.ts (via simple-git)
|
|
383
|
+
↓
|
|
384
|
+
Returns: { ok: true }
|
|
385
|
+
↓
|
|
386
|
+
GitStatusPanel.tsx refreshes: GET /api/project/:name/git/status
|
|
387
|
+
↓
|
|
388
|
+
GitService.status() returns:
|
|
389
|
+
{
|
|
390
|
+
current: "main",
|
|
391
|
+
staged: ["src/index.ts"],
|
|
392
|
+
unstaged: ["README.md"],
|
|
393
|
+
untracked: ["temp.log"]
|
|
394
|
+
}
|
|
395
|
+
↓
|
|
396
|
+
UI updates: "src/index.ts" moves from "Unstaged" to "Staged"
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Deployment Architecture
|
|
402
|
+
|
|
403
|
+
### Single-Machine Deployment (Current)
|
|
404
|
+
```
|
|
405
|
+
Linux/macOS Host
|
|
406
|
+
├── ppm (compiled binary)
|
|
407
|
+
│ └── Embeds: server code, frontend assets
|
|
408
|
+
├── ppm.yaml (config, auto-generated)
|
|
409
|
+
└── ~/.ppm/ (optional: session cache, logs)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Daemon Mode (Optional)
|
|
413
|
+
```
|
|
414
|
+
$ ppm start --daemon
|
|
415
|
+
→ Background process (nohup or systemd)
|
|
416
|
+
→ Logs to ~/.ppm/server.log
|
|
417
|
+
→ PID saved to ~/.ppm/server.pid
|
|
418
|
+
|
|
419
|
+
$ ppm stop
|
|
420
|
+
→ Reads PID
|
|
421
|
+
→ Sends SIGTERM
|
|
422
|
+
→ Graceful shutdown (close WS, cleanup PTY)
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Future: Multi-Machine (Not in v2)
|
|
426
|
+
Would require:
|
|
427
|
+
- Central state server (Redis/Postgres)
|
|
428
|
+
- Session sharing across servers
|
|
429
|
+
- Shared filesystem or file sync protocol
|
|
430
|
+
- Load balancer
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## Error Handling Strategy
|
|
435
|
+
|
|
436
|
+
| Layer | Error Type | Handling |
|
|
437
|
+
|-------|-----------|----------|
|
|
438
|
+
| **Presentation** | Network error | Retry, show toast |
|
|
439
|
+
| **API** | Invalid input | 400 Bad Request, error message |
|
|
440
|
+
| **Service** | File not found | Throw Error, API returns 404 |
|
|
441
|
+
| **Service** | Git failed | Throw Error with git output |
|
|
442
|
+
| **Provider** | Token invalid | Return error event |
|
|
443
|
+
| **Filesystem** | Permission denied | Throw Error with context |
|
|
444
|
+
|
|
445
|
+
**Pattern:** Bottom-up exception bubbling with context addition at each layer.
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## Security Architecture
|
|
450
|
+
|
|
451
|
+
| Component | Security Measure | Implementation |
|
|
452
|
+
|-----------|-----------------|-----------------|
|
|
453
|
+
| **Auth** | Token validation | Middleware checks header token vs config |
|
|
454
|
+
| **Path Traversal** | Path validation | FileService rejects paths with `..` |
|
|
455
|
+
| **WebSocket** | Token in URL query | WS connects with `?token=...` or via session |
|
|
456
|
+
| **CLI** | Config file permissions | 0600 (user read/write only) |
|
|
457
|
+
| **API** | No sensitive data in logs | Token masked in debug output |
|
|
458
|
+
| **CORS** | Same-origin only | WS on same host as HTTP API |
|
|
459
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hienlh/ppm",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Personal Project Manager — mobile-first web IDE with AI assistance",
|
|
5
|
+
"module": "src/index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ppm": "src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "bun run --hot src/index.ts start",
|
|
12
|
+
"dev:web": "bun run vite --config vite.config.ts",
|
|
13
|
+
"build:web": "bun run vite build --config vite.config.ts",
|
|
14
|
+
"build": "bun run build:web && bun build src/index.ts --compile --outfile dist/ppm",
|
|
15
|
+
"start": "bun run src/index.ts start",
|
|
16
|
+
"typecheck": "bunx tsc --noEmit",
|
|
17
|
+
"postinstall": "node node_modules/ccburn/scripts/postinstall.js"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@tailwindcss/vite": "^4.2.1",
|
|
21
|
+
"@types/bun": "latest",
|
|
22
|
+
"@types/js-yaml": "^4.0.9",
|
|
23
|
+
"@types/node": "^25.5.0",
|
|
24
|
+
"@types/react": "^19.2.14",
|
|
25
|
+
"@types/react-dom": "^19.2.3",
|
|
26
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
27
|
+
"tailwindcss": "^4.2.1",
|
|
28
|
+
"vite": "^8.0.0",
|
|
29
|
+
"vite-plugin-pwa": "^1.2.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"typescript": "^5.9.3"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.76",
|
|
36
|
+
"@codemirror/autocomplete": "^6.20.1",
|
|
37
|
+
"@codemirror/lang-css": "^6.3.1",
|
|
38
|
+
"@codemirror/lang-html": "^6.4.11",
|
|
39
|
+
"@codemirror/lang-javascript": "^6.2.5",
|
|
40
|
+
"@codemirror/lang-json": "^6.0.2",
|
|
41
|
+
"@codemirror/lang-markdown": "^6.5.0",
|
|
42
|
+
"@codemirror/lang-python": "^6.2.1",
|
|
43
|
+
"@codemirror/merge": "^6.12.1",
|
|
44
|
+
"@codemirror/theme-one-dark": "^6.1.3",
|
|
45
|
+
"@uiw/react-codemirror": "^4.25.8",
|
|
46
|
+
"@xterm/addon-fit": "^0.11.0",
|
|
47
|
+
"@xterm/addon-web-links": "^0.12.0",
|
|
48
|
+
"@xterm/xterm": "^6.0.0",
|
|
49
|
+
"ccburn": "^0.6.0",
|
|
50
|
+
"clsx": "^2.1.1",
|
|
51
|
+
"codemirror": "^6.0.2",
|
|
52
|
+
"commander": "^14.0.3",
|
|
53
|
+
"diff2html": "^3.4.56",
|
|
54
|
+
"hono": "^4.12.8",
|
|
55
|
+
"js-yaml": "^4.1.1",
|
|
56
|
+
"lucide-react": "^0.577.0",
|
|
57
|
+
"marked": "^17.0.4",
|
|
58
|
+
"next-themes": "^0.4.6",
|
|
59
|
+
"radix-ui": "^1.4.3",
|
|
60
|
+
"react": "^19.2.4",
|
|
61
|
+
"react-dom": "^19.2.4",
|
|
62
|
+
"react-resizable-panels": "^4.7.3",
|
|
63
|
+
"simple-git": "^3.33.0",
|
|
64
|
+
"sonner": "^2.0.7",
|
|
65
|
+
"tailwind-merge": "^3.5.0",
|
|
66
|
+
"zustand": "^5.0.11"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Phase 1: Project Skeleton + Shared Types
|
|
2
|
+
|
|
3
|
+
**Owner:** Lead
|
|
4
|
+
**Priority:** Critical
|
|
5
|
+
**Depends on:** None
|
|
6
|
+
**Effort:** Small
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Initialize monorepo, install dependencies, create shared types, config files. This unblocks all other phases.
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
1. **Init Bun project**
|
|
15
|
+
```bash
|
|
16
|
+
bun init
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
2. **Install backend dependencies**
|
|
20
|
+
```bash
|
|
21
|
+
bun add hono commander js-yaml simple-git @anthropic-ai/claude-agent-sdk
|
|
22
|
+
bun add -d @types/node typescript
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
3. **Install frontend dependencies**
|
|
26
|
+
```bash
|
|
27
|
+
bun add react react-dom zustand @tanstack/react-query
|
|
28
|
+
bun add -d vite @vitejs/plugin-react tailwindcss @tailwindcss/vite vite-plugin-pwa
|
|
29
|
+
bun add codemirror @codemirror/lang-javascript @codemirror/lang-typescript @codemirror/lang-python @codemirror/lang-html @codemirror/lang-css @codemirror/lang-json @codemirror/lang-markdown @codemirror/autocomplete @codemirror/merge @codemirror/theme-one-dark @uiw/react-codemirror
|
|
30
|
+
bun add @xterm/xterm @xterm/addon-fit @xterm/addon-web-links
|
|
31
|
+
bun add diff2html react-resizable-panels lucide-react
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
4. **Init shadcn/ui**
|
|
35
|
+
```bash
|
|
36
|
+
bunx --bun shadcn@latest init
|
|
37
|
+
bunx --bun shadcn@latest add button dialog dropdown-menu context-menu input tabs scroll-area tooltip separator sonner
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
5. **Create config files**
|
|
41
|
+
- `tsconfig.json` — strict, paths alias `@/` → `src/web/`
|
|
42
|
+
- `vite.config.ts` — React plugin, Tailwind, PWA plugin, build output to `dist/web`
|
|
43
|
+
- `tailwind.config.ts` — shadcn/ui preset, mobile-first
|
|
44
|
+
- `bunfig.toml` — Bun config
|
|
45
|
+
- `ppm.example.yaml` — example config
|
|
46
|
+
- `.env.example` — `ANTHROPIC_API_KEY=`
|
|
47
|
+
- `.gitignore`
|
|
48
|
+
|
|
49
|
+
6. **Create shared types** (`src/types/`)
|
|
50
|
+
- `config.ts` — PpmConfig, AuthConfig, ProjectConfig, AIProviderConfig
|
|
51
|
+
- `project.ts` — Project, ProjectInfo
|
|
52
|
+
- `git.ts` — GitCommit, GitBranch, GitStatus, GitGraphData, GitDiffResult, GitFileChange
|
|
53
|
+
- `chat.ts` — AIProvider interface, ChatEvent, SessionConfig, SessionInfo, ToolApprovalRequest
|
|
54
|
+
- `terminal.ts` — TerminalSession, TerminalResize
|
|
55
|
+
- `api.ts` — API request/response wrappers
|
|
56
|
+
|
|
57
|
+
7. **Create entry point** `src/index.ts` — minimal Commander.js setup with `start` command placeholder
|
|
58
|
+
|
|
59
|
+
8. **Create README.md** with project description, setup instructions
|
|
60
|
+
|
|
61
|
+
9. **Create CLAUDE.md** with project conventions
|
|
62
|
+
|
|
63
|
+
10. **Verify build**
|
|
64
|
+
```bash
|
|
65
|
+
bun run src/index.ts --help
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Files to Create
|
|
69
|
+
|
|
70
|
+
- `package.json`, `tsconfig.json`, `vite.config.ts`, `tailwind.config.ts`, `bunfig.toml`
|
|
71
|
+
- `ppm.example.yaml`, `.env.example`, `.gitignore`
|
|
72
|
+
- `src/index.ts`
|
|
73
|
+
- `src/types/*.ts` (6 files)
|
|
74
|
+
- `README.md`, `CLAUDE.md`, `LICENSE`
|
|
75
|
+
|
|
76
|
+
## Success Criteria
|
|
77
|
+
|
|
78
|
+
- [x] `bun run src/index.ts --help` shows CLI help
|
|
79
|
+
- [x] `bun run --hot src/web/main.tsx` (via vite) starts dev server
|
|
80
|
+
- [x] All shared types compile without errors
|
|
81
|
+
- [x] shadcn/ui components installed and importable
|