clideck 1.31.3 → 1.31.4

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 CHANGED
@@ -26,7 +26,7 @@ the main problem with using multiple agents is not starting them. it is managing
26
26
 
27
27
  Terminal multiplexers are great at panes. clideck is about conversations.
28
28
 
29
- 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.
29
+ A pane grid is flat. agent work usually is not. projects, 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.
30
30
 
31
31
  ## Quick start
32
32
 
package/config.js CHANGED
@@ -32,46 +32,6 @@ list your fidings please.`,
32
32
  },
33
33
  ];
34
34
 
35
- const STARTER_ROLES = [
36
- {
37
- id: 'starter-role-programmer',
38
- name: 'Programmer',
39
- instructions: `You are the main programmer of this project.
40
- Do you not apply workarounds or bandaids, prefer pure solutions.
41
- NEVER use plan tool/mode, start to build immediatly and ask questions along the way if any.
42
- Check if any external findings are valid before applying changes, the reviewer doesnt always updated with the full scope.
43
- When you done with changes, list concisely what you did.
44
-
45
- Learn the project quickly if exist. Go over the structure, code and functionality.
46
- Let me know when you are ready.`,
47
- },
48
- {
49
- id: 'starter-role-reviewer',
50
- name: 'Reviewer',
51
- instructions: `You are the code reviewer in this project.
52
- Your task is check the coder output and list critical / logical design flow issues, ugly workarounds or functionalty you just dont understand why its there. Do not waste time and list insignificunt findings.
53
-
54
- If you didnt find anything, response with no findings.
55
-
56
- You never write code!.
57
-
58
- Quickly learn the project if exist. Go over the structure, code and functionality and let me know when you are ready.`,
59
- },
60
- {
61
- id: 'starter-role-product-manager',
62
- name: 'Product manager',
63
- instructions: `You are the product manager of this project, you should understand why we do what we do and what is the best way to do it. You dont care about technical limitations or directions, the only thing matter to you is the user UI/UX and how this agents team will ship a top notch, professional deliveries.
64
- Do not allow the team to round angles and skip small stuff that will basly impact the user.
65
-
66
- You never write code!
67
- You dont use your plan tool/mode - instead you are planning immediatly as you go.
68
-
69
- Go over the project if exist and understand from the code, documentations and readme what is it and why we do it.
70
-
71
- Let me know when you are ready`,
72
- },
73
- ];
74
-
75
35
  const DEFAULTS = {
76
36
  defaultPath: join(os.homedir(), 'Documents'),
77
37
  commands: [
@@ -88,7 +48,6 @@ const DEFAULTS = {
88
48
  defaultTheme: 'catppuccin-mocha',
89
49
  defaultShell,
90
50
  prompts: [],
91
- roles: [],
92
51
  projects: [],
93
52
  };
94
53
 
@@ -185,7 +144,6 @@ function migrate(cfg) {
185
144
  }
186
145
  }
187
146
  if (!cfg.projects) cfg.projects = [];
188
- if (!cfg.roles) cfg.roles = [];
189
147
  return cfg;
190
148
  }
191
149
 
@@ -194,7 +152,6 @@ function load() {
194
152
  return {
195
153
  ...deepCopy(DEFAULTS),
196
154
  prompts: deepCopy(STARTER_PROMPTS),
197
- roles: deepCopy(STARTER_ROLES),
198
155
  };
199
156
  }
200
157
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clideck",
3
- "version": "1.31.3",
3
+ "version": "1.31.4",
4
4
  "description": "One screen for all your AI coding agents — run, monitor, and manage multiple CLI agents from a single browser tab",
5
5
  "main": "server.js",
6
6
  "bin": {
package/plugin-loader.js CHANGED
@@ -207,13 +207,13 @@ function buildApi(pluginId, pluginDir, state) {
207
207
  const s = sessionsFn?.()?.get(id);
208
208
  if (!s) return null;
209
209
  const state = sessionStatus.get(id) || '';
210
- return { id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId, roleName: s.roleName || null, working: state.startsWith('1:') };
210
+ return { id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId, working: state.startsWith('1:') };
211
211
  },
212
212
  getSessions() {
213
213
  const sessions = sessionsFn?.();
214
214
  if (!sessions) return [];
215
215
  return [...sessions].map(([id, s]) => ({
216
- id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId, roleName: s.roleName || null, working: (sessionStatus.get(id) || '').startsWith('1:'),
216
+ id, name: s.name, cwd: s.cwd, commandId: s.commandId, presetId: s.presetId || 'shell', themeId: s.themeId, projectId: s.projectId, working: (sessionStatus.get(id) || '').startsWith('1:'),
217
217
  }));
218
218
  },
219
219
 
@@ -232,7 +232,6 @@ function buildApi(pluginId, pluginDir, state) {
232
232
  inputToSession(id, data) { inputFn?.({ id, data }); },
233
233
  setAutoApproveMenu(id, enabled) { enabled ? autoApproveMenus.add(id) : autoApproveMenus.delete(id); },
234
234
 
235
- getRoles() { return JSON.parse(JSON.stringify(getConfigFn?.()?.roles || [])); },
236
235
  getProjects() { return JSON.parse(JSON.stringify(getConfigFn?.()?.projects || [])); },
237
236
  getTranscript(id, n, order) { return transcript.getTurns(id, n || 20, order || 'end'); },
238
237
  detectMenu(lines, presetId) { return transcript.detectMenu(lines, presetId); },
@@ -88,6 +88,18 @@ export function unregisterAllForPlugin(pluginId) {
88
88
  // Prompt autocomplete (// trigger) runs first, then hotkey dispatch.
89
89
  export function attachToTerminal(term, presetId) {
90
90
  term.attachCustomKeyEventHandler((e) => {
91
+ if (e.type === 'keydown'
92
+ && e.ctrlKey
93
+ && !e.metaKey
94
+ && !e.altKey
95
+ && !e.shiftKey
96
+ && e.code === 'KeyC'
97
+ && term.hasSelection()) {
98
+ e.preventDefault();
99
+ navigator.clipboard?.writeText(term.getSelection()).catch(() => {});
100
+ return false;
101
+ }
102
+
91
103
  // Claude Code uses Shift+Enter for multiline input, but xterm emits the
92
104
  // same "\r" as plain Enter. Scope CSI-u translation to Claude only so
93
105
  // other terminals keep their existing Enter behavior unchanged.
@@ -40,6 +40,50 @@ const MIN_CONTRAST_RATIO = 4.5;
40
40
  const DARK_BALLS = ['#00e5ff', '#5df0d6', '#9b8cff'];
41
41
  const LIGHT_BALLS = ['#0891b2', '#059669', '#7c3aed'];
42
42
 
43
+ const URL_RE = /\bhttps?:\/\/[^\s<>"'`]+/g;
44
+
45
+ function cleanUrlMatch(text, index) {
46
+ let url = text;
47
+ while (/[),.;:!?\\\]}]+$/.test(url)) url = url.slice(0, -1);
48
+ if (!url) return null;
49
+ try {
50
+ const parsed = new URL(url);
51
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return null;
52
+ } catch {
53
+ return null;
54
+ }
55
+ return { text: url, index };
56
+ }
57
+
58
+ function openTerminalLink(url) {
59
+ const win = window.open(url, '_blank', 'noopener,noreferrer');
60
+ if (win) win.opener = null;
61
+ }
62
+
63
+ function addLinkProvider(term) {
64
+ term.registerLinkProvider({
65
+ provideLinks(y, callback) {
66
+ const line = term.buffer.active.getLine(y - 1);
67
+ if (!line) return callback(undefined);
68
+ const text = line.translateToString(true);
69
+ const links = [];
70
+ for (const match of text.matchAll(URL_RE)) {
71
+ const cleaned = cleanUrlMatch(match[0], match.index || 0);
72
+ if (!cleaned) continue;
73
+ links.push({
74
+ text: cleaned.text,
75
+ range: {
76
+ start: { x: cleaned.index + 1, y },
77
+ end: { x: cleaned.index + cleaned.text.length, y },
78
+ },
79
+ activate: (_event, linkText) => openTerminalLink(linkText),
80
+ });
81
+ }
82
+ callback(links.length ? links : undefined);
83
+ },
84
+ });
85
+ }
86
+
43
87
  function startBounce(container) {
44
88
  const isDark = !document.documentElement.classList.contains('light');
45
89
  const colors = isDark ? DARK_BALLS : LIGHT_BALLS;
@@ -386,6 +430,7 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
386
430
  });
