clideck 1.25.6 → 1.25.8
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 +34 -32
- package/activity.js +3 -1
- package/handlers.js +12 -1
- package/package.json +1 -1
- package/sessions.js +1 -0
- package/telemetry-receiver.js +28 -2
- package/transcript.js +25 -1
- package/workplan.txt +43 -0
package/README.md
CHANGED
|
@@ -1,79 +1,81 @@
|
|
|
1
|
-
|
|
1
|
+
<img src="public/img/clideck-logo-icon.png" width="48" alt="clideck logo">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# clideck
|
|
4
|
+
|
|
5
|
+
> **Formerly `termix-cli`** — if you arrived here from an old link, you're in the right place. The project has been renamed to **CliDeck**. Update your install: `npm install -g clideck`
|
|
6
|
+
|
|
7
|
+
Manage your AI agents like WhatsApp chats.
|
|
4
8
|
|
|
5
9
|
[Documentation](https://docs.clideck.dev/) | [Video Demo](https://youtu.be/hICrtjGAeDk) | [Website](https://clideck.dev/)
|
|
6
10
|
|
|
7
|
-

|
|
8
12
|
|
|
9
|
-
You
|
|
13
|
+
You run Claude Code, Codex, Gemini CLI in separate terminals. You alt-tab between them, forget which one finished, lose sessions when you close the lid.
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
clideck puts all your agents in one screen — a sidebar with every session, live status, last message preview, and timestamps. Click a session, you're in its terminal. Exactly like switching between chats.
|
|
16
|
+
|
|
17
|
+
Native terminals. Your keystrokes go straight to the agent, nothing in between. clideck never reads your prompts or output.
|
|
13
18
|
|
|
14
19
|
## Quick Start
|
|
15
20
|
|
|
16
21
|
```bash
|
|
17
|
-
|
|
18
|
-
clideck
|
|
22
|
+
npx clideck
|
|
19
23
|
```
|
|
20
24
|
|
|
21
25
|
Open [http://localhost:4000](http://localhost:4000). Click **+**, pick an agent, start working.
|
|
22
26
|
|
|
23
|
-
Or
|
|
27
|
+
Or install globally:
|
|
24
28
|
|
|
25
29
|
```bash
|
|
26
|
-
|
|
30
|
+
npm install -g clideck
|
|
31
|
+
clideck
|
|
27
32
|
```
|
|
28
33
|
|
|
29
34
|
## What You Get
|
|
30
35
|
|
|
31
|
-
- **Live status** — see which
|
|
32
|
-
- **Session resume** — close
|
|
33
|
-
- **Notifications** — browser and sound alerts
|
|
36
|
+
- **Live working/idle status** — see which agent is thinking and which is waiting for you, without checking each terminal
|
|
37
|
+
- **Session resume** — close clideck, reopen it tomorrow, pick up where you left off
|
|
38
|
+
- **Notifications** — browser and sound alerts when an agent finishes or needs input
|
|
34
39
|
- **Message previews** — latest output from each agent, right in the sidebar
|
|
35
40
|
- **Projects** — group sessions by project with drag-and-drop
|
|
36
41
|
- **Search** — find any session by name or scroll back through transcript content
|
|
37
|
-
- **Prompt Library** — save reusable prompts
|
|
38
|
-
- **Plugins** — ships with Voice Input and Trim Clip
|
|
39
|
-
- **15 themes** — dark and light
|
|
40
|
-
|
|
42
|
+
- **Prompt Library** — save reusable prompts, type `//` in any terminal to paste them
|
|
43
|
+
- **Plugins** — ships with Voice Input and Trim Clip, or build your own
|
|
44
|
+
- **15 themes** — dark and light, plus custom theme support
|
|
45
|
+
|
|
46
|
+
## Mobile Access
|
|
47
|
+
|
|
48
|
+
Check on your agents from your phone. Start a task, walk away, glance at your phone — see who's done, who's working, who needs input. Pair with one QR scan, no account needed. E2E encrypted — the relay cannot read your code.
|
|
41
49
|
|
|
42
50
|
## Supported Agents
|
|
43
51
|
|
|
44
|
-
|
|
52
|
+
clideck auto-detects whether each agent is working or idle:
|
|
45
53
|
|
|
46
54
|
| Agent | Status detection | Setup |
|
|
47
55
|
|-------|-----------------|-------|
|
|
48
56
|
| **Claude Code** | Automatic | Nothing to configure |
|
|
49
|
-
| **Codex** | Automatic | One-click setup in
|
|
50
|
-
| **Gemini CLI** | Automatic | One-click setup in
|
|
51
|
-
| **OpenCode** | Via plugin bridge | One-click setup in
|
|
57
|
+
| **Codex** | Automatic | One-click setup in clideck |
|
|
58
|
+
| **Gemini CLI** | Automatic | One-click setup in clideck |
|
|
59
|
+
| **OpenCode** | Via plugin bridge | One-click setup in clideck |
|
|
52
60
|
| **Shell** | I/O activity only | None |
|
|
53
61
|
|
|
54
|
-
Claude Code works out of the box. Other agents need a one-time
|
|
62
|
+
Claude Code works out of the box. Other agents need a one-time setup that clideck walks you through.
|
|
55
63
|
|
|
56
64
|
## How It Works
|
|
57
65
|
|
|
58
|
-
Each agent runs in a
|
|
59
|
-
|
|
60
|
-
OpenTelemetry runs locally between the agent and CliDeck. No data is collected, transmitted, or stored outside your machine.
|
|
61
|
-
|
|
62
|
-
## Prompt Library
|
|
63
|
-
|
|
64
|
-
Save prompts you use often and paste them into any terminal session instantly. Open the Prompts panel from the sidebar, click **+** to add a prompt with a name and text.
|
|
66
|
+
Each agent runs in a real terminal (PTY) on your machine. clideck receives lightweight status signals via OpenTelemetry — it knows *that* an agent is working, not *what* it's working on.
|
|
65
67
|
|
|
66
|
-
|
|
68
|
+
Everything runs locally. No data is collected, transmitted, or stored outside your machine.
|
|
67
69
|
|
|
68
70
|
## Platform Support
|
|
69
71
|
|
|
70
|
-
Tested on **macOS** and **Windows**. Works in any modern browser. Linux: untested — if you try it, open an issue
|
|
72
|
+
Tested on **macOS** and **Windows**. Works in any modern browser. Linux: untested — if you try it, open an issue.
|
|
71
73
|
|
|
72
74
|
## Documentation
|
|
73
75
|
|
|
74
76
|
Full setup guides, agent configuration, and plugin development:
|
|
75
77
|
|
|
76
|
-
**[
|
|
78
|
+
**[docs.clideck.dev](https://docs.clideck.dev/)**
|
|
77
79
|
|
|
78
80
|
## Acknowledgments
|
|
79
81
|
|
package/activity.js
CHANGED
|
@@ -56,6 +56,8 @@ function isActive(id) {
|
|
|
56
56
|
return s ? (Date.now() - s.lastOutAt < 2000) : false;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
function lastOutputAt(id) { return stream[id]?.lastOutAt || 0; }
|
|
60
|
+
|
|
59
61
|
function clear(id) { delete net[id]; delete stream[id]; }
|
|
60
62
|
|
|
61
|
-
module.exports = { start, stop, trackIn, trackOut, isActive, clear };
|
|
63
|
+
module.exports = { start, stop, trackIn, trackOut, isActive, lastOutputAt, clear };
|
package/handlers.js
CHANGED
|
@@ -134,10 +134,21 @@ function onConnection(ws) {
|
|
|
134
134
|
plugins.notifyStatus(msg.id, !!msg.working);
|
|
135
135
|
}
|
|
136
136
|
break;
|
|
137
|
-
case 'terminal.buffer':
|
|
137
|
+
case 'terminal.buffer': {
|
|
138
138
|
require('./transcript').storeBuffer(msg.id, msg.lines);
|
|
139
139
|
sessions.broadcast({ type: 'screen.updated', id: msg.id });
|
|
140
|
+
const sess = sessions.getSessions().get(msg.id);
|
|
141
|
+
if (sess) {
|
|
142
|
+
const choices = require('./transcript').detectMenu(msg.lines, sess.presetId);
|
|
143
|
+
const key = choices ? JSON.stringify(choices) : '';
|
|
144
|
+
if (key !== (sess._menuKey || '')) {
|
|
145
|
+
sess._menuKey = key;
|
|
146
|
+
sessions.broadcast({ type: 'session.menu', id: msg.id, choices: choices || [] });
|
|
147
|
+
if (choices) sessions.broadcast({ type: 'session.status', id: msg.id, working: false, source: 'menu' });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
140
150
|
break;
|
|
151
|
+
}
|
|
141
152
|
case 'resize': sessions.resize(msg); break;
|
|
142
153
|
case 'rename': sessions.rename(msg); break;
|
|
143
154
|
case 'close': sessions.close(msg, cfg); break;
|
package/package.json
CHANGED
package/sessions.js
CHANGED
|
@@ -282,6 +282,7 @@ function list() {
|
|
|
282
282
|
id, name: s.name, themeId: s.themeId, commandId: s.commandId, presetId: s.presetId || 'shell', projectId: s.projectId, muted: !!s.muted,
|
|
283
283
|
// Last preview text for sidebar display on reconnect
|
|
284
284
|
lastPreview: s.lastPreview || '', lastActivityAt: s.lastActivityAt || null,
|
|
285
|
+
menu: s._menuKey ? JSON.parse(s._menuKey) : undefined,
|
|
285
286
|
}));
|
|
286
287
|
}
|
|
287
288
|
|
package/telemetry-receiver.js
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// CLI agents export telemetry events here; we capture agent session IDs
|
|
3
3
|
// (for resume) and detect whether telemetry is configured (setup prompts).
|
|
4
4
|
|
|
5
|
+
const ioActivity = require('./activity');
|
|
5
6
|
const activity = new Map(); // sessionId → has received events
|
|
6
7
|
const pendingSetup = new Map(); // sessionId → timer (waiting for first event)
|
|
8
|
+
const pendingIdle = new Map(); // sessionId → timer (api_request → confirm idle after output silence)
|
|
7
9
|
let broadcastFn = null;
|
|
8
10
|
let sessionsFn = null;
|
|
9
11
|
|
|
@@ -69,6 +71,8 @@ function handleLogs(req, res) {
|
|
|
69
71
|
const attrs = parseAttrs(lr.attributes);
|
|
70
72
|
|
|
71
73
|
const eventName = attrs['event.name'];
|
|
74
|
+
// Debug telemetry logs — uncomment as needed, do not delete
|
|
75
|
+
// if (serviceName === 'claude-code' && eventName) console.log(`[telemetry:claude] ${eventName}`);
|
|
72
76
|
// if (serviceName === 'codex_cli_rs' && eventName) console.log(`[telemetry:codex] ${eventName}`);
|
|
73
77
|
// if (serviceName === 'gemini-cli' && eventName) console.log(`[telemetry:gemini] ${eventName}`);
|
|
74
78
|
|
|
@@ -77,11 +81,12 @@ function handleLogs(req, res) {
|
|
|
77
81
|
if (startEvents.has(eventName)) {
|
|
78
82
|
broadcastFn?.({ type: 'session.status', id: resolvedId, working: true, source: 'telemetry' });
|
|
79
83
|
}
|
|
80
|
-
// Claude: telemetry-only status.
|
|
84
|
+
// Claude: telemetry-only status. api_request → pending idle (confirm after 1s output silence, expire after 6s).
|
|
81
85
|
if (serviceName === 'claude-code' && eventName) {
|
|
82
86
|
if (eventName === 'api_request') {
|
|
83
|
-
|
|
87
|
+
startPendingIdle(resolvedId);
|
|
84
88
|
} else if (eventName !== 'user_prompt') {
|
|
89
|
+
cancelPendingIdle(resolvedId);
|
|
85
90
|
broadcastFn?.({ type: 'session.status', id: resolvedId, working: true, source: 'telemetry' });
|
|
86
91
|
}
|
|
87
92
|
}
|
|
@@ -142,8 +147,29 @@ function cancelPendingSetup(sessionId) {
|
|
|
142
147
|
}
|
|
143
148
|
}
|
|
144
149
|
|
|
150
|
+
// Pending idle: api_request starts a check loop. Confirm idle after 1s of output silence. Expire after 6s.
|
|
151
|
+
function startPendingIdle(id) {
|
|
152
|
+
cancelPendingIdle(id);
|
|
153
|
+
const started = Date.now();
|
|
154
|
+
const check = setInterval(() => {
|
|
155
|
+
const elapsed = Date.now() - started;
|
|
156
|
+
if (elapsed > 6000) { cancelPendingIdle(id); return; }
|
|
157
|
+
if (Date.now() - Math.max(started, ioActivity.lastOutputAt(id)) >= 1000) {
|
|
158
|
+
cancelPendingIdle(id);
|
|
159
|
+
broadcastFn?.({ type: 'session.status', id, working: false, source: 'telemetry' });
|
|
160
|
+
}
|
|
161
|
+
}, 250);
|
|
162
|
+
pendingIdle.set(id, check);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function cancelPendingIdle(id) {
|
|
166
|
+
const timer = pendingIdle.get(id);
|
|
167
|
+
if (timer) { clearInterval(timer); pendingIdle.delete(id); }
|
|
168
|
+
}
|
|
169
|
+
|
|
145
170
|
function clear(id) {
|
|
146
171
|
activity.delete(id);
|
|
172
|
+
cancelPendingIdle(id);
|
|
147
173
|
const pending = pendingSetup.get(id);
|
|
148
174
|
if (pending) { clearTimeout(pending.timer); pendingSetup.delete(id); }
|
|
149
175
|
}
|
package/transcript.js
CHANGED
|
@@ -302,4 +302,28 @@ function clear(id) {
|
|
|
302
302
|
try { unlinkSync(join(DIR, `${id}.screen`)); } catch {}
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
|
|
305
|
+
// Detect interactive menus from raw screen lines. Returns [{value, label, selected}] or null.
|
|
306
|
+
// Finds the footer line, then walks upward collecting only the contiguous menu block.
|
|
307
|
+
const MENU_MARKERS = { 'claude-code': /[❯›]/, codex: /[›❯]/, 'gemini-cli': /●/ };
|
|
308
|
+
const MENU_CHOICE_RE = /^\s*(?:[│❯›●•]\s+)*(\d+)\.\s+(.+)$/;
|
|
309
|
+
function detectMenu(lines, presetId) {
|
|
310
|
+
const marker = MENU_MARKERS[presetId];
|
|
311
|
+
if (!marker) return null;
|
|
312
|
+
let footerIdx = -1;
|
|
313
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
314
|
+
if (/\besc\b|\(esc\)/i.test(lines[i])) { footerIdx = MENU_CHOICE_RE.test(lines[i]) ? i + 1 : i; break; }
|
|
315
|
+
}
|
|
316
|
+
if (footerIdx < 0) return null;
|
|
317
|
+
const choices = [];
|
|
318
|
+
for (let i = footerIdx - 1; i >= 0; i--) {
|
|
319
|
+
if (!lines[i].trim() || /^[│\s]+$/.test(lines[i])) continue;
|
|
320
|
+
const m = lines[i].match(MENU_CHOICE_RE);
|
|
321
|
+
if (!m) break;
|
|
322
|
+
if (choices.length && +m[1] >= +choices[0].value) break;
|
|
323
|
+
choices.unshift({ value: m[1], label: m[2].trim(), selected: marker.test(lines[i]) });
|
|
324
|
+
}
|
|
325
|
+
if (!choices.some(c => c.selected)) return null;
|
|
326
|
+
return choices.length ? choices : null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
module.exports = { init, trackInput, trackOutput, storeBuffer, getScreen, getScreenTurns, getLastTurns, getCache, clear, setPrefix, detectMenu };
|
package/workplan.txt
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
CliDeck — Work Plan
|
|
2
|
+
======================
|
|
3
|
+
|
|
4
|
+
## Structured menu events — DONE
|
|
5
|
+
|
|
6
|
+
Moved interactive menu detection from mobile-side parsing to CliDeck desktop.
|
|
7
|
+
Menus detected server-side, broadcast as structured data, rendered on both desktop and mobile.
|
|
8
|
+
|
|
9
|
+
Implementation:
|
|
10
|
+
- transcript.js: detectMenu(lines, presetId) — walks upward from footer/esc anchor,
|
|
11
|
+
collects sequential numbered choices, requires agent-specific marker (❯/›/●) on at least one line.
|
|
12
|
+
Handles footerless Gemini menus via (esc) in choice text.
|
|
13
|
+
- handlers.js: after terminal.buffer capture, runs detectMenu, broadcasts session.menu
|
|
14
|
+
with structured choices. Forces idle when menu detected. Deduplicates via _menuKey on session.
|
|
15
|
+
- sessions.js: list() includes menu state for daemon reconnect.
|
|
16
|
+
- daemon.js: forwards session.menu to mobile, caches per-session menu state,
|
|
17
|
+
sends on subscribe, clears on working status.
|
|
18
|
+
- mobile index.html: renderMenu(choices) replaces 28-line updateChoices parser.
|
|
19
|
+
Menu state comes from session.menu event, no local parsing.
|
|
20
|
+
|
|
21
|
+
## Pending idle for Claude — DONE
|
|
22
|
+
|
|
23
|
+
Claude's api_request no longer flips to idle immediately. Instead:
|
|
24
|
+
- api_request → starts pending idle window (polls every 250ms)
|
|
25
|
+
- Confirm idle: 1s of no PTY output (measured from max(started, lastOutputAt))
|
|
26
|
+
- Cancel: any new telemetry event, or 6s timeout
|
|
27
|
+
- activity.js: exposes lastOutputAt(id) — timestamp of last PTY output per session
|
|
28
|
+
- telemetry-receiver.js: startPendingIdle/cancelPendingIdle manage the window
|
|
29
|
+
|
|
30
|
+
Why: api_request fires between tool calls mid-work, not just at end-of-turn.
|
|
31
|
+
Immediate idle caused false status flickers. The 1s output silence confirms
|
|
32
|
+
the agent is truly waiting for user input.
|
|
33
|
+
|
|
34
|
+
## Future: desktop sidebar menu preview
|
|
35
|
+
|
|
36
|
+
Show "please make a choice" or choice labels in the desktop session list
|
|
37
|
+
when a menu is active. Infrastructure is ready (session.menu broadcast exists).
|
|
38
|
+
|
|
39
|
+
## Future: OpenCode menu support
|
|
40
|
+
|
|
41
|
+
OpenCode not yet in MENU_MARKERS. Need to determine:
|
|
42
|
+
- Does OpenCode expose menu events via bridge plugin API?
|
|
43
|
+
- If not, what marker character does OpenCode use for interactive menus?
|