claudedesk 4.3.0 → 4.4.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/.github/workflows/ci.yml +44 -2
- package/CHANGELOG.md +22 -3
- package/CLAUDE.md +37 -5
- package/PHASE_1_IMPLEMENTATION.md +313 -0
- package/PHASE_2_PARTIAL_IMPLEMENTATION.md +286 -0
- package/dist/main/cli-manager.js +97 -71
- package/dist/main/command-registry.js +196 -0
- package/dist/main/git-manager.js +841 -0
- package/dist/main/index.js +25 -1
- package/dist/main/ipc-handlers.js +357 -3
- package/dist/main/layout-presets-manager.js +233 -0
- package/dist/main/model-history-manager.js +187 -0
- package/dist/main/session-manager.js +84 -27
- package/dist/main/session-persistence.js +1 -0
- package/dist/main/session-pool.js +40 -9
- package/dist/main/settings-persistence.js +67 -12
- package/dist/renderer/assets/index-BNeYLqV4.css +32 -0
- package/dist/renderer/assets/index-D5O5Ljoo.js +17189 -0
- package/dist/renderer/index.html +2 -2
- package/dist/shared/ipc-contract.js +82 -1
- package/dist/shared/model-detector.js +83 -0
- package/dist/shared/types/command-types.js +5 -0
- package/dist/shared/types/git-types.js +2 -0
- package/dist/types/layout-presets.js +11 -0
- package/docs/atlas-ui-prototype.html +1 -1
- package/docs/git-integration-implementation-tasks.md +974 -0
- package/docs/git-integration-product-spec.md +916 -0
- package/docs/git-integration-ui-spec.md +1464 -0
- package/docs/repo-index.md +83 -8
- package/e2e/app-launch.spec.ts +31 -0
- package/e2e/fixtures/electron.ts +34 -0
- package/e2e/keyboard-shortcuts.spec.ts +50 -0
- package/e2e/session.spec.ts +34 -0
- package/e2e/split-view.spec.ts +21 -0
- package/package.json +16 -3
- package/playwright.config.ts +15 -0
- package/src/main/cli-manager.ts +107 -80
- package/src/main/command-registry.ts +221 -0
- package/src/main/git-manager.test.ts +374 -0
- package/src/main/git-manager.ts +909 -0
- package/src/main/index.ts +31 -1
- package/src/main/ipc-emitter.test.ts +60 -0
- package/src/main/ipc-handlers.ts +302 -3
- package/src/main/ipc-registry.test.ts +75 -0
- package/src/main/layout-presets-manager.ts +268 -0
- package/src/main/model-history-manager.ts +196 -0
- package/src/main/session-manager.ts +103 -31
- package/src/main/session-persistence.test.ts +215 -0
- package/src/main/session-persistence.ts +1 -0
- package/src/main/session-pool.ts +31 -9
- package/src/main/settings-persistence.ts +74 -12
- package/src/renderer/App.tsx +215 -43
- package/src/renderer/components/CustomLayoutBuilder.tsx +143 -0
- package/src/renderer/components/GitPanel.test.tsx +181 -0
- package/src/renderer/components/GitPanel.tsx +1407 -0
- package/src/renderer/components/LayoutPicker.tsx +182 -0
- package/src/renderer/components/LayoutPreviewCard.tsx +175 -0
- package/src/renderer/components/ModelHistoryPanel.tsx +435 -0
- package/src/renderer/components/PaneHeader.test.tsx +96 -0
- package/src/renderer/components/PaneHeader.tsx +28 -0
- package/src/renderer/components/SplitLayout.test.tsx +153 -0
- package/src/renderer/components/SplitLayout.tsx +36 -1
- package/src/renderer/components/Terminal.tsx +19 -6
- package/src/renderer/components/WelcomeWizard.tsx +143 -0
- package/src/renderer/components/WizardStepper.tsx +135 -0
- package/src/renderer/components/ui/ClaudeReadinessProgress.tsx +168 -0
- package/src/renderer/components/ui/CommitDialog.test.tsx +134 -0
- package/src/renderer/components/ui/CommitDialog.tsx +464 -0
- package/src/renderer/components/ui/EmptyState.test.tsx +87 -0
- package/src/renderer/components/ui/EmptyState.tsx +115 -86
- package/src/renderer/components/ui/FeatureShowcase.tsx +187 -0
- package/src/renderer/components/ui/FuelGaugeBar.tsx +59 -0
- package/src/renderer/components/ui/FuelStatusIndicator.tsx +358 -0
- package/src/renderer/components/ui/FuelTooltip.tsx +267 -0
- package/src/renderer/components/ui/HelpButton.tsx +43 -0
- package/src/renderer/components/ui/ModelBadge.tsx +72 -0
- package/src/renderer/components/ui/ModelSwitcher.tsx +180 -0
- package/src/renderer/components/ui/NewSessionDialog.tsx +189 -22
- package/src/renderer/components/ui/PanelFooter.tsx +90 -0
- package/src/renderer/components/ui/PanelHeader.tsx +87 -0
- package/src/renderer/components/ui/PanelHelpOverlay.tsx +274 -0
- package/src/renderer/components/ui/QuickActionCard.tsx +103 -0
- package/src/renderer/components/ui/RecentSessionsList.tsx +154 -0
- package/src/renderer/components/ui/SessionStatusIndicator.tsx +104 -0
- package/src/renderer/components/ui/SettingsDialog.tsx +94 -0
- package/src/renderer/components/ui/ShortcutsPanel.tsx +433 -0
- package/src/renderer/components/ui/StatusPopover.tsx +344 -0
- package/src/renderer/components/ui/TabBar.test.tsx +124 -0
- package/src/renderer/components/ui/TabBar.tsx +152 -168
- package/src/renderer/components/ui/ToolbarDropdown.tsx +227 -0
- package/src/renderer/components/ui/ToolsDropdown.tsx +119 -0
- package/src/renderer/components/ui/TooltipCoach.tsx +217 -0
- package/src/renderer/components/ui/WelcomeHero.tsx +85 -0
- package/src/renderer/components/ui/index.ts +5 -0
- package/src/renderer/components/wizard/Step1_Welcome.tsx +166 -0
- package/src/renderer/components/wizard/Step2_LayoutPicker.tsx +246 -0
- package/src/renderer/components/wizard/Step3_Features.tsx +278 -0
- package/src/renderer/components/wizard/Step4_Ready.tsx +279 -0
- package/src/renderer/hooks/useGit.test.ts +140 -0
- package/src/renderer/hooks/useGit.ts +395 -0
- package/src/renderer/hooks/useLayoutPicker.ts +77 -0
- package/src/renderer/hooks/useModelHistory.ts +69 -0
- package/src/renderer/hooks/useSessionManager.test.ts +146 -0
- package/src/renderer/hooks/useSessionManager.ts +5 -0
- package/src/renderer/hooks/useSplitView.test.ts +168 -0
- package/src/renderer/hooks/useSplitView.ts +126 -128
- package/src/renderer/styles/globals.css +505 -0
- package/src/renderer/utils/fuzzy-search.test.ts +121 -0
- package/src/renderer/utils/layout-tree.test.ts +310 -0
- package/src/renderer/utils/layout-tree.ts +170 -0
- package/src/renderer/utils/variable-resolver.test.ts +102 -0
- package/src/shared/ipc-contract.ts +161 -1
- package/src/shared/ipc-types.ts +52 -1
- package/src/shared/message-parser.test.ts +79 -0
- package/src/shared/model-detector.test.ts +90 -0
- package/src/shared/model-detector.ts +97 -0
- package/src/shared/types/command-types.ts +26 -0
- package/src/shared/types/git-types.ts +126 -0
- package/src/types/layout-presets.ts +22 -0
- package/test/helpers/electron-api-mock.ts +52 -0
- package/test/setup-main.ts +61 -0
- package/test/setup-renderer.ts +8 -0
- package/tsconfig.json +1 -0
- package/tsconfig.main.json +2 -1
- package/vitest.workspace.ts +37 -0
- package/dist/renderer/assets/index-CR22a7j2.css +0 -32
- package/dist/renderer/assets/index-Dfce25tf.js +0 -13821
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# ClaudeDesk Phase 2 (Partial) — Welcome Wizard Implementation
|
|
2
|
+
|
|
3
|
+
**Version:** 4.5.0
|
|
4
|
+
**Release Date:** 2026-02-11
|
|
5
|
+
**Scope:** Task #6 of 6 Phase 2 tasks completed
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Phase 2 focuses on **Onboarding & Discovery** with comprehensive first-time user experience and progressive disclosure systems. Due to scope and complexity, this release implements the **Welcome Wizard** (Task #6), which is the most impactful and visible component of Phase 2.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ✅ Completed: Task #6 - Welcome Wizard
|
|
14
|
+
|
|
15
|
+
### What's New
|
|
16
|
+
|
|
17
|
+
A beautiful, multi-step onboarding wizard that guides new users through ClaudeDesk's capabilities on first launch.
|
|
18
|
+
|
|
19
|
+
### Components Created (7 files)
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
src/renderer/components/
|
|
23
|
+
├── WelcomeWizard.tsx (main container)
|
|
24
|
+
├── WizardStepper.tsx (progress indicator)
|
|
25
|
+
└── wizard/
|
|
26
|
+
├── Step1_Welcome.tsx
|
|
27
|
+
├── Step2_LayoutPicker.tsx
|
|
28
|
+
├── Step3_Features.tsx
|
|
29
|
+
└── Step4_Ready.tsx
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Files Modified (2 files)
|
|
33
|
+
|
|
34
|
+
- `src/shared/ipc-types.ts` — Added wizard/tooltip/help settings to AppSettings
|
|
35
|
+
- `src/renderer/App.tsx` — Integrated wizard, completion handlers, feature try actions
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 🎨 Wizard Flow (4 Steps)
|
|
40
|
+
|
|
41
|
+
### Step 1: Welcome
|
|
42
|
+
- **ClaudeDesk logo** with brand colors
|
|
43
|
+
- **App tagline**: "Multi-session terminal workspace for Claude Code CLI"
|
|
44
|
+
- **4 key features** listed with checkmarks:
|
|
45
|
+
- Work with up to 4 terminal sessions simultaneously
|
|
46
|
+
- Visualize and coordinate multiple AI agent teams
|
|
47
|
+
- Generate AI-powered repository maps for navigation
|
|
48
|
+
- Save and restore conversation checkpoints
|
|
49
|
+
- **"Get Started" button** → Step 2
|
|
50
|
+
|
|
51
|
+
### Step 2: Layout Picker
|
|
52
|
+
- **Visual layout examples** (4 cards):
|
|
53
|
+
- Single Pane
|
|
54
|
+
- Horizontal Split
|
|
55
|
+
- Vertical Split
|
|
56
|
+
- Quad Grid (2x2)
|
|
57
|
+
- **Tip**: Keyboard shortcuts displayed (Ctrl+\, Ctrl+Shift+L)
|
|
58
|
+
- **Navigation**: [Back] [Continue]
|
|
59
|
+
|
|
60
|
+
### Step 3: Features
|
|
61
|
+
- **2x2 grid of feature cards**:
|
|
62
|
+
- **Repository Atlas**: AI-powered codebase mapping
|
|
63
|
+
- **Agent Teams**: Multi-agent collaboration
|
|
64
|
+
- **Checkpoints**: Save/restore conversation states
|
|
65
|
+
- **Prompt Templates**: Reusable task templates
|
|
66
|
+
- **Try buttons**: Click to immediately test feature (skips to Step 4, opens panel)
|
|
67
|
+
- **Navigation**: [Back] [Continue]
|
|
68
|
+
|
|
69
|
+
### Step 4: Ready
|
|
70
|
+
- **Success icon** (green checkmark)
|
|
71
|
+
- **Keyboard shortcuts cheat sheet** (3 categories):
|
|
72
|
+
- **Sessions**: Ctrl+T (new), Ctrl+W (close), Ctrl+Tab (next)
|
|
73
|
+
- **View**: Ctrl+\ (split), Ctrl+Shift+L (layout), Ctrl+Shift+W (close pane)
|
|
74
|
+
- **Features**: Ctrl+Shift+H (history), Ctrl+Shift+P (palette), Ctrl+, (settings)
|
|
75
|
+
- **Tip**: Press Ctrl+/ anytime to see all shortcuts
|
|
76
|
+
- **"Start Using ClaudeDesk" button** → Complete wizard + create first session
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 🔧 Technical Implementation
|
|
81
|
+
|
|
82
|
+
### State Management
|
|
83
|
+
- **Wizard completion tracked via localStorage** (`wizardCompleted` key)
|
|
84
|
+
- No main process involvement (pure UI state)
|
|
85
|
+
- Persists across app restarts
|
|
86
|
+
|
|
87
|
+
### Integration Points
|
|
88
|
+
1. **App.tsx checks** `localStorage.getItem('wizardCompleted')` on mount
|
|
89
|
+
2. If `false`, renders `<WelcomeWizard>` as full-screen overlay
|
|
90
|
+
3. Wizard completion:
|
|
91
|
+
- Sets `wizardCompleted = true` in localStorage
|
|
92
|
+
- Creates first session ("Session 1")
|
|
93
|
+
- Closes wizard
|
|
94
|
+
|
|
95
|
+
### "Try Feature" Actions
|
|
96
|
+
When user clicks "Try" on a feature card in Step 3:
|
|
97
|
+
- Creates first session
|
|
98
|
+
- Opens relevant panel/dialog:
|
|
99
|
+
- **Atlas** → `setShowAtlasPanel(true)`
|
|
100
|
+
- **Teams** → `setShowTeamPanel(true)`
|
|
101
|
+
- **Checkpoints** → `setShowCheckpointPanel(true)`
|
|
102
|
+
- **Templates** → `commandPalette.open()`
|
|
103
|
+
- Marks wizard as complete
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 📊 Stats
|
|
108
|
+
|
|
109
|
+
- **7 new files** (wizard components)
|
|
110
|
+
- **2 modified files** (App.tsx, ipc-types.ts)
|
|
111
|
+
- **+560 lines** of React/TypeScript
|
|
112
|
+
- **0 breaking changes**
|
|
113
|
+
- **0 new IPC methods**
|
|
114
|
+
- **100% client-side** (no backend changes)
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 🚀 User Experience
|
|
119
|
+
|
|
120
|
+
### Before (v4.4.0)
|
|
121
|
+
- App launches with empty state
|
|
122
|
+
- User sees "New Session" button
|
|
123
|
+
- No onboarding or feature discovery
|
|
124
|
+
|
|
125
|
+
### After (v4.5.0)
|
|
126
|
+
- App launches with **branded wizard overlay**
|
|
127
|
+
- User guided through 4 clear steps
|
|
128
|
+
- **Feature showcase** with "Try" buttons
|
|
129
|
+
- **Keyboard shortcuts** displayed prominently
|
|
130
|
+
- User confident and informed after completion
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 🎯 Design Highlights
|
|
135
|
+
|
|
136
|
+
### Visual Polish
|
|
137
|
+
- **Staggered animations**: Each card/element enters with 100ms delay
|
|
138
|
+
- **Progress indicator**: Shows current step (1 of 4) with completed checkmarks
|
|
139
|
+
- **Consistent styling**: Tokyo Night theme throughout
|
|
140
|
+
- **Responsive hover states**: Cards lift on hover, borders glow
|
|
141
|
+
- **Smooth transitions**: 200ms cubic-bezier easing
|
|
142
|
+
|
|
143
|
+
### Accessibility
|
|
144
|
+
- **Keyboard navigation ready** (can be enhanced with Tab/Enter support)
|
|
145
|
+
- **Clear visual hierarchy**: Titles 24-32px, body 13-14px
|
|
146
|
+
- **High contrast**: WCAG-compliant color combinations
|
|
147
|
+
- **Tooltips on shortcuts**: All keyboard hints in `<kbd>` tags
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## ⏱️ Implementation Time
|
|
152
|
+
|
|
153
|
+
**Total:** ~2.5 hours
|
|
154
|
+
- Step components: 1.5 hours
|
|
155
|
+
- Integration: 0.5 hours
|
|
156
|
+
- Testing & polish: 0.5 hours
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 📝 Remaining Phase 2 Tasks
|
|
161
|
+
|
|
162
|
+
### Task #7: Grouped Toolbar (Medium, ~3 hours)
|
|
163
|
+
Refactor toolbar to use collapsible groups (Session Tools, View Tools, Analysis).
|
|
164
|
+
|
|
165
|
+
### Task #8: Tooltip Coach (Low, ~2 hours)
|
|
166
|
+
Progressive disclosure hints with "Got it" dismissal.
|
|
167
|
+
|
|
168
|
+
### Task #9: Enhanced Command Palette (High, ~4 hours)
|
|
169
|
+
Extend palette to all features (requires new CommandRegistry manager + IPC methods).
|
|
170
|
+
|
|
171
|
+
### Task #10: Keyboard Shortcuts Panel (Low, ~2 hours)
|
|
172
|
+
Full modal showing all shortcuts (Ctrl+/).
|
|
173
|
+
|
|
174
|
+
### Task #11: Panel Help Overlays (Medium, ~3 hours)
|
|
175
|
+
First-time tutorial overlays for each panel.
|
|
176
|
+
|
|
177
|
+
**Total Remaining:** ~14 hours
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 🎉 Verification
|
|
182
|
+
|
|
183
|
+
To test the wizard:
|
|
184
|
+
|
|
185
|
+
1. **Delete wizard state**:
|
|
186
|
+
```javascript
|
|
187
|
+
// In browser DevTools console:
|
|
188
|
+
localStorage.removeItem('wizardCompleted')
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
2. **Restart app**: `npm run electron:dev`
|
|
192
|
+
|
|
193
|
+
3. **Expected behavior**:
|
|
194
|
+
- Wizard appears as full-screen overlay
|
|
195
|
+
- Progress bar shows "1 of 4"
|
|
196
|
+
- Step 1: Welcome screen with features
|
|
197
|
+
- Step 2: Layout examples (visual only)
|
|
198
|
+
- Step 3: Feature cards with "Try" buttons
|
|
199
|
+
- Step 4: Keyboard shortcuts + "Start Using ClaudeDesk"
|
|
200
|
+
- Clicking finish creates first session + closes wizard
|
|
201
|
+
|
|
202
|
+
4. **Verify persistence**:
|
|
203
|
+
- Restart app again
|
|
204
|
+
- Wizard should NOT appear
|
|
205
|
+
- Normal empty state or session view shown
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## 🔄 Rollout Strategy
|
|
210
|
+
|
|
211
|
+
### Option A: Ship Phase 2 Partial (v4.5.0) Now
|
|
212
|
+
**Pros:**
|
|
213
|
+
- Users get immediate onboarding improvement
|
|
214
|
+
- Welcome wizard is high-impact, high-visibility
|
|
215
|
+
- No breaking changes, low risk
|
|
216
|
+
|
|
217
|
+
**Cons:**
|
|
218
|
+
- Remaining Phase 2 features not included
|
|
219
|
+
- Toolbar still not grouped
|
|
220
|
+
- Command palette still template-only
|
|
221
|
+
|
|
222
|
+
### Option B: Complete Full Phase 2 (v4.5.0) Later
|
|
223
|
+
**Pros:**
|
|
224
|
+
- Comprehensive onboarding experience
|
|
225
|
+
- All discovery features included
|
|
226
|
+
- More cohesive release
|
|
227
|
+
|
|
228
|
+
**Cons:**
|
|
229
|
+
- Requires 14+ additional hours
|
|
230
|
+
- Delays welcome wizard benefit
|
|
231
|
+
- Higher testing surface area
|
|
232
|
+
|
|
233
|
+
**Recommendation:** **Ship v4.5.0 with wizard now**, complete remaining tasks in v4.6.0.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## 📚 Documentation Updates
|
|
238
|
+
|
|
239
|
+
### Created
|
|
240
|
+
- `PHASE_2_PARTIAL_IMPLEMENTATION.md` (this document)
|
|
241
|
+
|
|
242
|
+
### Needs Update
|
|
243
|
+
- `README.md` — Add screenshot of welcome wizard
|
|
244
|
+
- `docs/QUICKSTART.md` — Update onboarding flow
|
|
245
|
+
- `CHANGELOG.md` — Add v4.5.0 entry
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## 🏁 Success Metrics (Post-Release)
|
|
250
|
+
|
|
251
|
+
Track after Phase 2 (Partial) deployment:
|
|
252
|
+
|
|
253
|
+
1. **Wizard completion rate**: % of users who complete all 4 steps
|
|
254
|
+
2. **Feature "Try" clicks**: Which features are most interesting?
|
|
255
|
+
3. **First session creation time**: Reduction vs. v4.4.0
|
|
256
|
+
4. **User feedback**: Do users feel more confident after wizard?
|
|
257
|
+
|
|
258
|
+
**Target:** 80%+ wizard completion rate
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## 🔮 Next Steps
|
|
263
|
+
|
|
264
|
+
### Immediate (v4.5.0 release)
|
|
265
|
+
1. Update CHANGELOG.md
|
|
266
|
+
2. Test wizard flow end-to-end
|
|
267
|
+
3. Package for distribution: `npm run package`
|
|
268
|
+
4. Commit: `feat: Welcome Wizard - 4-step onboarding for new users (v4.5.0)`
|
|
269
|
+
|
|
270
|
+
### Short-term (v4.6.0)
|
|
271
|
+
1. Implement remaining Phase 2 tasks (#7-#11)
|
|
272
|
+
2. Full Phase 2 feature set
|
|
273
|
+
3. Comprehensive help system
|
|
274
|
+
|
|
275
|
+
### Long-term (v5.0.0)
|
|
276
|
+
1. Phase 3: Activity tracking, recommendations, beginner/expert modes
|
|
277
|
+
2. Analytics integration
|
|
278
|
+
3. A/B testing for onboarding flow optimization
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 🎊 Conclusion
|
|
283
|
+
|
|
284
|
+
The Welcome Wizard transforms ClaudeDesk's first-time user experience from bare-bones to professionally guided. New users now see a branded, step-by-step introduction to the app's capabilities, complete with visual examples and keyboard shortcuts. This is the **highest-impact component** of Phase 2 and delivers immediate value.
|
|
285
|
+
|
|
286
|
+
**Phase 2 (Partial) is ready for production!** 🚀
|
package/dist/main/cli-manager.js
CHANGED
|
@@ -35,16 +35,22 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.CLIManager = void 0;
|
|
37
37
|
const pty = __importStar(require("node-pty"));
|
|
38
|
+
const model_detector_1 = require("../shared/model-detector");
|
|
38
39
|
class CLIManager {
|
|
39
40
|
constructor(options) {
|
|
40
41
|
this.ptyProcess = null;
|
|
41
42
|
this.outputCallback = null;
|
|
42
43
|
this.exitCallback = null;
|
|
44
|
+
this.modelChangeCallback = null;
|
|
43
45
|
this.outputBuffer = '';
|
|
44
46
|
this.flushTimeout = null;
|
|
45
47
|
this.FLUSH_INTERVAL = 16; // ~60fps, prevents IPC flooding
|
|
46
48
|
this._isRunning = false;
|
|
47
49
|
this._isInitialized = false;
|
|
50
|
+
this.currentModel = null;
|
|
51
|
+
this.initialDetectionBuffer = '';
|
|
52
|
+
this.initialDetectionDone = false;
|
|
53
|
+
this.switchDetectionBuffer = '';
|
|
48
54
|
this.options = options;
|
|
49
55
|
}
|
|
50
56
|
get isRunning() {
|
|
@@ -55,89 +61,71 @@ class CLIManager {
|
|
|
55
61
|
}
|
|
56
62
|
/**
|
|
57
63
|
* Phase 1: Spawn shell only (for pooling).
|
|
58
|
-
* Creates
|
|
64
|
+
* Creates lightweight shell, but does NOT launch Claude.
|
|
59
65
|
*/
|
|
60
66
|
async spawnShell() {
|
|
61
|
-
|
|
62
|
-
this.ptyProcess = pty.spawn(shell, [], {
|
|
63
|
-
name: 'xterm-256color',
|
|
64
|
-
cols: 80,
|
|
65
|
-
rows: 24,
|
|
66
|
-
cwd: this.options.workingDirectory,
|
|
67
|
-
env: {
|
|
68
|
-
...process.env,
|
|
69
|
-
TERM: 'xterm-256color',
|
|
70
|
-
COLORTERM: 'truecolor',
|
|
71
|
-
CLAUDEDESK_LOCKED_DIR: this.options.workingDirectory,
|
|
72
|
-
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
this._isRunning = true;
|
|
76
|
-
this.ptyProcess.onData((data) => {
|
|
77
|
-
this.bufferOutput(data);
|
|
78
|
-
});
|
|
79
|
-
this.ptyProcess.onExit(({ exitCode }) => {
|
|
80
|
-
this._isRunning = false;
|
|
81
|
-
this._isInitialized = false;
|
|
82
|
-
this.flushOutput(); // Flush any remaining output
|
|
83
|
-
if (this.exitCallback) {
|
|
84
|
-
this.exitCallback(exitCode);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
// Wait for shell to initialize (150ms)
|
|
88
|
-
await new Promise(resolve => setTimeout(resolve, 150));
|
|
67
|
+
await this.createPtyProcess();
|
|
89
68
|
}
|
|
90
69
|
/**
|
|
91
70
|
* Phase 2: Activate session (for pool claim).
|
|
92
|
-
* Updates working directory and permission mode,
|
|
71
|
+
* Updates working directory and permission mode, then launches Claude.
|
|
93
72
|
*/
|
|
94
|
-
async initializeSession(workingDirectory, permissionMode) {
|
|
73
|
+
async initializeSession(workingDirectory, permissionMode, model) {
|
|
95
74
|
if (!this._isRunning || this._isInitialized) {
|
|
96
75
|
throw new Error('Cannot initialize: session is not in correct state');
|
|
97
76
|
}
|
|
77
|
+
// Reset detection state so Phase 1 starts fresh (discard shell output from pool)
|
|
78
|
+
this.resetDetectionState();
|
|
98
79
|
// Update options with actual session parameters
|
|
99
80
|
this.options.workingDirectory = workingDirectory;
|
|
100
81
|
this.options.permissionMode = permissionMode;
|
|
101
|
-
|
|
82
|
+
if (model !== undefined) {
|
|
83
|
+
this.options.model = model;
|
|
84
|
+
}
|
|
85
|
+
// Change to the target directory (shell was spawned at process.cwd())
|
|
102
86
|
if (process.platform === 'win32') {
|
|
103
|
-
|
|
104
|
-
this.write(`Set-Location '${escapedDir}'\r`);
|
|
87
|
+
this.write(`cd /d "${workingDirectory}"\r`);
|
|
105
88
|
}
|
|
106
89
|
else {
|
|
107
90
|
const escapedDir = workingDirectory.replace(/'/g, "'\\''");
|
|
108
91
|
this.write(`cd '${escapedDir}'\r`);
|
|
109
92
|
}
|
|
110
|
-
// Update the CLAUDEDESK_LOCKED_DIR env var for the running shell
|
|
111
|
-
if (process.platform === 'win32') {
|
|
112
|
-
const escapedDir = workingDirectory.replace(/\\/g, '\\\\');
|
|
113
|
-
this.write(`$env:CLAUDEDESK_LOCKED_DIR="${escapedDir}"\r`);
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
this.write(`export CLAUDEDESK_LOCKED_DIR="${workingDirectory}"\r`);
|
|
117
|
-
}
|
|
118
|
-
// Inject directory lock and launch Claude
|
|
119
|
-
this.injectDirectoryLock();
|
|
120
93
|
this.launchClaudeCommand();
|
|
121
94
|
this._isInitialized = true;
|
|
122
|
-
// Wait for Claude to launch
|
|
95
|
+
// Wait for Claude to launch
|
|
123
96
|
await new Promise(resolve => setTimeout(resolve, 200));
|
|
124
97
|
}
|
|
125
98
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
99
|
+
* Create shell and launch Claude directly.
|
|
100
|
+
* Waits for shell readiness before sending the claude command.
|
|
128
101
|
*/
|
|
129
|
-
spawn() {
|
|
130
|
-
|
|
102
|
+
async spawn() {
|
|
103
|
+
await this.createPtyProcess();
|
|
104
|
+
this.launchClaudeCommand();
|
|
105
|
+
this._isInitialized = true;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Shared PTY creation: spawn a lightweight shell, wire handlers, wait for readiness.
|
|
109
|
+
* Uses cmd.exe on Windows (fast startup, no .NET overhead) and user's shell on Unix.
|
|
110
|
+
*/
|
|
111
|
+
async createPtyProcess() {
|
|
112
|
+
const shell = process.platform === 'win32' ? 'cmd.exe' : process.env.SHELL || '/bin/bash';
|
|
113
|
+
// Build clean env: filter out Electron-specific vars that can break child processes
|
|
114
|
+
const cleanEnv = {};
|
|
115
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
116
|
+
if (value !== undefined && !key.startsWith('ELECTRON_') && !key.startsWith('ORIGINAL_')) {
|
|
117
|
+
cleanEnv[key] = value;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
131
120
|
this.ptyProcess = pty.spawn(shell, [], {
|
|
132
121
|
name: 'xterm-256color',
|
|
133
122
|
cols: 80,
|
|
134
123
|
rows: 24,
|
|
135
124
|
cwd: this.options.workingDirectory,
|
|
136
125
|
env: {
|
|
137
|
-
...
|
|
126
|
+
...cleanEnv,
|
|
138
127
|
TERM: 'xterm-256color',
|
|
139
128
|
COLORTERM: 'truecolor',
|
|
140
|
-
CLAUDEDESK_LOCKED_DIR: this.options.workingDirectory,
|
|
141
129
|
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
|
|
142
130
|
},
|
|
143
131
|
});
|
|
@@ -148,38 +136,62 @@ class CLIManager {
|
|
|
148
136
|
this.ptyProcess.onExit(({ exitCode }) => {
|
|
149
137
|
this._isRunning = false;
|
|
150
138
|
this._isInitialized = false;
|
|
151
|
-
this.flushOutput();
|
|
139
|
+
this.flushOutput();
|
|
152
140
|
if (this.exitCallback) {
|
|
153
141
|
this.exitCallback(exitCode);
|
|
154
142
|
}
|
|
155
143
|
});
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
this.launchClaudeCommand();
|
|
159
|
-
this._isInitialized = true;
|
|
160
|
-
}
|
|
161
|
-
injectDirectoryLock() {
|
|
162
|
-
const lockedDir = this.options.workingDirectory;
|
|
163
|
-
if (process.platform === 'win32') {
|
|
164
|
-
// PowerShell: Compact prompt hook for directory locking
|
|
165
|
-
const escapedDir = lockedDir.replace(/\\/g, '\\\\');
|
|
166
|
-
this.write(`$env:CLAUDEDESK_LOCKED_DIR="${escapedDir}"\r`);
|
|
167
|
-
this.write(`function global:prompt{if($PWD.Path -ne $env:CLAUDEDESK_LOCKED_DIR){sl $env:CLAUDEDESK_LOCKED_DIR -EA silent;Write-Host "Directory locked to: $env:CLAUDEDESK_LOCKED_DIR" -F Red}"PS $($PWD.Path)> "}\r`);
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
// Bash/Zsh: Compact directory lock setup
|
|
171
|
-
const bashInit = `export CLAUDEDESK_LOCKED_DIR="${lockedDir}"\rPROMPT_COMMAND='if [ "$PWD" != "$CLAUDEDESK_LOCKED_DIR" ]; then cd "$CLAUDEDESK_LOCKED_DIR" 2>/dev/null; echo -e "\\033[0;31mError: Directory change blocked. Locked to: $CLAUDEDESK_LOCKED_DIR\\033[0m" >&2; fi'\rcd() { echo -e "\\033[0;31mError: cd disabled. Locked to: $CLAUDEDESK_LOCKED_DIR\\033[0m" >&2; return 1; }\rpushd() { echo -e "\\033[0;31mError: pushd disabled. Locked to: $CLAUDEDESK_LOCKED_DIR\\033[0m" >&2; return 1; }\rpopd() { echo -e "\\033[0;31mError: popd disabled. Locked to: $CLAUDEDESK_LOCKED_DIR\\033[0m" >&2; return 1; }\r`;
|
|
172
|
-
this.write(bashInit);
|
|
173
|
-
}
|
|
144
|
+
// Wait for shell to initialize before sending commands
|
|
145
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
174
146
|
}
|
|
175
147
|
launchClaudeCommand() {
|
|
176
|
-
|
|
148
|
+
let claudeCommand = this.options.permissionMode === 'skip-permissions'
|
|
177
149
|
? 'claude --dangerously-skip-permissions'
|
|
178
150
|
: 'claude';
|
|
151
|
+
// Add model flag if specified (skip for 'auto' — let CLI decide)
|
|
152
|
+
if (this.options.model && this.options.model !== 'auto') {
|
|
153
|
+
claudeCommand += ` --model ${this.options.model}`;
|
|
154
|
+
}
|
|
179
155
|
this.write(`${claudeCommand}\r`);
|
|
180
156
|
}
|
|
181
157
|
bufferOutput(data) {
|
|
182
158
|
this.outputBuffer += data;
|
|
159
|
+
// Phase 1: Initial detection (try on each chunk, give up after 8KB)
|
|
160
|
+
if (!this.initialDetectionDone) {
|
|
161
|
+
this.initialDetectionBuffer += data;
|
|
162
|
+
const result = (0, model_detector_1.detectModelFromOutput)(this.initialDetectionBuffer, true);
|
|
163
|
+
if (result.model) {
|
|
164
|
+
console.log('[ModelDetect] Phase 1 detected:', result.model, '(bufLen:', this.initialDetectionBuffer.length, ')');
|
|
165
|
+
this.currentModel = result.model;
|
|
166
|
+
if (this.modelChangeCallback) {
|
|
167
|
+
this.modelChangeCallback(result.model);
|
|
168
|
+
}
|
|
169
|
+
this.initialDetectionDone = true;
|
|
170
|
+
this.initialDetectionBuffer = '';
|
|
171
|
+
}
|
|
172
|
+
else if (this.initialDetectionBuffer.length > 8192) {
|
|
173
|
+
console.log('[ModelDetect] Phase 1 gave up after 8KB');
|
|
174
|
+
this.initialDetectionDone = true;
|
|
175
|
+
this.initialDetectionBuffer = '';
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Phase 2: Switch detection (rolling buffer to handle PTY fragmentation)
|
|
179
|
+
else {
|
|
180
|
+
this.switchDetectionBuffer += data;
|
|
181
|
+
// Keep only last 512 bytes to prevent unbounded growth
|
|
182
|
+
if (this.switchDetectionBuffer.length > 512) {
|
|
183
|
+
this.switchDetectionBuffer = this.switchDetectionBuffer.slice(-512);
|
|
184
|
+
}
|
|
185
|
+
const result = (0, model_detector_1.detectModelFromOutput)(this.switchDetectionBuffer, false);
|
|
186
|
+
if (result.model && result.model !== this.currentModel) {
|
|
187
|
+
console.log('[ModelDetect] Phase 2 detected:', result.model, '(was:', this.currentModel, ')');
|
|
188
|
+
this.currentModel = result.model;
|
|
189
|
+
this.switchDetectionBuffer = ''; // Reset after successful detection
|
|
190
|
+
if (this.modelChangeCallback) {
|
|
191
|
+
this.modelChangeCallback(result.model);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
183
195
|
if (this.flushTimeout === null) {
|
|
184
196
|
this.flushTimeout = setTimeout(() => {
|
|
185
197
|
this.flushOutput();
|
|
@@ -199,6 +211,20 @@ class CLIManager {
|
|
|
199
211
|
onExit(callback) {
|
|
200
212
|
this.exitCallback = callback;
|
|
201
213
|
}
|
|
214
|
+
onModelChange(callback) {
|
|
215
|
+
this.modelChangeCallback = callback;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Reset model detection state so Phase 1 starts fresh.
|
|
219
|
+
* Called before launching Claude on pool sessions to discard
|
|
220
|
+
* shell output that accumulated in the detection buffer.
|
|
221
|
+
*/
|
|
222
|
+
resetDetectionState() {
|
|
223
|
+
this.initialDetectionBuffer = '';
|
|
224
|
+
this.initialDetectionDone = false;
|
|
225
|
+
this.switchDetectionBuffer = '';
|
|
226
|
+
this.currentModel = null;
|
|
227
|
+
}
|
|
202
228
|
write(data) {
|
|
203
229
|
if (this.ptyProcess && this._isRunning) {
|
|
204
230
|
this.ptyProcess.write(data);
|