387
431
  const fit = new FitAddon.FitAddon();
388
432
  term.loadAddon(fit);
433
+ addLinkProvider(term);
389
434
  term.onData(data => send({ type: 'input', id, data }));
390
435
 
391
436
  // [TRANSCRIPT-CAPTURE] initial settled capture plus one delayed idle save
package/sessions.js CHANGED
@@ -108,29 +108,7 @@ function spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken,
108
108
  if (preset?.telemetryEnv) telemetry.watchSession(id, bin);
109
109
  if (preset?.bridge === 'opencode') opencodeBridge.watchSession(id, cwd);
110
110
 
111
- function injectRolePrompt() {
112
- if (!session.pendingRolePrompt) return;
113
- transcript.recordInjectedInput(id, session.pendingRolePrompt);
114
- term.write(session.pendingRolePrompt);
115
- setTimeout(() => term.write('\r'), 150);
116
- console.log(`Session ${id.slice(0, 8)}: injected role prompt`);
117
- delete session.pendingRolePrompt;
118
- delete session._rolePromptTimer;
119
- }
120
-
121
111
  term.onData((data) => {
122
- // Role prompts should be injected only when the agent is likely ready for
123
- // input. For Codex, use the first OTLP startup event instead of a blind
124
- // fixed startup delay; other agents keep the existing delayed path.
125
- if (session.pendingRolePrompt && !session._rolePromptTimer) {
126
- if (session.presetId === 'codex') {
127
- if (telemetry.hasEvents(id)) injectRolePrompt();
128
- } else {
129
- session._rolePromptTimer = setTimeout(() => {
130
- if (session.pendingRolePrompt) injectRolePrompt();
131
- }, 3000);
132
- }
133
- }
134
112
  session.chunks.push(data);
135
113
  session.chunksSize += data.length;
136
114
  while (session.chunksSize > MAX_BUFFER && session.chunks.length > 1) {
@@ -164,7 +142,6 @@ function spawnSession(id, cmd, parts, cwd, name, themeId, commandId, savedToken,
164
142
  resumable.push({
165
143
  id, name: s.name, commandId: s.commandId, presetId: s.presetId || 'shell', cwd: s.cwd,
166
144
  themeId: s.themeId, sessionToken: s.sessionToken, projectId: s.projectId, muted: !!s.muted,
167
- roleName: s.roleName || null,
168
145
  lastPreview: s.lastPreview || '', lastActivityAt: s.lastActivityAt || null,
169
146
  savedAt: new Date().toISOString(),
170
147
  });
@@ -202,18 +179,6 @@ function create(msg, ws, cfg) {
202
179
  return;
203
180
  }
204
181
 
205
- // If a role was selected, store identity on session and queue prompt injection
206
- if (msg.roleId) {
207
- const role = (cfg.roles || []).find(r => r.id === msg.roleId);
208
- if (role) {
209
- const s = sessions.get(id);
210
- if (s) {
211
- s.roleName = role.name;
212
- if (role.instructions) s.pendingRolePrompt = role.instructions;
213
- }
214
- }
215
- }
216
-
217
182
  const createdPresetId = PRESETS.find(p => binName(p.command) === binName(cmd.command))?.presetId || 'shell';
218
183
  const installId = msg.installId || undefined;
219
184
  broadcast({ type: 'created', id, name, themeId, commandId: cmd.id, presetId: createdPresetId, projectId, installId });
@@ -245,7 +210,6 @@ function createProgrammatic(opts, cfg) {
245
210
  if (err) return { error: err.message };
246
211
 
247
212
  const s = sessions.get(id);
248
- if (s && opts.roleName) s.roleName = opts.roleName;
249
213
  if (s && opts.ephemeral) s.ephemeral = true;
250
214
 
251
215
  const presetId = PRESETS.find(p => binName(p.command) === binName(cmd.command))?.presetId || 'shell';
@@ -292,7 +256,6 @@ function resume(msg, ws, cfg) {
292
256
  const s = sessions.get(id);
293
257
  if (s) {
294
258
  if (saved.muted) s.muted = true;
295
- if (saved.roleName) s.roleName = saved.roleName;
296
259
  }
297
260
 
298
261
  // Remove from resumable list and notify all clients
@@ -386,7 +349,7 @@ function restart(msg, ws, cfg) {
386
349
  }
387
350
 
388
351
  const savedToken = s.sessionToken;
389
- const { name, cwd, commandId, projectId, roleName, muted, lastPreview, lastActivityAt } = s;
352
+ const { name, cwd, commandId, projectId, muted, lastPreview, lastActivityAt } = s;
390
353
 
391
354
  activity.clear(id);
392
355
  telemetry.clear(id);
@@ -405,7 +368,6 @@ function restart(msg, ws, cfg) {
405
368
 
406
369
  const next = sessions.get(id);
407
370
  if (next) {
408
- next.roleName = roleName || null;
409
371
  next.muted = !!muted;
410
372
  next.lastPreview = lastPreview || '';
411
373
  next.lastActivityAt = lastActivityAt || null;
@@ -417,7 +379,6 @@ function restart(msg, ws, cfg) {
417
379
  function list() {
418
380
  return [...sessions].map(([id, s]) => ({
419
381
  id, name: s.name, themeId: s.themeId, commandId: s.commandId, presetId: s.presetId || 'shell', projectId: s.projectId, muted: !!s.muted,
420
- roleName: s.roleName || null,
421
382
  // Last preview text for sidebar display on reconnect
422
383
  lastPreview: s.lastPreview || '', lastActivityAt: s.lastActivityAt || null,
423
384
  menu: s._menuKey ? JSON.parse(s._menuKey) : undefined,
@@ -486,7 +447,6 @@ function saveSessions(cfg) {
486
447
  .map(([id, s]) => ({
487
448
  id, name: s.name, commandId: s.commandId, presetId: s.presetId || 'shell', cwd: s.cwd,
488
449
  themeId: s.themeId, sessionToken: s.sessionToken, projectId: s.projectId, muted: !!s.muted,
489
- roleName: s.roleName || null,
490
450
  lastPreview: s.lastPreview || '', lastActivityAt: s.lastActivityAt || null,
491
451
  savedAt: new Date().toISOString(),
492
452
  }));
@@ -1,112 +0,0 @@
1
- // Roles panel — manage worker role definitions (core feature, not plugin-specific)
2
- import { state, send } from './state.js';
3
- import { esc } from './utils.js';
4
-
5
- const panel = document.getElementById('panel-roles');
6
-
7
- function getRoles() { return state.cfg.roles || []; }
8
-
9
- function save() { send({ type: 'config.update', config: state.cfg }); }
10
-
11
- export function renderRoles() {
12
- const roles = getRoles();
13
- panel.innerHTML = `
14
- <div class="flex items-center justify-between px-3 pt-3 pb-2">
15
- <span class="text-sm font-bold text-slate-200 tracking-tight" style="font-family:'JetBrains Mono',monospace">Roles</span>
16
- <button id="btn-add-role" class="icon-btn w-7 h-7 flex items-center justify-center rounded-md border border-slate-600 text-slate-400 hover:bg-slate-700 hover:text-slate-200 transition-colors text-sm" title="New role">+</button>
17
- </div>
18
- <div id="roles-list" class="tmx-scroll flex-1 overflow-y-auto border-t border-slate-700/50"></div>`;
19
-
20
- panel.querySelector('#btn-add-role').addEventListener('click', () => openEditor());
21
-
22
- const list = panel.querySelector('#roles-list');
23
- list.addEventListener('click', (e) => {
24
- if (e.target.closest('.role-edit')) {
25
- const idx = +e.target.closest('.role-row').dataset.idx;
26
- openEditor(idx);
27
- return;
28
- }
29
- if (e.target.closest('.role-del')) {
30
- const idx = +e.target.closest('.role-row').dataset.idx;
31
- state.cfg.roles.splice(idx, 1);
32
- save();
33
- renderRoles();
34
- return;
35
- }
36
- });
37
-
38
- renderRoleList(roles);
39
- }
40
-
41
- function renderRoleList(roles) {
42
- const list = panel.querySelector('#roles-list');
43
- if (!roles.length) {
44
- list.innerHTML = `<div class="flex flex-col items-center justify-center h-full px-6 text-center">
45
- <p class="text-sm text-slate-400 mb-1">No roles defined</p>
46
- <p class="text-xs text-slate-600 leading-relaxed">Define agent identities with a name and instructions.<br>Roles are sent to the agent when a session starts<br>and can be used by plugins like Autopilot.</p>
47
- </div>`;
48
- return;
49
- }
50
- list.innerHTML = roles.map((r, idx) => `
51
- <div class="role-row group flex items-start gap-2 px-3 py-2.5 cursor-default hover:bg-slate-800/40 transition-colors ${idx > 0 ? 'border-t border-slate-700/30' : ''}" data-idx="${idx}">
52
- <div class="flex-1 min-w-0">
53
- <div class="text-[13px] font-medium text-slate-200 truncate">${esc(r.name)}</div>
54
- <div class="text-[11px] text-slate-500 mt-0.5 line-clamp-2 leading-relaxed">${esc(r.instructions)}</div>
55
- </div>
56
- <div class="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0 mt-0.5">
57
- <button class="role-edit w-6 h-6 flex items-center justify-center rounded text-slate-500 hover:text-slate-300 hover:bg-slate-700/60 transition-colors" title="Edit">
58
- <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path d="M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>
59
- </button>
60
- <button class="role-del w-6 h-6 flex items-center justify-center rounded text-slate-500 hover:text-red-400 hover:bg-slate-700/60 transition-colors" title="Delete">
61
- <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path d="M18 6L6 18M6 6l12 12"/></svg>
62
- </button>
63
- </div>
64
- </div>
65
- `).join('');
66
- }
67
-
68
- function closeEditor() { document.getElementById('role-editor')?.remove(); }
69
-
70
- function openEditor(idx) {
71
- if (document.getElementById('role-editor')) { closeEditor(); if (idx == null) return; }
72
- const existing = idx != null ? getRoles()[idx] : null;
73
-
74
- const card = document.createElement('div');
75
- card.id = 'role-editor';
76
- card.className = 'p-3 border-b border-slate-700/50 bg-slate-800/30';
77
- card.innerHTML = `
78
- <input id="re-name" type="text" maxlength="40" placeholder="Role name" value="${esc(existing?.name || '')}"
79
- class="w-full px-3 py-2 text-sm bg-slate-900 border border-slate-700 rounded-md text-slate-200 placeholder-slate-500 outline-none focus:border-blue-500 transition-colors mb-2">
80
- <textarea id="re-instructions" rows="4" placeholder="Who am I? e.g. You are a senior software architect. You break down goals into clear, actionable tasks with acceptance criteria."
81
- class="w-full max-w-full px-3 py-1.5 text-xs bg-slate-900 border border-slate-700 rounded-md text-slate-200 placeholder-slate-600 outline-none focus:border-blue-500 transition-colors resize-y leading-relaxed font-mono mb-2" style="min-height:5lh">${esc(existing?.instructions || '')}</textarea>
82
- <div class="flex items-center gap-2">
83
- <button id="re-save" class="px-4 py-1.5 text-xs font-medium bg-blue-600 hover:bg-blue-500 text-white rounded-md transition-colors">${existing ? 'Save' : 'Add'}</button>
84
- <button id="re-cancel" class="px-3 py-1.5 text-xs text-slate-500 hover:text-slate-300 transition-colors">Cancel</button>
85
- </div>`;
86
-
87
- const list = panel.querySelector('#roles-list');
88
- list.parentElement.insertBefore(card, list);
89
-
90
- const nameEl = card.querySelector('#re-name');
91
- const instrEl = card.querySelector('#re-instructions');
92
- nameEl.focus();
93
-
94
- const doSave = () => {
95
- const name = nameEl.value.trim();
96
- const instructions = instrEl.value.trim();
97
- if (!name || !instructions) return;
98
- if (!state.cfg.roles) state.cfg.roles = [];
99
- if (idx != null) {
100
- state.cfg.roles[idx] = { ...state.cfg.roles[idx], name, instructions };
101
- } else {
102
- state.cfg.roles.push({ id: crypto.randomUUID(), name, instructions });
103
- }
104
- save();
105
- closeEditor();
106
- renderRoles();
107
- };
108
-
109
- card.querySelector('#re-save').addEventListener('click', doSave);
110
- card.querySelector('#re-cancel').addEventListener('click', closeEditor);
111
- nameEl.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeEditor(); });
112
- }