claudedesk 4.5.0 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -1
- package/CLAUDE.md +8 -3
- package/dist/main/index.js +10 -1
- package/dist/main/ipc-handlers.js +107 -1
- package/dist/main/quota-service.js +6 -4
- package/dist/main/tunnel-manager.js +546 -0
- package/dist/renderer/assets/{index-DPk5RtXR.js → index-BtxPa5XT.js} +3537 -1765
- package/dist/renderer/assets/{index-DTGEErZK.css → index-Df3cuu7-.css} +1 -1
- package/dist/renderer/index.html +2 -2
- package/dist/shared/ipc-contract.js +36 -0
- package/dist/shared/types/tunnel-types.js +2 -0
- package/docs/repo-index.md +15 -2
- package/docs/tunnel-implementation-plan.md +255 -0
- package/docs/tunnel-ui-spec.md +1117 -0
- package/package.json +1 -1
- package/src/main/index.ts +12 -1
- package/src/main/ipc-handlers.ts +68 -1
- package/src/main/quota-service.ts +6 -4
- package/src/main/tunnel-manager.ts +622 -0
- package/src/renderer/App.tsx +19 -0
- package/src/renderer/components/TunnelCreateDialog.tsx +669 -0
- package/src/renderer/components/TunnelPanel.tsx +2005 -0
- package/src/renderer/components/TunnelRequestLogs.tsx +568 -0
- package/src/renderer/components/ui/BudgetPanel.tsx +17 -0
- package/src/renderer/components/ui/FuelTooltip.tsx +10 -2
- package/src/renderer/components/ui/TabBar.tsx +7 -1
- package/src/renderer/components/ui/ToolsDropdown.tsx +21 -0
- package/src/renderer/hooks/useTunnel.ts +266 -0
- package/src/shared/ipc-contract.ts +73 -0
- package/src/shared/ipc-types.ts +17 -0
- package/src/shared/types/tunnel-types.ts +80 -0
- package/PHASE_1_IMPLEMENTATION.md +0 -313
- package/PHASE_2_PARTIAL_IMPLEMENTATION.md +0 -286
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
+
## [4.6.0] - 2026-02-19
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **LaunchTunnel integration** — Expose local ports to the internet via LaunchTunnel (14th domain)
|
|
20
|
+
- `TunnelManager` with hybrid REST API + CLI (`lt preview`) process management
|
|
21
|
+
- API key management with validation, stored in `~/.claudedesk/tunnel-settings.json`
|
|
22
|
+
- Tunnel list with 30s cache, status mapping (API snake_case → camelCase)
|
|
23
|
+
- Account info section (email, plan badge, status)
|
|
24
|
+
- Request log viewer with method/path/status/duration/size columns
|
|
25
|
+
- TunnelPanel (4 views: setup/main/settings/logs), TunnelCreateDialog, TunnelRequestLogs
|
|
26
|
+
- CLI auto-detection via `where`/`which`, `shell: true` for cross-platform spawn
|
|
27
|
+
- Ctrl+Shift+U keyboard shortcut, ToolsDropdown entry with active count badge
|
|
28
|
+
- 17 IPC methods (`tunnel:*`): 13 invoke + 4 events
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- **Tunnel spawn ENOENT** — Use `shell: true` in spawn for cross-platform `.cmd` shim compatibility
|
|
32
|
+
- **Tunnel status mapping** — API returns `"active"` not `"connected"`; added to `mapApiStatus`
|
|
33
|
+
- **Tunnel CLI subcommand** — Fixed `lt preview --port` (was missing `preview` subcommand)
|
|
34
|
+
- **Tunnel URL parsing** — Match `URL:` output format from LaunchTunnel CLI (not `your url is:`)
|
|
35
|
+
- **API snake_case mapping** — Map `created_at`, `status_code`, `duration_ms` etc. to camelCase
|
|
36
|
+
- **Account response unwrapping** — API returns `{"user": {...}}` wrapper; now unwrapped correctly
|
|
37
|
+
- **Request logs endpoint** — Reverted from `/requests` back to correct `/logs` path
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- **IPC contract** — Expanded from 149 to ~166 methods (added 17 tunnel methods)
|
|
41
|
+
- **Project scale** — ~150 source files, ~49,000 LOC, 14 domains, 14 managers
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
16
45
|
## [4.5.0] - 2026-02-17
|
|
17
46
|
|
|
18
47
|
### Added
|
|
@@ -251,7 +280,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on suggesting changes and
|
|
|
251
280
|
|
|
252
281
|
---
|
|
253
282
|
|
|
254
|
-
[Unreleased]: https://github.com/carloluisito/claudedesk/compare/v4.
|
|
283
|
+
[Unreleased]: https://github.com/carloluisito/claudedesk/compare/v4.6.0...HEAD
|
|
284
|
+
[4.6.0]: https://github.com/carloluisito/claudedesk/compare/v4.5.0...v4.6.0
|
|
255
285
|
[4.5.0]: https://github.com/carloluisito/claudedesk/compare/v4.4.1...v4.5.0
|
|
256
286
|
[4.4.1]: https://github.com/carloluisito/claudedesk/compare/v4.3.1...v4.4.1
|
|
257
287
|
[4.3.1]: https://github.com/carloluisito/claudedesk/compare/v4.3.0...v4.3.1
|
package/CLAUDE.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Electron desktop app wrapping Claude Code CLI with multi-session terminals, split views, and agent team visualization.
|
|
4
4
|
|
|
5
|
+
## Workflow Rule
|
|
6
|
+
|
|
7
|
+
**Always run the `requirements-clarifier` agent (via the Task tool) on the user's initial prompt before planning or implementing.** This ensures requirements are analyzed, ambiguities are surfaced, and acceptance criteria are established before any work begins. Skip only for trivial tasks (typo fixes, single-line changes, or purely informational questions).
|
|
8
|
+
|
|
5
9
|
## Tech Stack
|
|
6
10
|
|
|
7
11
|
Electron 28 | React 18 | TypeScript | xterm.js | node-pty | Tailwind CSS | reactflow
|
|
@@ -11,9 +15,9 @@ Electron 28 | React 18 | TypeScript | xterm.js | node-pty | Tailwind CSS | react
|
|
|
11
15
|
```
|
|
12
16
|
┌─────────────────────────────────────────────┐
|
|
13
17
|
│ Main Process (Node.js) │
|
|
14
|
-
│
|
|
18
|
+
│ 14 managers + IPC handlers + session pool │
|
|
15
19
|
└──────────────────┬──────────────────────────┘
|
|
16
|
-
│ IPC (
|
|
20
|
+
│ IPC (~166 methods)
|
|
17
21
|
┌──────────────────┴──────────────────────────┐
|
|
18
22
|
│ Preload (auto-derived context bridge) │
|
|
19
23
|
└──────────────────┬──────────────────────────┘
|
|
@@ -26,7 +30,7 @@ Electron 28 | React 18 | TypeScript | xterm.js | node-pty | Tailwind CSS | react
|
|
|
26
30
|
|
|
27
31
|
**3-layer pattern per domain:** Manager (main) → Hook (renderer) → Components (renderer)
|
|
28
32
|
|
|
29
|
-
**IPC contract** (`src/shared/ipc-contract.ts`) is the single source of truth —
|
|
33
|
+
**IPC contract** (`src/shared/ipc-contract.ts`) is the single source of truth — ~166 methods. The preload bridge and `ElectronAPI` type are auto-derived from it.
|
|
30
34
|
|
|
31
35
|
## Domain Map
|
|
32
36
|
|
|
@@ -44,6 +48,7 @@ Electron 28 | React 18 | TypeScript | xterm.js | node-pty | Tailwind CSS | react
|
|
|
44
48
|
| Atlas | atlas-manager | useAtlas, AtlasPanel | types/atlas-types.ts | `atlas:*` |
|
|
45
49
|
| Git | git-manager | useGit, useDiffViewer, GitPanel, DiffViewer, DiffFileNav, DiffViewerHeader, DiffContentArea, CommitDialog, WorktreePanel, WorktreeCleanupDialog, diff-parser | types/git-types.ts | `git:*` |
|
|
46
50
|
| Playbooks | playbook-manager, playbook-executor, built-in-playbooks | usePlaybooks, PlaybookPicker, PlaybookParameterDialog, PlaybookProgressPanel, PlaybookPanel, PlaybookEditor | types/playbook-types.ts | `playbook:*` |
|
|
51
|
+
| Tunnels | tunnel-manager | useTunnel, TunnelPanel, TunnelCreateDialog, TunnelRequestLogs | types/tunnel-types.ts | `tunnel:*` |
|
|
47
52
|
| Window | index.ts | ConfirmDialog, SettingsDialog, AboutDialog, TitleBarBranding | ipc-types.ts | `window:*`, `dialog:*` |
|
|
48
53
|
|
|
49
54
|
## Adding a New IPC Method
|
package/dist/main/index.js
CHANGED
|
@@ -51,6 +51,7 @@ const model_history_manager_1 = require("./model-history-manager");
|
|
|
51
51
|
const git_manager_1 = require("./git-manager");
|
|
52
52
|
const playbook_manager_1 = require("./playbook-manager");
|
|
53
53
|
const playbook_executor_1 = require("./playbook-executor");
|
|
54
|
+
const tunnel_manager_1 = require("./tunnel-manager");
|
|
54
55
|
const ipc_handlers_1 = require("./ipc-handlers");
|
|
55
56
|
// Prevent EPIPE crashes when stdout/stderr pipe breaks (e.g., renderer window closes while PTY is active)
|
|
56
57
|
process.stdout?.on?.('error', (err) => { if (err.code !== 'EPIPE')
|
|
@@ -75,6 +76,7 @@ let modelHistoryManager = null;
|
|
|
75
76
|
let gitManager = null;
|
|
76
77
|
let playbookManager = null;
|
|
77
78
|
let playbookExecutor = null;
|
|
79
|
+
let tunnelManager = null;
|
|
78
80
|
const CONFIG_DIR = path.join(electron_1.app.getPath('home'), '.claudedesk');
|
|
79
81
|
const WINDOW_STATE_FILE = path.join(CONFIG_DIR, 'window-state.json');
|
|
80
82
|
function ensureConfigDir() {
|
|
@@ -187,6 +189,9 @@ function createWindow() {
|
|
|
187
189
|
// Initialize playbook manager + executor
|
|
188
190
|
playbookManager = new playbook_manager_1.PlaybookManager();
|
|
189
191
|
playbookExecutor = new playbook_executor_1.PlaybookExecutor(sessionManager, checkpointManager, playbookManager);
|
|
192
|
+
// Initialize tunnel manager
|
|
193
|
+
tunnelManager = new tunnel_manager_1.TunnelManager();
|
|
194
|
+
tunnelManager.setMainWindow(mainWindow);
|
|
190
195
|
// Initialize command registry
|
|
191
196
|
commandRegistry = new command_registry_1.CommandRegistry();
|
|
192
197
|
// Initialize agent team manager
|
|
@@ -198,7 +203,7 @@ function createWindow() {
|
|
|
198
203
|
agentTeamManager?.onSessionClosed(sessionId);
|
|
199
204
|
});
|
|
200
205
|
// Setup IPC handlers with pool reference
|
|
201
|
-
(0, ipc_handlers_1.setupIPCHandlers)(mainWindow, sessionManager, settingsManager, templatesManager, historyManager, checkpointManager, sessionPool, agentTeamManager, atlasManager, layoutPresetsManager, commandRegistry, modelHistoryManager, gitManager, playbookManager, playbookExecutor);
|
|
206
|
+
(0, ipc_handlers_1.setupIPCHandlers)(mainWindow, sessionManager, settingsManager, templatesManager, historyManager, checkpointManager, sessionPool, agentTeamManager, atlasManager, layoutPresetsManager, commandRegistry, modelHistoryManager, gitManager, playbookManager, playbookExecutor, tunnelManager);
|
|
202
207
|
// Initialize pool (delayed, async)
|
|
203
208
|
setTimeout(() => {
|
|
204
209
|
if (sessionPool) {
|
|
@@ -243,6 +248,10 @@ function createWindow() {
|
|
|
243
248
|
});
|
|
244
249
|
mainWindow.on('closed', () => {
|
|
245
250
|
(0, ipc_handlers_1.removeIPCHandlers)();
|
|
251
|
+
if (tunnelManager) {
|
|
252
|
+
tunnelManager.destroy();
|
|
253
|
+
tunnelManager = null;
|
|
254
|
+
}
|
|
246
255
|
if (modelHistoryManager) {
|
|
247
256
|
modelHistoryManager.shutdown();
|
|
248
257
|
modelHistoryManager = null;
|
|
@@ -43,7 +43,7 @@ const file_dragdrop_handler_1 = require("./file-dragdrop-handler");
|
|
|
43
43
|
const ipc_emitter_1 = require("./ipc-emitter");
|
|
44
44
|
const ipc_registry_1 = require("./ipc-registry");
|
|
45
45
|
let registry = null;
|
|
46
|
-
function setupIPCHandlers(mainWindow, sessionManager, settingsManager, templatesManager, historyManager, checkpointManager, sessionPool, agentTeamManager, atlasManager, layoutPresetsManager, commandRegistry, modelHistoryManager, gitManager, playbookManager, playbookExecutor) {
|
|
46
|
+
function setupIPCHandlers(mainWindow, sessionManager, settingsManager, templatesManager, historyManager, checkpointManager, sessionPool, agentTeamManager, atlasManager, layoutPresetsManager, commandRegistry, modelHistoryManager, gitManager, playbookManager, playbookExecutor, tunnelManager) {
|
|
47
47
|
// Connect managers to window
|
|
48
48
|
sessionManager.setMainWindow(mainWindow);
|
|
49
49
|
checkpointManager.setMainWindow(mainWindow);
|
|
@@ -1028,6 +1028,112 @@ function setupIPCHandlers(mainWindow, sessionManager, settingsManager, templates
|
|
|
1028
1028
|
claudeVersion,
|
|
1029
1029
|
};
|
|
1030
1030
|
});
|
|
1031
|
+
// ── LaunchTunnel ──
|
|
1032
|
+
registry.handle('tunnelList', async () => {
|
|
1033
|
+
try {
|
|
1034
|
+
return await tunnelManager.list();
|
|
1035
|
+
}
|
|
1036
|
+
catch (err) {
|
|
1037
|
+
console.error('Failed to list tunnels:', err);
|
|
1038
|
+
throw err;
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
registry.handle('tunnelCreate', async (_e, request) => {
|
|
1042
|
+
try {
|
|
1043
|
+
return await tunnelManager.create(request);
|
|
1044
|
+
}
|
|
1045
|
+
catch (err) {
|
|
1046
|
+
console.error('Failed to create tunnel:', err);
|
|
1047
|
+
throw err;
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
registry.handle('tunnelStop', async (_e, tunnelId) => {
|
|
1051
|
+
try {
|
|
1052
|
+
return await tunnelManager.stop(tunnelId);
|
|
1053
|
+
}
|
|
1054
|
+
catch (err) {
|
|
1055
|
+
console.error('Failed to stop tunnel:', err);
|
|
1056
|
+
throw err;
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
registry.handle('tunnelGetInfo', async (_e, tunnelId) => {
|
|
1060
|
+
try {
|
|
1061
|
+
return await tunnelManager.getInfo(tunnelId);
|
|
1062
|
+
}
|
|
1063
|
+
catch (err) {
|
|
1064
|
+
console.error('Failed to get tunnel info:', err);
|
|
1065
|
+
throw err;
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
registry.handle('tunnelGetLogs', async (_e, tunnelId, limit) => {
|
|
1069
|
+
try {
|
|
1070
|
+
return await tunnelManager.getLogs(tunnelId, limit);
|
|
1071
|
+
}
|
|
1072
|
+
catch (err) {
|
|
1073
|
+
console.error('Failed to get tunnel logs:', err);
|
|
1074
|
+
throw err;
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
registry.handle('tunnelGetSettings', async () => {
|
|
1078
|
+
return tunnelManager.getSettings();
|
|
1079
|
+
});
|
|
1080
|
+
registry.handle('tunnelUpdateSettings', async (_e, settings) => {
|
|
1081
|
+
try {
|
|
1082
|
+
return tunnelManager.updateSettings(settings);
|
|
1083
|
+
}
|
|
1084
|
+
catch (err) {
|
|
1085
|
+
console.error('Failed to update tunnel settings:', err);
|
|
1086
|
+
throw err;
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
registry.handle('tunnelGetAccount', async () => {
|
|
1090
|
+
try {
|
|
1091
|
+
return await tunnelManager.getAccount();
|
|
1092
|
+
}
|
|
1093
|
+
catch (err) {
|
|
1094
|
+
console.error('Failed to get tunnel account:', err);
|
|
1095
|
+
throw err;
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
registry.handle('tunnelGetUsage', async (_e, tunnelId) => {
|
|
1099
|
+
try {
|
|
1100
|
+
return await tunnelManager.getUsage(tunnelId);
|
|
1101
|
+
}
|
|
1102
|
+
catch (err) {
|
|
1103
|
+
console.error('Failed to get tunnel usage:', err);
|
|
1104
|
+
throw err;
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
registry.handle('tunnelRefresh', async () => {
|
|
1108
|
+
try {
|
|
1109
|
+
return await tunnelManager.refresh();
|
|
1110
|
+
}
|
|
1111
|
+
catch (err) {
|
|
1112
|
+
console.error('Failed to refresh tunnels:', err);
|
|
1113
|
+
throw err;
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
registry.handle('tunnelDetectBinary', async () => {
|
|
1117
|
+
return tunnelManager.detectBinary();
|
|
1118
|
+
});
|
|
1119
|
+
registry.handle('tunnelValidateKey', async (_e, apiKey) => {
|
|
1120
|
+
try {
|
|
1121
|
+
return await tunnelManager.validateKey(apiKey);
|
|
1122
|
+
}
|
|
1123
|
+
catch (err) {
|
|
1124
|
+
console.error('Failed to validate tunnel API key:', err);
|
|
1125
|
+
throw err;
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
registry.handle('tunnelStopAll', async () => {
|
|
1129
|
+
try {
|
|
1130
|
+
return await tunnelManager.stopAll();
|
|
1131
|
+
}
|
|
1132
|
+
catch (err) {
|
|
1133
|
+
console.error('Failed to stop all tunnels:', err);
|
|
1134
|
+
throw err;
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1031
1137
|
// ── Session I/O (send — fire and forget) ──
|
|
1032
1138
|
registry.on('sendSessionInput', (_e, input) => {
|
|
1033
1139
|
sessionManager.sendInput(input.sessionId, input.data);
|
|
@@ -275,12 +275,14 @@ function buildBurnRateResult(rate5h, rate7d, current, history) {
|
|
|
275
275
|
else if (recentRate < olderRate - 0.5)
|
|
276
276
|
trend = 'decreasing';
|
|
277
277
|
}
|
|
278
|
-
// Label based on projected time to limit
|
|
278
|
+
// Label based on projected time to limit (worst of both quotas)
|
|
279
279
|
let label = 'on-track';
|
|
280
|
-
|
|
281
|
-
|
|
280
|
+
const projections = [projectedMinutes5h, projectedMinutes7d].filter((v) => v !== null);
|
|
281
|
+
if (projections.length > 0) {
|
|
282
|
+
const minProjected = Math.min(...projections);
|
|
283
|
+
if (minProjected < 60)
|
|
282
284
|
label = 'critical';
|
|
283
|
-
else if (
|
|
285
|
+
else if (minProjected < 120)
|
|
284
286
|
label = 'elevated';
|
|
285
287
|
}
|
|
286
288
|
return {
|