@hienlh/ppm 0.1.0 → 0.1.1
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/dist/web/assets/api-client-Bnf9LAt4.js +1 -0
- package/dist/web/assets/arrow-up-from-line-BXL5dtbG.js +1 -0
- package/dist/web/assets/button-BxijdhtM.js +1 -0
- package/dist/web/assets/chat-tab-BZopEuub.js +61 -0
- package/dist/web/assets/code-editor-hbllHzj7.js +2 -0
- package/dist/web/assets/createLucideIcon-Dy1wlrF7.js +1 -0
- package/dist/web/assets/dialog-RczsXsmw.js +45 -0
- package/dist/web/assets/diff-viewer-D6ixPlNB.js +4 -0
- package/dist/web/assets/dist-CSp7ir0r.js +46 -0
- package/dist/web/assets/external-link-WSiY-639.js +1 -0
- package/dist/web/assets/git-graph-DXMB_DoT.js +1 -0
- package/dist/web/assets/git-status-panel-D8ZUQrRF.js +1 -0
- package/dist/web/assets/index-DGSLw2GE.js +10 -0
- package/dist/web/assets/index-DYd_2slk.css +2 -0
- package/dist/web/assets/jsx-runtime-BnxRlLMJ.js +1 -0
- package/dist/web/assets/project-list-DWVXEimw.js +1 -0
- package/dist/web/assets/react-Uzd0zARU.js +1 -0
- package/dist/web/assets/refresh-cw-DtopuYJf.js +1 -0
- package/dist/web/assets/settings-tab-DJRzIAuP.js +1 -0
- package/dist/web/assets/terminal-tab-BrP-ENHg.css +1 -0
- package/dist/web/assets/terminal-tab-CbwaI-oq.js +36 -0
- package/dist/web/assets/trash-2-CHLebaNh.js +1 -0
- package/dist/web/assets/utils-Cgi2TYRi.js +1 -0
- package/dist/web/assets/x-BISR7bpK.js +1 -0
- package/dist/web/icon-192.svg +5 -0
- package/dist/web/icon-512.svg +5 -0
- package/dist/web/index.html +25 -0
- package/dist/web/manifest.webmanifest +1 -0
- package/dist/web/registerSW.js +1 -0
- package/dist/web/sw.js +1 -0
- package/dist/web/workbox-3e722498.js +1 -0
- package/package.json +2 -1
- package/.claude/agent-memory/tester/MEMORY.md +0 -3
- package/.claude/agent-memory/tester/project-ppm-test-conventions.md +0 -32
- package/.github/workflows/release.yml +0 -46
- package/plans/260314-2009-ppm-implementation/phase-01-project-skeleton.md +0 -81
- package/plans/260314-2009-ppm-implementation/phase-02-backend-core.md +0 -148
- package/plans/260314-2009-ppm-implementation/phase-03-frontend-shell.md +0 -256
- package/plans/260314-2009-ppm-implementation/phase-04-file-explorer-editor.md +0 -120
- package/plans/260314-2009-ppm-implementation/phase-05-web-terminal.md +0 -174
- package/plans/260314-2009-ppm-implementation/phase-06-git-integration.md +0 -244
- package/plans/260314-2009-ppm-implementation/phase-07-ai-chat.md +0 -242
- package/plans/260314-2009-ppm-implementation/phase-08-cli-commands.md +0 -143
- package/plans/260314-2009-ppm-implementation/phase-09-pwa-build-deploy.md +0 -209
- package/plans/260314-2009-ppm-implementation/phase-10-testing.md +0 -311
- package/plans/260314-2009-ppm-implementation/plan.md +0 -202
- package/plans/260315-0356-project-scoped-api-refactor/phase-01-backend-project-router.md +0 -145
- package/plans/260315-0356-project-scoped-api-refactor/phase-02-frontend-api-migration.md +0 -107
- package/plans/260315-0356-project-scoped-api-refactor/phase-03-per-project-tabs.md +0 -100
- package/plans/260315-0356-project-scoped-api-refactor/phase-04-websocket-migration.md +0 -66
- package/plans/260315-0356-project-scoped-api-refactor/plan.md +0 -87
- package/plans/reports/brainstorm-260314-1938-final-techstack.md +0 -342
- package/plans/reports/docs-manager-260315-1314-documentation-creation.md +0 -386
- package/plans/reports/fullstack-developer-260314-2252-phase-02-backend-core.md +0 -57
- package/plans/reports/fullstack-developer-260314-2253-phase-03-frontend-shell.md +0 -70
- package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-api-terminal-ws.md +0 -49
- package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-explorer-editor-terminal.md +0 -52
- package/plans/reports/fullstack-developer-260314-2307-ai-chat-phase7.md +0 -58
- package/plans/reports/fullstack-developer-260314-2307-phase-06-git-integration.md +0 -33
- package/plans/reports/research-260314-1911-ppm-tech-stack.md +0 -318
- package/plans/reports/research-260314-1930-claude-code-integration.md +0 -293
- package/plans/reports/researcher-260314-2232-node-pty-bun-crash-analysis.md +0 -305
- package/plans/reports/researcher-260314-2232-ui-style.md +0 -942
- package/plans/reports/researcher-260315-0300-opcode-claude-interaction.md +0 -745
- package/plans/reports/researcher-260315-0303-opcode-deep-analysis.md +0 -742
- package/plans/reports/researcher-260315-0305-claude-agent-sdk-github-research.md +0 -423
- package/plans/reports/tester-260314-2053-initial-test-suite.md +0 -81
- package/repomix-output.xml +0 -23745
- package/tests/integration/api/chat-routes.test.ts +0 -95
- package/tests/integration/claude-agent-sdk-integration.test.ts +0 -228
- package/tests/integration/ws/chat-websocket.test.ts +0 -312
- package/tests/test-setup.ts +0 -5
- package/tests/unit/providers/claude-agent-sdk.test.ts +0 -339
- package/tests/unit/providers/mock-provider.test.ts +0 -143
- package/tests/unit/services/chat-service.test.ts +0 -100
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
# Phase 6: Git Integration
|
|
2
|
-
|
|
3
|
-
**Owner:** backend-dev (API) + frontend-dev (UI) — parallel
|
|
4
|
-
**Priority:** High
|
|
5
|
-
**Depends on:** Phase 4 (file explorer, diff viewer reuse)
|
|
6
|
-
**Effort:** Large (git graph is complex)
|
|
7
|
-
|
|
8
|
-
## Overview
|
|
9
|
-
|
|
10
|
-
Git status panel (stage/unstage/commit/push/pull), git diff viewer, git graph visualization (SVG, ported from vscode-git-graph approach).
|
|
11
|
-
|
|
12
|
-
## Backend (backend-dev)
|
|
13
|
-
|
|
14
|
-
### Files
|
|
15
|
-
```
|
|
16
|
-
src/services/git.service.ts
|
|
17
|
-
src/server/routes/git.ts
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
### Git Service
|
|
21
|
-
```typescript
|
|
22
|
-
import simpleGit from 'simple-git';
|
|
23
|
-
|
|
24
|
-
class GitService {
|
|
25
|
-
// Status
|
|
26
|
-
async status(projectPath: string): Promise<GitStatus>
|
|
27
|
-
async diff(projectPath: string, ref1?: string, ref2?: string): Promise<string> // unified diff
|
|
28
|
-
async fileDiff(projectPath: string, filePath: string): Promise<string>
|
|
29
|
-
|
|
30
|
-
// Staging
|
|
31
|
-
async stage(projectPath: string, files: string[]): Promise<void>
|
|
32
|
-
async unstage(projectPath: string, files: string[]): Promise<void>
|
|
33
|
-
|
|
34
|
-
// Commit + Push/Pull
|
|
35
|
-
async commit(projectPath: string, message: string): Promise<string> // returns hash
|
|
36
|
-
async push(projectPath: string, remote?: string, branch?: string): Promise<void>
|
|
37
|
-
async pull(projectPath: string, remote?: string, branch?: string): Promise<void>
|
|
38
|
-
|
|
39
|
-
// Branch ops
|
|
40
|
-
async branches(projectPath: string): Promise<GitBranch[]>
|
|
41
|
-
async createBranch(projectPath: string, name: string, from?: string): Promise<void>
|
|
42
|
-
async checkout(projectPath: string, ref: string): Promise<void>
|
|
43
|
-
async deleteBranch(projectPath: string, name: string, force?: boolean): Promise<void>
|
|
44
|
-
async merge(projectPath: string, source: string): Promise<void>
|
|
45
|
-
|
|
46
|
-
// Graph data
|
|
47
|
-
async graphData(projectPath: string, maxCount?: number): Promise<GitGraphData>
|
|
48
|
-
|
|
49
|
-
// Advanced (web-only, no CLI needed)
|
|
50
|
-
async cherryPick(projectPath: string, hash: string): Promise<void>
|
|
51
|
-
async revert(projectPath: string, hash: string): Promise<void>
|
|
52
|
-
async createTag(projectPath: string, name: string, hash?: string): Promise<void>
|
|
53
|
-
|
|
54
|
-
// PR URL
|
|
55
|
-
getCreatePrUrl(projectPath: string, branch: string): string | null
|
|
56
|
-
// Parse remote URL → GitHub/GitLab PR creation URL
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Graph Data Extraction
|
|
61
|
-
|
|
62
|
-
**[V2 FIX]** Do NOT parse `git log --format` manually with newline separators. Use simple-git's built-in `.log()` which returns correctly typed `LogResult`:
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
async graphData(projectPath: string, maxCount = 200): Promise<GitGraphData> {
|
|
66
|
-
const git = simpleGit(projectPath);
|
|
67
|
-
|
|
68
|
-
// Use simple-git's built-in log() — handles parsing correctly
|
|
69
|
-
const log = await git.log({
|
|
70
|
-
'--all': null,
|
|
71
|
-
maxCount,
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// log.all is already typed: { hash, date, message, author_name, author_email, refs, body, diff? }[]
|
|
75
|
-
const commits: GitCommit[] = log.all.map(c => ({
|
|
76
|
-
hash: c.hash,
|
|
77
|
-
abbreviatedHash: c.hash.slice(0, 7),
|
|
78
|
-
subject: c.message,
|
|
79
|
-
body: c.body,
|
|
80
|
-
authorName: c.author_name,
|
|
81
|
-
authorEmail: c.author_email,
|
|
82
|
-
authorDate: c.date,
|
|
83
|
-
parents: [], // Need separate call or parse refs
|
|
84
|
-
refs: c.refs ? c.refs.split(', ').filter(Boolean) : [],
|
|
85
|
-
}));
|
|
86
|
-
|
|
87
|
-
// Get parent hashes via raw format (safe: one field per line)
|
|
88
|
-
const parentLog = await git.raw([
|
|
89
|
-
'log', '--all', `--max-count=${maxCount}`,
|
|
90
|
-
'--format=%H %P' // hash + space-separated parents on ONE line
|
|
91
|
-
]);
|
|
92
|
-
const parentMap = new Map<string, string[]>();
|
|
93
|
-
for (const line of parentLog.trim().split('\n')) {
|
|
94
|
-
const [hash, ...parents] = line.split(' ');
|
|
95
|
-
if (hash) parentMap.set(hash, parents.filter(Boolean));
|
|
96
|
-
}
|
|
97
|
-
for (const c of commits) {
|
|
98
|
-
c.parents = parentMap.get(c.hash) ?? [];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const branchSummary = await git.branch(['-a', '--no-color']);
|
|
102
|
-
const branches: GitBranch[] = Object.entries(branchSummary.branches).map(([name, info]) => ({
|
|
103
|
-
name,
|
|
104
|
-
current: info.current,
|
|
105
|
-
remote: name.startsWith('remotes/'),
|
|
106
|
-
commitHash: info.commit,
|
|
107
|
-
ahead: 0,
|
|
108
|
-
behind: 0,
|
|
109
|
-
}));
|
|
110
|
-
|
|
111
|
-
return { commits, branches };
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Lane Allocation Algorithm
|
|
116
|
-
Port from vscode-git-graph `web/graph.ts`:
|
|
117
|
-
- Each branch gets a lane (column index)
|
|
118
|
-
- Merge/fork lines connect lanes
|
|
119
|
-
- Color = `laneIndex % colorPalette.length`
|
|
120
|
-
- Return: `Map<commitHash, { lane: number, lines: Line[] }>`
|
|
121
|
-
|
|
122
|
-
### API Routes
|
|
123
|
-
```
|
|
124
|
-
GET /api/git/status/:project
|
|
125
|
-
GET /api/git/diff/:project?ref1=&ref2=
|
|
126
|
-
GET /api/git/file-diff/:project?file=&ref=
|
|
127
|
-
GET /api/git/graph/:project?max=500
|
|
128
|
-
GET /api/git/branches/:project
|
|
129
|
-
POST /api/git/stage { project, files }
|
|
130
|
-
POST /api/git/unstage { project, files }
|
|
131
|
-
POST /api/git/commit { project, message }
|
|
132
|
-
POST /api/git/push { project, remote?, branch? }
|
|
133
|
-
POST /api/git/pull { project, remote?, branch? }
|
|
134
|
-
POST /api/git/branch/create { project, name, from? }
|
|
135
|
-
POST /api/git/checkout { project, ref }
|
|
136
|
-
POST /api/git/branch/delete { project, name, force? }
|
|
137
|
-
POST /api/git/merge { project, source }
|
|
138
|
-
POST /api/git/cherry-pick { project, hash }
|
|
139
|
-
POST /api/git/revert { project, hash }
|
|
140
|
-
POST /api/git/tag { project, name, hash? }
|
|
141
|
-
GET /api/git/pr-url/:project?branch= → { url }
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## Frontend (frontend-dev)
|
|
145
|
-
|
|
146
|
-
### Files
|
|
147
|
-
```
|
|
148
|
-
src/web/components/git/git-graph.tsx
|
|
149
|
-
src/web/components/git/git-graph-renderer.tsx
|
|
150
|
-
src/web/components/git/git-status-panel.tsx
|
|
151
|
-
src/web/components/git/git-diff-tab.tsx
|
|
152
|
-
src/web/components/git/commit-context-menu.tsx
|
|
153
|
-
src/web/lib/git-graph-layout.ts
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### Git Status Panel
|
|
157
|
-
- Split into: Changes (unstaged) + Staged Changes
|
|
158
|
-
- Each file: icon (M/A/D/R), filename, click → open diff
|
|
159
|
-
- Buttons: Stage All, Unstage All
|
|
160
|
-
- Individual file: click +/- to stage/unstage
|
|
161
|
-
- Commit section: textarea input + "Commit" button
|
|
162
|
-
- Push/Pull buttons with branch name display
|
|
163
|
-
- Auto-refresh via WS `/ws/events` or polling
|
|
164
|
-
|
|
165
|
-
### Git Graph (SVG)
|
|
166
|
-
```typescript
|
|
167
|
-
// git-graph-renderer.tsx
|
|
168
|
-
// Receives: commits + lanes from backend API
|
|
169
|
-
|
|
170
|
-
const GitGraphRenderer = ({ data }: { data: GitGraphData }) => {
|
|
171
|
-
// SVG element with:
|
|
172
|
-
// 1. Branch lines: <path> elements, curved or angular
|
|
173
|
-
// 2. Commit nodes: <circle> elements
|
|
174
|
-
// 3. Labels: branch names, tags as badges
|
|
175
|
-
// 4. Click handlers on commits → expand details
|
|
176
|
-
// 5. Context menu on commits and branches
|
|
177
|
-
|
|
178
|
-
// Virtualization: only render visible rows
|
|
179
|
-
// Each commit row height = GRID_Y (e.g., 24px)
|
|
180
|
-
// Scroll container with virtual list
|
|
181
|
-
};
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### Git Graph Context Menu (shadcn/ui ContextMenu)
|
|
185
|
-
**On commit node:**
|
|
186
|
-
- Checkout this commit
|
|
187
|
-
- Create branch here...
|
|
188
|
-
- Cherry pick
|
|
189
|
-
- Revert
|
|
190
|
-
- Create tag...
|
|
191
|
-
- Copy commit hash
|
|
192
|
-
- View diff
|
|
193
|
-
|
|
194
|
-
**On branch label:**
|
|
195
|
-
- Checkout
|
|
196
|
-
- Merge into current branch
|
|
197
|
-
- Delete branch
|
|
198
|
-
- Rename branch
|
|
199
|
-
- Push
|
|
200
|
-
- Pull
|
|
201
|
-
- Rebase onto current
|
|
202
|
-
- Create Pull Request → opens browser URL
|
|
203
|
-
|
|
204
|
-
### Git Diff Tab
|
|
205
|
-
- Reuse `@codemirror/merge` diff viewer from Phase 4
|
|
206
|
-
- Opened from: git status (click file), git graph (view diff), file explorer (compare)
|
|
207
|
-
- Header: file path, ref1 vs ref2
|
|
208
|
-
|
|
209
|
-
## Success Criteria
|
|
210
|
-
|
|
211
|
-
**Git Status Panel:**
|
|
212
|
-
- [ ] Shows two sections: "Changes" (unstaged) and "Staged Changes" with file counts
|
|
213
|
-
- [ ] Each file shows status icon (M=modified, A=added, D=deleted, R=renamed) + filename
|
|
214
|
-
- [ ] Click file in either section → opens diff view in new tab
|
|
215
|
-
- [ ] Click "+" on unstaged file → stages it (moves to Staged section)
|
|
216
|
-
- [ ] Click "−" on staged file → unstages it (moves to Changes section)
|
|
217
|
-
- [ ] "Stage All" button stages all changed files
|
|
218
|
-
- [ ] "Unstage All" button unstages all staged files
|
|
219
|
-
- [ ] Commit section: textarea for message + "Commit" button
|
|
220
|
-
- [ ] Commit with empty message → button disabled / shows warning
|
|
221
|
-
- [ ] Commit with nothing staged → shows "Nothing to commit" message
|
|
222
|
-
- [ ] After successful commit → status panel refreshes, staged files clear
|
|
223
|
-
- [ ] Push/Pull buttons show current branch name, disabled when no remote
|
|
224
|
-
|
|
225
|
-
**Git Graph:**
|
|
226
|
-
- [ ] Renders commit history as SVG with colored branch lanes
|
|
227
|
-
- [ ] Each commit: circle node + abbreviated hash + subject + author + relative date
|
|
228
|
-
- [ ] Branch labels rendered as colored badges on corresponding commits
|
|
229
|
-
- [ ] Merge commits show lines connecting from parent lanes
|
|
230
|
-
- [ ] Scroll through 200+ commits without performance issues (virtualized rendering)
|
|
231
|
-
- [ ] Right-click commit → context menu: Checkout, Create branch, Cherry pick, Revert, Create tag, Copy hash, View diff
|
|
232
|
-
- [ ] Right-click branch label → context menu: Checkout, Merge into current, Delete, Push, Create PR
|
|
233
|
-
- [ ] "Create PR" → opens correct GitHub/GitLab URL (parsed from remote) in new browser tab
|
|
234
|
-
|
|
235
|
-
**Git Diff:**
|
|
236
|
-
- [ ] Diff tab shows file path + refs in header
|
|
237
|
-
- [ ] Side-by-side diff using `@codemirror/merge` with syntax highlighting
|
|
238
|
-
- [ ] Added lines highlighted green, removed lines highlighted red
|
|
239
|
-
- [ ] Opened from: git status (click file), git graph (view diff), file explorer (compare)
|
|
240
|
-
|
|
241
|
-
**Mobile:**
|
|
242
|
-
- [ ] Git graph scrollable horizontally and vertically with touch
|
|
243
|
-
- [ ] Context menu appears on long-press (commit node or branch label)
|
|
244
|
-
- [ ] Status panel: swipe file row to stage/unstage (nice-to-have, buttons work as fallback)
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
# Phase 7: AI Chat
|
|
2
|
-
|
|
3
|
-
**Owner:** backend-dev (provider + WS) + frontend-dev (chat UI) — parallel
|
|
4
|
-
**Priority:** High
|
|
5
|
-
**Depends on:** Phase 2, Phase 3
|
|
6
|
-
**Effort:** Large
|
|
7
|
-
|
|
8
|
-
## Overview
|
|
9
|
-
|
|
10
|
-
AI chat with Claude Agent SDK as first provider. Generic AIProvider interface for multi-provider support. Chat UI with streaming, tool approvals, session management.
|
|
11
|
-
|
|
12
|
-
## Backend (backend-dev)
|
|
13
|
-
|
|
14
|
-
### Files
|
|
15
|
-
```
|
|
16
|
-
src/providers/provider.interface.ts # Already in types, implement here
|
|
17
|
-
src/providers/claude-agent-sdk.ts
|
|
18
|
-
src/providers/cli-subprocess.ts # Stub for future
|
|
19
|
-
src/providers/registry.ts
|
|
20
|
-
src/services/chat.service.ts
|
|
21
|
-
src/server/ws/chat.ts
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### Claude Agent SDK Provider
|
|
25
|
-
```typescript
|
|
26
|
-
import { query, listSessions, getSessionMessages } from '@anthropic-ai/claude-agent-sdk';
|
|
27
|
-
|
|
28
|
-
class ClaudeAgentSdkProvider implements AIProvider {
|
|
29
|
-
id = 'claude';
|
|
30
|
-
name = 'Claude Code';
|
|
31
|
-
|
|
32
|
-
async createSession(config: SessionConfig): Promise<Session> {
|
|
33
|
-
// Start a new query() — capture session_id from init message
|
|
34
|
-
// Return session handle
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async resumeSession(sessionId: string): Promise<Session> {
|
|
38
|
-
// Use { resume: sessionId } option
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async *sendMessage(sessionId: string, message: string): AsyncIterable<ChatEvent> {
|
|
42
|
-
const options = sessionId
|
|
43
|
-
? { resume: sessionId, allowedTools: [...] }
|
|
44
|
-
: { allowedTools: [...] };
|
|
45
|
-
|
|
46
|
-
for await (const msg of query({ prompt: message, options })) {
|
|
47
|
-
// Map SDK messages → ChatEvent types
|
|
48
|
-
if (msg.type === 'assistant') {
|
|
49
|
-
for (const block of msg.content) {
|
|
50
|
-
if (block.type === 'text') yield { type: 'text', content: block.text };
|
|
51
|
-
if (block.type === 'tool_use') yield { type: 'tool_use', tool: block.name, input: block.input };
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (msg.type === 'result') {
|
|
55
|
-
yield { type: 'done', sessionId: msg.session_id };
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async listSessions(): Promise<SessionInfo[]> {
|
|
61
|
-
return await listSessions();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async deleteSession(sessionId: string): Promise<void> {
|
|
65
|
-
// Delete session file from ~/.claude/projects/...
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Tool Approval Flow
|
|
71
|
-
```typescript
|
|
72
|
-
// In claude-agent-sdk.ts
|
|
73
|
-
// canUseTool callback → forward to frontend via WS
|
|
74
|
-
|
|
75
|
-
const options = {
|
|
76
|
-
canUseTool: async (toolName: string, input: any) => {
|
|
77
|
-
// Send approval request to frontend via WS
|
|
78
|
-
const response = await this.requestApproval(sessionId, toolName, input);
|
|
79
|
-
if (response.approved) {
|
|
80
|
-
return { behavior: 'allow', updatedInput: input };
|
|
81
|
-
}
|
|
82
|
-
return { behavior: 'deny', message: response.reason || 'User denied' };
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Provider Registry
|
|
88
|
-
```typescript
|
|
89
|
-
class ProviderRegistry {
|
|
90
|
-
private providers: Map<string, AIProvider> = new Map();
|
|
91
|
-
|
|
92
|
-
register(provider: AIProvider): void
|
|
93
|
-
get(id: string): AIProvider | undefined
|
|
94
|
-
list(): AIProviderInfo[]
|
|
95
|
-
getDefault(): AIProvider
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Chat Service
|
|
100
|
-
```typescript
|
|
101
|
-
class ChatService {
|
|
102
|
-
constructor(private registry: ProviderRegistry)
|
|
103
|
-
|
|
104
|
-
async createSession(providerId: string, config: SessionConfig): Promise<Session>
|
|
105
|
-
async resumeSession(providerId: string, sessionId: string): Promise<Session>
|
|
106
|
-
async listSessions(providerId?: string): Promise<SessionInfo[]>
|
|
107
|
-
async deleteSession(providerId: string, sessionId: string): Promise<void>
|
|
108
|
-
sendMessage(providerId: string, sessionId: string, message: string): AsyncIterable<ChatEvent>
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### WebSocket Handler
|
|
113
|
-
```
|
|
114
|
-
WS /ws/chat/:sessionId
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
Protocol (JSON messages):
|
|
118
|
-
```typescript
|
|
119
|
-
// Client → Server
|
|
120
|
-
{ type: 'message', content: string }
|
|
121
|
-
{ type: 'approval_response', requestId: string, approved: boolean, reason?: string }
|
|
122
|
-
|
|
123
|
-
// Server → Client
|
|
124
|
-
{ type: 'text', content: string }
|
|
125
|
-
{ type: 'tool_use', tool: string, input: any }
|
|
126
|
-
{ type: 'tool_result', output: string }
|
|
127
|
-
{ type: 'approval_request', requestId: string, tool: string, input: any }
|
|
128
|
-
{ type: 'done', sessionId: string }
|
|
129
|
-
{ type: 'error', message: string }
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## Frontend (frontend-dev)
|
|
133
|
-
|
|
134
|
-
### Files
|
|
135
|
-
```
|
|
136
|
-
src/web/components/chat/chat-tab.tsx
|
|
137
|
-
src/web/components/chat/message-list.tsx
|
|
138
|
-
src/web/components/chat/message-input.tsx
|
|
139
|
-
src/web/components/chat/tool-approval.tsx
|
|
140
|
-
src/web/components/chat/session-picker.tsx
|
|
141
|
-
src/web/hooks/use-chat.ts
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Chat Tab Layout
|
|
145
|
-
```
|
|
146
|
-
┌─────────────────────────────────┐
|
|
147
|
-
│ Claude Code ▼ [Session: abc] │ ← provider picker + session info
|
|
148
|
-
├─────────────────────────────────┤
|
|
149
|
-
│ │
|
|
150
|
-
│ User: Fix the bug in auth.ts │
|
|
151
|
-
│ │
|
|
152
|
-
│ Claude: I'll read the file... │
|
|
153
|
-
│ 📄 Read auth.ts │
|
|
154
|
-
│ ✅ Tool result: (content) │
|
|
155
|
-
│ │
|
|
156
|
-
│ ⚠️ Bash: rm -rf /tmp/test │
|
|
157
|
-
│ [Allow] [Deny] │ ← tool approval dialog
|
|
158
|
-
│ │
|
|
159
|
-
├─────────────────────────────────┤
|
|
160
|
-
│ 📎 Type a message... [Send] │ ← input with file attach
|
|
161
|
-
└─────────────────────────────────┘
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
### Message Types Rendering
|
|
165
|
-
- **User message:** Right-aligned bubble (or left with avatar)
|
|
166
|
-
- **Assistant text:** Markdown rendered (code blocks with syntax highlight)
|
|
167
|
-
- **Tool use:** Collapsible card showing tool name + input
|
|
168
|
-
- **Tool result:** Collapsible card showing output (truncated)
|
|
169
|
-
- **Approval request:** Highlighted card with Allow/Deny buttons + tool details
|
|
170
|
-
- **Error:** Red alert banner
|
|
171
|
-
|
|
172
|
-
### Session Picker
|
|
173
|
-
- Dropdown or modal listing all sessions
|
|
174
|
-
- Each session: provider icon, title (first message truncated), timestamp
|
|
175
|
-
- Actions: Resume, Delete, Fork (nice-to-have)
|
|
176
|
-
- "New Chat" button → create fresh session
|
|
177
|
-
|
|
178
|
-
### useChat Hook
|
|
179
|
-
```typescript
|
|
180
|
-
const useChat = (sessionId?: string) => {
|
|
181
|
-
const ws = useWebSocket(`/ws/chat/${sessionId}`);
|
|
182
|
-
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
|
183
|
-
const [isStreaming, setIsStreaming] = useState(false);
|
|
184
|
-
const [pendingApproval, setPendingApproval] = useState<ApprovalRequest | null>(null);
|
|
185
|
-
|
|
186
|
-
const sendMessage = (content: string) => {
|
|
187
|
-
ws.send(JSON.stringify({ type: 'message', content }));
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const respondToApproval = (requestId: string, approved: boolean) => {
|
|
191
|
-
ws.send(JSON.stringify({ type: 'approval_response', requestId, approved }));
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
return { messages, isStreaming, pendingApproval, sendMessage, respondToApproval };
|
|
195
|
-
};
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
### Mobile Considerations
|
|
199
|
-
- Chat input: sticky bottom, auto-resize textarea
|
|
200
|
-
- Long messages: scrollable code blocks
|
|
201
|
-
- Tool approval: full-width card, large touch targets for Allow/Deny
|
|
202
|
-
- Keyboard: input should push content up, not cover it
|
|
203
|
-
|
|
204
|
-
## Chat History & Reconnect
|
|
205
|
-
|
|
206
|
-
### REST API for Message History
|
|
207
|
-
- `GET /api/chat/sessions` → list all sessions (id, provider, title, createdAt)
|
|
208
|
-
- `GET /api/chat/sessions/:id/messages` → full message history for a session
|
|
209
|
-
- On WS reconnect: client loads history via REST, then subscribes to live stream
|
|
210
|
-
- This avoids WS replay complexity — REST is simpler and more reliable
|
|
211
|
-
|
|
212
|
-
### Reconnect Flow
|
|
213
|
-
```
|
|
214
|
-
Client disconnects
|
|
215
|
-
→ WS closes
|
|
216
|
-
→ Client: exponential backoff reconnect
|
|
217
|
-
→ Client reconnects to same sessionId
|
|
218
|
-
→ Client: GET /api/chat/sessions/:id/messages → render history
|
|
219
|
-
→ Server: resume streaming from where it left off (if AI still generating)
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### Session Persistence
|
|
223
|
-
- Claude Agent SDK sessions persist automatically (stored in ~/.claude/)
|
|
224
|
-
- PPM stores session metadata in `~/.ppm/chat-sessions.json`: id, provider, title, projectName, createdAt
|
|
225
|
-
- Session list fetched from PPM metadata file (fast), not from SDK (slow)
|
|
226
|
-
|
|
227
|
-
## Success Criteria
|
|
228
|
-
|
|
229
|
-
- [ ] Can create new chat session → WS connects, session appears in session list
|
|
230
|
-
- [ ] Sending message streams AI response text in real-time (character by character)
|
|
231
|
-
- [ ] Tool use blocks render with tool name + collapsible input JSON
|
|
232
|
-
- [ ] Tool result blocks render with collapsible output
|
|
233
|
-
- [ ] Tool approval dialog: shows tool name + input, Allow/Deny buttons work, response sent via WS
|
|
234
|
-
- [ ] After Allow → tool executes and result streams back; after Deny → AI acknowledges denial
|
|
235
|
-
- [ ] Can resume existing session: select from picker → loads history via REST → WS reconnects
|
|
236
|
-
- [ ] Session list shows all sessions with provider icon, truncated title, timestamp
|
|
237
|
-
- [ ] "New Chat" button creates fresh session with current project context
|
|
238
|
-
- [ ] Multiple chat tabs work simultaneously (each with own WS connection)
|
|
239
|
-
- [ ] WS disconnect → reconnect → message history loaded from REST API
|
|
240
|
-
- [ ] Works on mobile: sticky bottom input, keyboard pushes content up, large Allow/Deny buttons
|
|
241
|
-
- [ ] Markdown rendering in AI messages: code blocks with syntax highlighting, lists, headers
|
|
242
|
-
- [ ] Error messages from AI/server shown as red alert banner (not silent failure)
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
# Phase 8: CLI Commands
|
|
2
|
-
|
|
3
|
-
**Owner:** backend-dev
|
|
4
|
-
**Priority:** Medium
|
|
5
|
-
**Depends on:** Phase 2, Phase 6, Phase 7
|
|
6
|
-
**Effort:** Medium
|
|
7
|
-
|
|
8
|
-
## Overview
|
|
9
|
-
|
|
10
|
-
Implement remaining CLI commands that call Service Layer directly. All commands share project resolution logic (CWD auto-detect + `-p` flag).
|
|
11
|
-
|
|
12
|
-
## Files
|
|
13
|
-
```
|
|
14
|
-
src/cli/commands/projects.ts
|
|
15
|
-
src/cli/commands/config.ts
|
|
16
|
-
src/cli/commands/git.ts
|
|
17
|
-
src/cli/commands/chat.ts
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Commands
|
|
21
|
-
|
|
22
|
-
### ppm projects
|
|
23
|
-
```bash
|
|
24
|
-
ppm projects list # Table: name, path, branch, status
|
|
25
|
-
ppm projects add <path> [--name <n>] # Add project to config
|
|
26
|
-
ppm projects remove <name-or-path> # Remove from config
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### ppm config
|
|
30
|
-
```bash
|
|
31
|
-
ppm config get <key> # e.g., ppm config get port
|
|
32
|
-
ppm config set <key> <value> # e.g., ppm config set port 9090
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### ppm git
|
|
36
|
-
All git commands accept `-p <project>` flag. Default: CWD auto-detect.
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
ppm git status [-p proj] # Show status (like git status --short)
|
|
40
|
-
ppm git log [-p proj] [-n 20] # Show recent commits
|
|
41
|
-
ppm git diff [-p proj] [ref1] [ref2] # Show diff
|
|
42
|
-
ppm git stage [-p proj] <files...> # Stage files (or "." for all)
|
|
43
|
-
ppm git unstage [-p proj] <files...> # Unstage files
|
|
44
|
-
ppm git commit [-p proj] -m "msg" # Commit staged changes
|
|
45
|
-
ppm git push [-p proj] # Push to remote
|
|
46
|
-
ppm git pull [-p proj] # Pull from remote
|
|
47
|
-
ppm git branch create [-p proj] <name> [--from <ref>]
|
|
48
|
-
ppm git branch checkout [-p proj] <name>
|
|
49
|
-
ppm git branch delete [-p proj] <name> [--force]
|
|
50
|
-
ppm git branch merge [-p proj] <source>
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### ppm chat
|
|
54
|
-
```bash
|
|
55
|
-
ppm chat list [-p proj] # List sessions (table: id, provider, title, date)
|
|
56
|
-
ppm chat create [-p proj] [--provider claude] # Create session, print session ID
|
|
57
|
-
ppm chat send [-p proj] <session-id> "message" # Send message, stream response to stdout
|
|
58
|
-
ppm chat resume [-p proj] <session-id> # Interactive mode (stdin/stdout)
|
|
59
|
-
ppm chat delete [-p proj] <session-id> # Delete session
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
`ppm chat send` streams response to stdout as it arrives. Useful for AI-to-AI orchestration:
|
|
63
|
-
```bash
|
|
64
|
-
# AI agent sends a task to PPM chat
|
|
65
|
-
RESPONSE=$(ppm chat send -p myapp abc123 "Fix the bug in auth.ts")
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
`ppm chat resume` enters interactive mode:
|
|
69
|
-
```
|
|
70
|
-
You: Fix the auth bug
|
|
71
|
-
Claude: I'll read the file...
|
|
72
|
-
[Tool: Read auth.ts] Allow? (y/n): y
|
|
73
|
-
Claude: Found the issue...
|
|
74
|
-
You:
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Implementation Pattern
|
|
78
|
-
|
|
79
|
-
All commands follow same pattern:
|
|
80
|
-
```typescript
|
|
81
|
-
// commands/git.ts
|
|
82
|
-
import { Command } from 'commander';
|
|
83
|
-
|
|
84
|
-
export function registerGitCommands(program: Command) {
|
|
85
|
-
const git = program.command('git').description('Git operations');
|
|
86
|
-
|
|
87
|
-
git.command('status')
|
|
88
|
-
.option('-p, --project <name>', 'Project name')
|
|
89
|
-
.action(async (options) => {
|
|
90
|
-
const project = resolveProject(options);
|
|
91
|
-
const gitService = new GitService();
|
|
92
|
-
const status = await gitService.status(project.path);
|
|
93
|
-
// Pretty print to terminal
|
|
94
|
-
printGitStatus(status);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// ... more subcommands
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Output Formatting
|
|
102
|
-
|
|
103
|
-
- Use colors (via `chalk` or Bun built-in ANSI) for terminal output
|
|
104
|
-
- Tables for list commands (projects list, chat list)
|
|
105
|
-
- Git status: colored M/A/D indicators like git
|
|
106
|
-
- Streaming output for chat send
|
|
107
|
-
|
|
108
|
-
## Success Criteria
|
|
109
|
-
|
|
110
|
-
**Project Resolution:**
|
|
111
|
-
- [ ] `-p myproject` flag resolves project by name
|
|
112
|
-
- [ ] No `-p` flag + CWD inside registered project → auto-detects
|
|
113
|
-
- [ ] No `-p` flag + CWD not in any project → clear error: "Not in a registered project. Use -p <name>"
|
|
114
|
-
|
|
115
|
-
**ppm projects:**
|
|
116
|
-
- [ ] `ppm projects list` → formatted table with columns: Name, Path, Branch, Status
|
|
117
|
-
- [ ] `ppm projects add /path/to/repo --name myrepo` → adds project, confirms with message
|
|
118
|
-
- [ ] `ppm projects add` with duplicate name → error message
|
|
119
|
-
- [ ] `ppm projects remove myrepo` → removes, confirms with message
|
|
120
|
-
|
|
121
|
-
**ppm config:**
|
|
122
|
-
- [ ] `ppm config get port` → prints current port value
|
|
123
|
-
- [ ] `ppm config set port 9090` → updates config file, confirms
|
|
124
|
-
- [ ] `ppm config get nonexistent` → error message
|
|
125
|
-
|
|
126
|
-
**ppm git:**
|
|
127
|
-
- [ ] `ppm git status` → colored output matching git status --short format (M=yellow, A=green, D=red)
|
|
128
|
-
- [ ] `ppm git log -n 5` → shows last 5 commits with hash, message, author, date
|
|
129
|
-
- [ ] `ppm git stage .` → stages all files, prints count
|
|
130
|
-
- [ ] `ppm git commit -m "test"` → creates commit, prints hash
|
|
131
|
-
- [ ] `ppm git commit` with nothing staged → "Nothing to commit" error
|
|
132
|
-
- [ ] `ppm git push` → pushes to remote, prints result
|
|
133
|
-
- [ ] `ppm git branch create feature-x` → creates branch, confirms
|
|
134
|
-
- [ ] `ppm git branch checkout feature-x` → switches branch, confirms
|
|
135
|
-
- [ ] `ppm git branch delete feature-x` → deletes branch, confirms
|
|
136
|
-
|
|
137
|
-
**ppm chat:**
|
|
138
|
-
- [ ] `ppm chat list` → table with columns: ID, Provider, Title, Date
|
|
139
|
-
- [ ] `ppm chat create` → creates session, prints session ID
|
|
140
|
-
- [ ] `ppm chat send <id> "fix the bug"` → streams response to stdout in real-time
|
|
141
|
-
- [ ] `ppm chat resume <id>` → interactive mode with `You:` / `Claude:` prompts
|
|
142
|
-
- [ ] Tool approval in interactive mode: `[Tool: Bash] Allow? (y/n):` prompt
|
|
143
|
-
- [ ] `ppm chat delete <id>` → deletes session, confirms
|