clideck 1.29.1 → 1.29.3
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/README.md +53 -67
- package/assets/autopilot-original.gif +0 -0
- package/assets/autopilot.gif +0 -0
- package/handlers.js +11 -4
- package/package.json +1 -1
- package/plugins/autopilot/client.js +5 -1
- package/plugins/autopilot/prompt.md +2 -1
- package/public/index.html +3 -3
- package/public/js/app.js +101 -64
- package/transcript-parser.js +27 -0
package/README.md
CHANGED
|
@@ -1,101 +1,87 @@
|
|
|
1
|
-
<
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="public/img/clideck-logo-icon.png" width="64" alt="clideck logo">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">clideck</h1>
|
|
4
6
|
|
|
5
|
-
>
|
|
7
|
+
<p align="center">
|
|
8
|
+
one screen for AI coding agents.
|
|
9
|
+
<br><br>
|
|
10
|
+
<a href="https://clideck.dev">Website</a> · <a href="https://docs.clideck.dev">Docs</a> · <a href="https://youtu.be/hICrtjGAeDk">Demo</a>
|
|
11
|
+
</p>
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://github.com/rustykuntz/clideck/stargazers">
|
|
15
|
+
<img src="https://img.shields.io/github/stars/rustykuntz/clideck?style=social" alt="stars">
|
|
16
|
+
</a>
|
|
17
|
+
<a href="https://www.npmjs.com/package/clideck">
|
|
18
|
+
<img src="https://img.shields.io/npm/v/clideck" alt="npm version">
|
|
19
|
+
</a>
|
|
20
|
+
</p>
|
|
8
21
|
|
|
9
|
-
|
|
22
|
+
<!-- TODO: Replace with a ~10 second GIF showing: open clideck,
|
|
23
|
+
sidebar with multiple agents across projects, click between them,
|
|
24
|
+
one working one idle. No narration needed. -->
|
|
10
25
|
|
|
11
|
-
|
|
26
|
+
<p align="center">
|
|
27
|
+
<img src="assets/clideck-themes.jpg" width="720" alt="clideck dashboard">
|
|
28
|
+
</p>
|
|
12
29
|
|
|
13
|
-
|
|
30
|
+
clideck is a local app for running multiple AI coding agents without juggling terminals. Claude Code, Codex, Gemini CLI, and OpenCode all live in one browser window with a chat-style sidebar, live status, message previews, session resume, and projects to keep things organized. an autopilot routes work between agents automatically, and an E2E encrypted mobile relay gives full control over all agents from a phone.
|
|
14
31
|
|
|
15
|
-
|
|
32
|
+
the main problem with using multiple agents is not starting them. it is managing them. terminals pile up, finished work gets missed, good sessions disappear after a restart. clideck does not sit in the middle rewriting prompts or output - it only watches lightweight status signals (OpenTelemetry) so it can tell which agent is working, which is idle, and which is waiting. everything runs locally, no data leaves your machine.
|
|
16
33
|
|
|
17
|
-
|
|
34
|
+
## Why this exists
|
|
18
35
|
|
|
19
|
-
|
|
36
|
+
Terminal multiplexers are great at panes. clideck is about conversations.
|
|
20
37
|
|
|
21
|
-
|
|
38
|
+
A pane grid is flat. agent work usually is not. projects, roles, previews, timestamps, notifications, resume, and sometimes a bit of routing between specialists all fit more naturally into a chat app layout. it also maps naturally to mobile, so the same mental model works on desktop and phone.
|
|
22
39
|
|
|
23
|
-
|
|
24
|
-
npx clideck
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Open [http://localhost:4000](http://localhost:4000). Click **+**, pick an agent and optionally a project and role, start working.
|
|
28
|
-
|
|
29
|
-
New users get 3 built-in roles (Programmer, Reviewer, Product Manager) and 3 starter prompts in the prompt library.
|
|
30
|
-
|
|
31
|
-
Or install globally:
|
|
40
|
+
## Quick start
|
|
32
41
|
|
|
33
42
|
```bash
|
|
34
43
|
npm install -g clideck
|
|
35
44
|
clideck
|
|
36
45
|
```
|
|
37
46
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- **Roles** — define reusable agent identities (Programmer, Reviewer, PM) and assign them when creating sessions. Instructions are injected into the agent automatically.
|
|
41
|
-
- **Autopilot** — project-level workflow routing. Watches your role-assigned agents, waits for them to finish, forwards output to the next specialist. Fingerprints each output, tracks handoff history, and guards against repeat loops. Supports 8 LLM providers (Anthropic, OpenAI, Google, Groq, xAI, Mistral, OpenRouter, Cerebras). Notifies you when work is complete or blocked.
|
|
42
|
-
- **Mobile access** — check on your agents from your phone with a QR scan. E2E encrypted.
|
|
43
|
-
- **Live working/idle status** — see which agent is thinking and which is waiting for you, without checking each terminal
|
|
44
|
-
- **Session resume** — close clideck, reopen it tomorrow, pick up where you left off
|
|
45
|
-
- **Notifications** — browser and sound alerts when an agent finishes or needs input
|
|
46
|
-
- **Message previews** — latest output from each agent, right in the sidebar
|
|
47
|
-
- **Projects** — group sessions by project with drag-and-drop
|
|
48
|
-
- **Search** — find any session by name or scroll back through transcript content
|
|
49
|
-
- **Prompt Library** — save reusable prompts, type `//` in any terminal to paste them
|
|
50
|
-
- **Plugins** — full server + client API with hooks for input, output, status, transcript, and menus. Programmatic session control, toolbar and project actions, session pills, and a settings UI. Ships with Voice Input, Trim Clip, and Autopilot — or build your own.
|
|
51
|
-
- **15 themes** — dark and light, plus custom theme support
|
|
52
|
-
|
|
53
|
-
## Mobile Access
|
|
54
|
-
|
|
55
|
-
Start a task on your laptop, walk away, check progress from your phone. See who's working, who's idle, who needs input. Send messages, answer choice menus, browse conversation history, and resume sessions — all from the browser on your phone.
|
|
56
|
-
|
|
57
|
-
Pair with one QR scan, no account needed. End-to-end encrypted with AES-256-GCM — the relay sees only opaque blobs. Your code never leaves your machines.
|
|
58
|
-
|
|
59
|
-
Mobile access is provided by [`clideck-remote`](https://www.npmjs.com/package/clideck-remote), a separate optional package. Install it with `npm install -g clideck-remote`.
|
|
47
|
+
Open [localhost:4000](http://localhost:4000). Click **+**, pick an agent, start working.
|
|
60
48
|
|
|
61
|
-
|
|
49
|
+
Or just run it once with `npx clideck`. Works on macOS and Windows. Node 18+. Linux: untested - if you try it, [open an issue](https://github.com/rustykuntz/clideck/issues).
|
|
62
50
|
|
|
63
|
-
|
|
51
|
+
## What makes it useful
|
|
64
52
|
|
|
65
|
-
|
|
66
|
-
|-------|-----------------|-------|
|
|
67
|
-
| **Claude Code** | Automatic | Nothing to configure |
|
|
68
|
-
| **Codex** | Automatic | One-click setup in clideck |
|
|
69
|
-
| **Gemini CLI** | Automatic | One-click setup in clideck |
|
|
70
|
-
| **OpenCode** | Via plugin bridge | One-click setup in clideck |
|
|
71
|
-
| **Shell** | I/O activity only | None |
|
|
53
|
+
**Live status** - see which agent is working and which is waiting. Status detection for Claude Code, Codex, Gemini CLI, and OpenCode.
|
|
72
54
|
|
|
73
|
-
|
|
55
|
+
**Session resume** - close the lid, reopen tomorrow, pick up where things left off. each agent's session ID is captured automatically.
|
|
74
56
|
|
|
75
|
-
|
|
57
|
+
**Roles** - give agents reusable identities like programmer, reviewer, or product manager. prompts are injected automatically when a session starts.
|
|
76
58
|
|
|
77
|
-
-
|
|
78
|
-
- OpenAI Codex `v0.118.0+`
|
|
79
|
-
- Claude Code `v2.1.90+`
|
|
80
|
-
- OpenCode `v1.2.26+`
|
|
59
|
+
**Autopilot** - enable autopilot on a project, walk away. it watches for one agent to finish, hands the output to the next one, and keeps going until the work is done or blocked. this is the part that makes sleep possible. routes content verbatim, no rewriting or summarizing. fingerprints each output and tracks handoff history to guard against repeat loops. ~50 output tokens per routing decision. supports Anthropic, OpenAI, Google, Groq, xAI, Mistral, OpenRouter, Cerebras.
|
|
81
60
|
|
|
82
|
-
|
|
61
|
+
<p align="center">
|
|
62
|
+
<img src="assets/autopilot.gif" width="720" alt="Autopilot routing work between agents">
|
|
63
|
+
</p>
|
|
83
64
|
|
|
84
|
-
|
|
65
|
+
**Mobile remote** - the agents keep running on the local machine. status, prompts, history, and replies stay available from a phone while away. E2E encrypted, no account needed.
|
|
85
66
|
|
|
86
|
-
|
|
67
|
+
**Native terminals** - each session opens into its real terminal. keys go straight to the agent, nothing sits in the middle.
|
|
87
68
|
|
|
88
|
-
|
|
69
|
+
## Supported agents
|
|
89
70
|
|
|
90
|
-
|
|
71
|
+
Claude Code, Codex, Gemini CLI, OpenCode, Shell, and any other terminal tool.
|
|
91
72
|
|
|
92
|
-
|
|
73
|
+
## Also
|
|
93
74
|
|
|
94
|
-
|
|
75
|
+
- **Projects** - group sessions, drag and drop
|
|
76
|
+
- **Prompt library** - save reusable prompts, type `//` to paste
|
|
77
|
+
- **Search** - find sessions or scroll through transcripts
|
|
78
|
+
- **Plugins** - server + client API. ships with Voice Input, Trim Clip, and Autopilot. build your own
|
|
79
|
+
- **15 themes** - dark, light, or make your own
|
|
80
|
+
- **Notifications** - browser + sound alerts when agents finish
|
|
95
81
|
|
|
96
|
-
|
|
82
|
+
## Docs
|
|
97
83
|
|
|
98
|
-
**[docs.clideck.dev](https://docs.clideck.dev
|
|
84
|
+
Guides, agent setup, plugin development: **[docs.clideck.dev](https://docs.clideck.dev)**
|
|
99
85
|
|
|
100
86
|
## Acknowledgments
|
|
101
87
|
|
|
@@ -103,4 +89,4 @@ Built with [xterm.js](https://xtermjs.org/).
|
|
|
103
89
|
|
|
104
90
|
## License
|
|
105
91
|
|
|
106
|
-
MIT
|
|
92
|
+
MIT - see [LICENSE](LICENSE).
|
|
Binary file
|
|
Binary file
|
package/handlers.js
CHANGED
|
@@ -59,19 +59,26 @@ function getInstalledVersion(bin) {
|
|
|
59
59
|
function checkRemoteUpdate(ws) {
|
|
60
60
|
const now = Date.now();
|
|
61
61
|
if (remoteUpdateCache && now - remoteUpdateCheckedAt < REMOTE_UPDATE_INTERVAL) {
|
|
62
|
-
|
|
62
|
+
ws.send(JSON.stringify({ type: 'remote.update', checked: true, ...remoteUpdateCache }));
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
65
|
const shellOpt = process.platform === 'win32';
|
|
66
66
|
require('child_process').execFile('npm', ['list', '-g', 'clideck-remote', '--json', '--depth=0'], { shell: shellOpt, timeout: 10000 }, (err, stdout) => {
|
|
67
67
|
let installed;
|
|
68
|
-
try { installed = JSON.parse(stdout).dependencies['clideck-remote'].version; }
|
|
68
|
+
try { installed = JSON.parse(stdout).dependencies['clideck-remote'].version; }
|
|
69
|
+
catch {
|
|
70
|
+
ws.send(JSON.stringify({ type: 'remote.update', available: false, checked: false }));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
69
73
|
require('child_process').execFile('npm', ['view', 'clideck-remote', 'version'], { shell: shellOpt, timeout: 10000 }, (err2, stdout2) => {
|
|
70
|
-
if (err2)
|
|
74
|
+
if (err2) {
|
|
75
|
+
ws.send(JSON.stringify({ type: 'remote.update', installed, available: false, checked: false }));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
71
78
|
const latest = stdout2.trim();
|
|
72
79
|
remoteUpdateCache = { installed, latest, available: compareVersions(latest, installed) > 0 };
|
|
73
80
|
remoteUpdateCheckedAt = now;
|
|
74
|
-
|
|
81
|
+
ws.send(JSON.stringify({ type: 'remote.update', checked: true, ...remoteUpdateCache }));
|
|
75
82
|
});
|
|
76
83
|
});
|
|
77
84
|
}
|
package/package.json
CHANGED
|
@@ -9,7 +9,11 @@ export function init(pluginApi) {
|
|
|
9
9
|
api.onMessage('started', (msg) => {
|
|
10
10
|
activeProjects.add(msg.projectId);
|
|
11
11
|
updateToggle(msg.projectId, true);
|
|
12
|
-
api.toast('Autopilot
|
|
12
|
+
api.toast('Keep this browser tab active and prevent sleep while Autopilot runs.', {
|
|
13
|
+
title: 'Autopilot started',
|
|
14
|
+
type: 'warn',
|
|
15
|
+
duration: 5000,
|
|
16
|
+
});
|
|
13
17
|
});
|
|
14
18
|
|
|
15
19
|
api.onMessage('stopped', (msg) => {
|
|
@@ -51,10 +51,11 @@ RULES
|
|
|
51
51
|
|
|
52
52
|
DO NOT USE notify_user UNLESS ABSOLUTELY NECESSARY
|
|
53
53
|
- Do NOT ask the user if you should continue. Do NOT notify them with requests like "Please resume agent X" or "Should I keep going?" or "Is this a good stopping point?"
|
|
54
|
+
- Do NOT alert the user that some agent asking for the user input before proceeding, this is not an execuse to stop and ask the user what to do. You should route the work to the next best agent until the workflow is truly blocked and cannot proceed without user input. (e.g. if the programmer ask for the user input, first make sure the reivewer or QA agent has not already reviewed the code, if not route it to them first)
|
|
54
55
|
- The user may be away from the computer and expects the agents to keep working until the task is naturally complete.
|
|
55
56
|
- You are autonomous. If you are unsure how to proceed, re-read the workflow state and the latest agent outputs, think differently, and route again.
|
|
56
57
|
- Repeat agents with the same output if needed, unless the routing state shows that the same handoff is being repeated without progress.
|
|
57
|
-
- You steer between agents until the task is complete or the user interrupts you, period.
|
|
58
|
+
- You steer between agents until the task is complete or the user interrupts you, period.
|
|
58
59
|
|
|
59
60
|
HOW TO THINK
|
|
60
61
|
For each decision, reason in this order:
|
package/public/index.html
CHANGED
|
@@ -368,10 +368,10 @@
|
|
|
368
368
|
<!-- Intro (not installed) -->
|
|
369
369
|
<div id="remote-intro" class="hidden px-6 py-6 flex flex-col items-center gap-4">
|
|
370
370
|
<svg class="w-12 h-12 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
|
|
371
|
-
<h3 class="text-[13px] font-semibold text-slate-200">CliDeck Mobile Remote</h3>
|
|
372
|
-
<p class="text-xs text-slate-400 text-center leading-relaxed">Control your AI agents from your phone. See live status, send messages, and get notifications — all end-to-end encrypted.</p>
|
|
371
|
+
<h3 id="remote-intro-title" class="text-[13px] font-semibold text-slate-200">CliDeck Mobile Remote</h3>
|
|
372
|
+
<p id="remote-intro-text" class="text-xs text-slate-400 text-center leading-relaxed">Control your AI agents from your phone. See live status, send messages, and get notifications — all end-to-end encrypted.</p>
|
|
373
373
|
<button id="remote-add" class="mt-1 w-full px-4 py-2.5 text-[13px] font-medium bg-blue-600 hover:bg-blue-500 text-white rounded-lg transition-colors">Add to CliDeck</button>
|
|
374
|
-
<p class="text-[11px] text-slate-600 text-center">Installs the <code class="text-slate-500">clideck-remote</code> package via npm</p>
|
|
374
|
+
<p id="remote-intro-foot" class="text-[11px] text-slate-600 text-center">Installs the <code class="text-slate-500">clideck-remote</code> package via npm</p>
|
|
375
375
|
</div>
|
|
376
376
|
|
|
377
377
|
<!-- Installing -->
|
package/public/js/app.js
CHANGED
|
@@ -291,7 +291,11 @@ function connect() {
|
|
|
291
291
|
handleInstallDone(msg.success);
|
|
292
292
|
break;
|
|
293
293
|
case 'remote.update':
|
|
294
|
-
|
|
294
|
+
remoteUpdateInfo = msg?.available ? msg : null;
|
|
295
|
+
if (remotePreflight?.pending) {
|
|
296
|
+
remotePreflight.updateSeen = true;
|
|
297
|
+
finishRemotePreflight();
|
|
298
|
+
}
|
|
295
299
|
break;
|
|
296
300
|
default:
|
|
297
301
|
if (msg.type?.startsWith('plugin.')) dispatchPluginMessage(msg);
|
|
@@ -607,7 +611,12 @@ function openPrevSessionsMenu(anchorEl) {
|
|
|
607
611
|
const menu = document.createElement('div');
|
|
608
612
|
menu.className = 'fixed z-[400] min-w-[160px] bg-slate-800 border border-slate-700 rounded-lg shadow-xl shadow-black/40 py-1';
|
|
609
613
|
|
|
610
|
-
|
|
614
|
+
// Clear exactly the dormant sessions currently rendered in "Previous Sessions".
|
|
615
|
+
// This keeps the action aligned with the UI even if a session has a stale projectId
|
|
616
|
+
// that no longer resolves to a real project group.
|
|
617
|
+
const dormantIds = [...document.querySelectorAll('#resumable-section [data-resumable-id]')]
|
|
618
|
+
.map(el => el.dataset.resumableId)
|
|
619
|
+
.filter(Boolean);
|
|
611
620
|
|
|
612
621
|
menu.innerHTML = `
|
|
613
622
|
<button class="pv-action flex items-center gap-2 w-full px-3 py-2 text-sm text-slate-300 hover:bg-slate-700 transition-colors text-left" data-action="clear-dormant">
|
|
@@ -1046,6 +1055,9 @@ let remoteModalOpen = false;
|
|
|
1046
1055
|
let remoteStatusPoll = null;
|
|
1047
1056
|
let remoteConnectedAt = null;
|
|
1048
1057
|
let remoteStatsTimer = null;
|
|
1058
|
+
let remoteUpdateInfo = null;
|
|
1059
|
+
let remotePreflight = null;
|
|
1060
|
+
let remoteLastStatus = null;
|
|
1049
1061
|
|
|
1050
1062
|
function startRemotePoll() {
|
|
1051
1063
|
stopRemotePoll();
|
|
@@ -1065,6 +1077,66 @@ function setRemotePane(pane) {
|
|
|
1065
1077
|
}
|
|
1066
1078
|
}
|
|
1067
1079
|
|
|
1080
|
+
function showRemoteIntro(opts = {}) {
|
|
1081
|
+
const title = document.getElementById('remote-intro-title');
|
|
1082
|
+
const text = document.getElementById('remote-intro-text');
|
|
1083
|
+
const foot = document.getElementById('remote-intro-foot');
|
|
1084
|
+
const btn = document.getElementById('remote-add');
|
|
1085
|
+
title.textContent = opts.title || 'CliDeck Mobile Remote';
|
|
1086
|
+
text.textContent = opts.text || 'Control your AI agents from your phone. See live status, send messages, and get notifications — all end-to-end encrypted.';
|
|
1087
|
+
foot.innerHTML = opts.foot || 'Installs the <code class="text-slate-500">clideck-remote</code> package via npm';
|
|
1088
|
+
btn.textContent = opts.button || 'Add to CliDeck';
|
|
1089
|
+
setRemotePane('intro');
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
function showRemoteUpdateRequired() {
|
|
1093
|
+
showRemoteIntro({
|
|
1094
|
+
title: 'Update Required',
|
|
1095
|
+
text: `Version ${remoteUpdateInfo.latest} is available. Update CliDeck Remote to continue with mobile pairing on this machine.`,
|
|
1096
|
+
foot: `Installed: <code class="text-slate-500">${esc(remoteUpdateInfo.installed)}</code> · Latest: <code class="text-slate-500">${esc(remoteUpdateInfo.latest)}</code>`,
|
|
1097
|
+
button: 'Update to Continue',
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function finishRemotePreflight() {
|
|
1102
|
+
if (!remotePreflight?.pending || !remotePreflight.statusSeen || !remotePreflight.updateSeen) return;
|
|
1103
|
+
remotePreflight = null;
|
|
1104
|
+
if (!remoteInstalled) {
|
|
1105
|
+
showRemoteIntro();
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
if (remoteUpdateInfo?.available) {
|
|
1109
|
+
showRemoteUpdateRequired();
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
if (remoteState === 'idle') {
|
|
1113
|
+
remoteState = 'connecting';
|
|
1114
|
+
setRemotePane('connecting');
|
|
1115
|
+
send({ type: 'remote.pair' });
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
if (remoteState === 'paired' && remoteLastStatus?.paired) {
|
|
1119
|
+
setRemotePane('active');
|
|
1120
|
+
setRemoteLock(true);
|
|
1121
|
+
startRemoteStats(remoteLastStatus.pairedAt);
|
|
1122
|
+
const deviceEl = document.getElementById('remote-device-info');
|
|
1123
|
+
if (deviceEl) {
|
|
1124
|
+
const parts = [remoteLastStatus.deviceName, remoteLastStatus.location].filter(Boolean);
|
|
1125
|
+
deviceEl.textContent = parts.length ? parts.join(' · ') : '';
|
|
1126
|
+
}
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
if (remoteState === 'waiting' && remoteLastStatus?.connected && remoteLastStatus?.url) {
|
|
1130
|
+
document.getElementById('remote-url-box').textContent = remoteLastStatus.url;
|
|
1131
|
+
const qrImg = document.getElementById('remote-qr-img');
|
|
1132
|
+
if (remoteLastStatus.qr && remoteLastStatus.qr.startsWith('data:')) { qrImg.src = remoteLastStatus.qr; qrImg.classList.remove('hidden'); }
|
|
1133
|
+
else qrImg.classList.add('hidden');
|
|
1134
|
+
setRemotePane('qr');
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
setRemotePane(remoteState === 'paired' ? 'active' : remoteState === 'waiting' ? 'qr' : 'connecting');
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1068
1140
|
function openRemoteModal() {
|
|
1069
1141
|
remoteModalOpen = true;
|
|
1070
1142
|
remoteModal.classList.remove('hidden');
|
|
@@ -1153,10 +1225,12 @@ function updateRemoteButton() {
|
|
|
1153
1225
|
}
|
|
1154
1226
|
|
|
1155
1227
|
function handleRemoteStatus(msg) {
|
|
1228
|
+
remoteLastStatus = msg;
|
|
1156
1229
|
remoteInstalled = !!msg.installed;
|
|
1157
1230
|
state.remoteVersion = msg.version || (msg.installed ? null : 'not installed');
|
|
1158
1231
|
updateVersionFooter();
|
|
1159
1232
|
const wasPaired = remoteState === 'paired';
|
|
1233
|
+
const preflighting = !!remotePreflight?.pending;
|
|
1160
1234
|
if (!msg.installed) {
|
|
1161
1235
|
remoteState = 'idle';
|
|
1162
1236
|
stopRemotePoll();
|
|
@@ -1165,7 +1239,7 @@ function handleRemoteStatus(msg) {
|
|
|
1165
1239
|
const wasFresh = remoteState !== 'paired';
|
|
1166
1240
|
remoteState = 'paired';
|
|
1167
1241
|
if (!remoteStatusPoll) startRemotePoll();
|
|
1168
|
-
if (wasFresh) {
|
|
1242
|
+
if (wasFresh && !preflighting) {
|
|
1169
1243
|
|
|
1170
1244
|
setRemotePane('active');
|
|
1171
1245
|
setRemoteLock(true);
|
|
@@ -1185,13 +1259,20 @@ function handleRemoteStatus(msg) {
|
|
|
1185
1259
|
if (msg.qr && msg.qr.startsWith('data:')) { qrImg.src = msg.qr; qrImg.classList.remove('hidden'); }
|
|
1186
1260
|
else qrImg.classList.add('hidden');
|
|
1187
1261
|
startRemotePoll();
|
|
1188
|
-
if (remoteModalOpen) setRemotePane('qr');
|
|
1262
|
+
if (!preflighting && remoteModalOpen) setRemotePane('qr');
|
|
1189
1263
|
} else {
|
|
1190
1264
|
remoteState = 'idle';
|
|
1191
1265
|
stopRemotePoll();
|
|
1192
1266
|
if (wasPaired) { stopRemoteStats(); setRemoteLock(false); }
|
|
1193
1267
|
}
|
|
1268
|
+
if (remoteUpdateInfo?.available && remoteModalOpen) {
|
|
1269
|
+
showRemoteUpdateRequired();
|
|
1270
|
+
}
|
|
1194
1271
|
updateRemoteButton();
|
|
1272
|
+
if (remotePreflight?.pending) {
|
|
1273
|
+
remotePreflight.statusSeen = true;
|
|
1274
|
+
finishRemotePreflight();
|
|
1275
|
+
}
|
|
1195
1276
|
}
|
|
1196
1277
|
|
|
1197
1278
|
function handleRemotePaired(msg) {
|
|
@@ -1201,9 +1282,18 @@ function handleRemotePaired(msg) {
|
|
|
1201
1282
|
const qrImg = document.getElementById('remote-qr-img');
|
|
1202
1283
|
if (msg.qr && msg.qr.startsWith('data:')) { qrImg.src = msg.qr; qrImg.classList.remove('hidden'); }
|
|
1203
1284
|
else qrImg.classList.add('hidden');
|
|
1204
|
-
setRemotePane('qr');
|
|
1205
1285
|
updateRemoteButton();
|
|
1206
1286
|
startRemotePoll();
|
|
1287
|
+
if (remoteUpdateInfo?.available && remoteModalOpen) {
|
|
1288
|
+
showRemoteUpdateRequired();
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
if (remotePreflight?.pending) {
|
|
1292
|
+
remotePreflight.statusSeen = true;
|
|
1293
|
+
finishRemotePreflight();
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
setRemotePane('qr');
|
|
1207
1297
|
}
|
|
1208
1298
|
|
|
1209
1299
|
function handleRemoteUnpaired() {
|
|
@@ -1232,6 +1322,7 @@ function appendInstallLog(text) {
|
|
|
1232
1322
|
function handleInstallDone(success) {
|
|
1233
1323
|
if (success) {
|
|
1234
1324
|
remoteInstalled = true;
|
|
1325
|
+
remoteUpdateInfo = null;
|
|
1235
1326
|
// Installed — go straight to pairing
|
|
1236
1327
|
remoteState = 'connecting';
|
|
1237
1328
|
setRemotePane('connecting');
|
|
@@ -1243,74 +1334,20 @@ function handleInstallDone(success) {
|
|
|
1243
1334
|
}
|
|
1244
1335
|
}
|
|
1245
1336
|
|
|
1246
|
-
let remoteUpdateShown = false;
|
|
1247
|
-
|
|
1248
|
-
function showRemoteUpdateToast(msg) {
|
|
1249
|
-
if (remoteUpdateShown) return;
|
|
1250
|
-
remoteUpdateShown = true;
|
|
1251
|
-
|
|
1252
|
-
const toast = document.createElement('div');
|
|
1253
|
-
toast.className = 'fixed bottom-5 right-5 z-[500] w-[360px] bg-slate-800/95 backdrop-blur-sm border border-slate-700/60 rounded-xl shadow-2xl shadow-black/60';
|
|
1254
|
-
toast.style.cssText = 'opacity:0;transform:translateY(12px);transition:opacity 0.3s ease,transform 0.3s ease';
|
|
1255
|
-
|
|
1256
|
-
toast.innerHTML = `
|
|
1257
|
-
<div class="flex items-center gap-2.5 px-4 pt-3.5 pb-1">
|
|
1258
|
-
<svg class="w-5 h-5 flex-shrink-0 text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>
|
|
1259
|
-
<span class="text-[13px] font-semibold text-slate-200">CliDeck Remote Update</span>
|
|
1260
|
-
<button class="dismiss-btn ml-auto w-6 h-6 flex items-center justify-center rounded-md text-slate-500 hover:text-slate-300 hover:bg-slate-700/50 transition-colors">
|
|
1261
|
-
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
|
1262
|
-
</button>
|
|
1263
|
-
</div>
|
|
1264
|
-
<p class="px-4 pt-1 pb-2.5 text-xs text-slate-400 leading-relaxed">
|
|
1265
|
-
Version <span class="text-slate-300">${esc(msg.latest)}</span> is available (installed: ${esc(msg.installed)}).
|
|
1266
|
-
</p>
|
|
1267
|
-
<div class="px-4 pb-3.5 flex items-center gap-2">
|
|
1268
|
-
<button class="update-btn flex-1 px-3 py-2 text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white rounded-lg transition-colors">Update</button>
|
|
1269
|
-
<button class="dismiss-btn px-3 py-2 text-xs text-slate-500 hover:text-slate-300 transition-colors">Later</button>
|
|
1270
|
-
</div>`;
|
|
1271
|
-
|
|
1272
|
-
const dismiss = () => {
|
|
1273
|
-
toast.style.opacity = '0';
|
|
1274
|
-
toast.style.transform = 'translateY(12px)';
|
|
1275
|
-
setTimeout(() => toast.remove(), 300);
|
|
1276
|
-
};
|
|
1277
|
-
|
|
1278
|
-
toast.querySelectorAll('.dismiss-btn').forEach(b => {
|
|
1279
|
-
b.onclick = () => { dismiss(); setTimeout(() => { remoteUpdateShown = false; }, 600000); };
|
|
1280
|
-
});
|
|
1281
|
-
|
|
1282
|
-
toast.querySelector('.update-btn').onclick = () => {
|
|
1283
|
-
dismiss();
|
|
1284
|
-
remoteUpdateShown = false;
|
|
1285
|
-
document.getElementById('remote-install-log').textContent = '';
|
|
1286
|
-
setRemotePane('installing');
|
|
1287
|
-
openRemoteModal();
|
|
1288
|
-
send({ type: 'remote.install' });
|
|
1289
|
-
};
|
|
1290
|
-
|
|
1291
|
-
document.body.appendChild(toast);
|
|
1292
|
-
requestAnimationFrame(() => { toast.style.opacity = '1'; toast.style.transform = 'translateY(0)'; });
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
1337
|
// Button click
|
|
1296
1338
|
btnRemote.addEventListener('click', () => {
|
|
1297
1339
|
if (remoteModalOpen && remoteState !== 'paired') { closeRemoteModal(); return; }
|
|
1298
1340
|
if (remoteModalOpen) return; // paired — can't dismiss
|
|
1299
1341
|
if (!remoteInstalled) {
|
|
1300
|
-
|
|
1342
|
+
showRemoteIntro();
|
|
1301
1343
|
document.getElementById('remote-install-log').textContent = '';
|
|
1302
1344
|
openRemoteModal();
|
|
1303
1345
|
return;
|
|
1304
1346
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
send({ type: 'remote.pair' });
|
|
1310
|
-
} else {
|
|
1311
|
-
setRemotePane(remoteState === 'paired' ? 'active' : remoteState === 'waiting' ? 'qr' : 'connecting');
|
|
1312
|
-
openRemoteModal();
|
|
1313
|
-
}
|
|
1347
|
+
remotePreflight = { pending: true, statusSeen: false, updateSeen: false };
|
|
1348
|
+
setRemotePane('connecting');
|
|
1349
|
+
openRemoteModal();
|
|
1350
|
+
send({ type: 'remote.status' });
|
|
1314
1351
|
});
|
|
1315
1352
|
|
|
1316
1353
|
// Install button
|
package/transcript-parser.js
CHANGED
|
@@ -4,12 +4,39 @@ function parseTurns(presetId, lines, users) {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
function parseLastAgentOnly(presetId, lines) {
|
|
7
|
+
if (presetId === 'claude-code') return parseLastClaudeAgentOnly(lines);
|
|
7
8
|
const turns = collapseAgentTurns((parsers[presetId] || (() => null))(lines, null));
|
|
8
9
|
if (!turns?.length) return null;
|
|
9
10
|
const last = [...turns].reverse().find(t => t.role === 'agent');
|
|
10
11
|
return last || null;
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
function parseLastClaudeAgentOnly(lines) {
|
|
15
|
+
const userPromptRe = /^(?:[│ ]\s*)?[❯›]\s(.*)$/;
|
|
16
|
+
const agentRe = /^(?:[│ ]\s*)?[⏺•●]\s(.*)$/;
|
|
17
|
+
let promptIdx = -1;
|
|
18
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
19
|
+
if (userPromptRe.test(lines[i])) { promptIdx = i; break; }
|
|
20
|
+
}
|
|
21
|
+
const upperBound = promptIdx >= 0 ? promptIdx : lines.length;
|
|
22
|
+
let start = -1;
|
|
23
|
+
for (let i = upperBound - 1; i >= 0; i--) {
|
|
24
|
+
if (agentRe.test(lines[i])) { start = i; break; }
|
|
25
|
+
}
|
|
26
|
+
if (start < 0) return null;
|
|
27
|
+
const first = lines[start].match(agentRe);
|
|
28
|
+
const turn = { role: 'agent', text: first[1] };
|
|
29
|
+
for (let i = start + 1; i < upperBound; i++) {
|
|
30
|
+
if (userPromptRe.test(lines[i]) || agentRe.test(lines[i])) break;
|
|
31
|
+
let cont = lines[i];
|
|
32
|
+
if (cont.startsWith('│ ')) cont = cont.slice(2);
|
|
33
|
+
else if (cont.startsWith(' ')) cont = cont.slice(2);
|
|
34
|
+
turn.text += '\n' + cont;
|
|
35
|
+
}
|
|
36
|
+
turn.text = turn.text.replace(/\n+$/, '');
|
|
37
|
+
return turn;
|
|
38
|
+
}
|
|
39
|
+
|
|
13
40
|
const parsers = {
|
|
14
41
|
'claude-code': (lines, users) => {
|
|
15
42
|
const known = users?.length ? new Set(users) : null;
|