clideck 1.22.2 → 1.22.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
@@ -70,7 +70,7 @@ Tested on **macOS** and **Windows**. Works in any modern browser. Linux: unteste
70
70
 
71
71
  Full setup guides, agent configuration, and plugin development:
72
72
 
73
- **[Documentation](https://clideck.dev/)**
73
+ **[Documentation](https://docs.clideck.dev/)**
74
74
 
75
75
  ## License
76
76
 
@@ -4,6 +4,7 @@
4
4
  "name": "Claude Code",
5
5
  "icon": "/img/claude-code.png",
6
6
  "command": "claude",
7
+ "installCmd": "npm install -g @anthropic-ai/claude-code",
7
8
  "isAgent": true,
8
9
  "canResume": true,
9
10
  "resumeCommand": "claude --resume {{sessionId}}",
@@ -23,6 +24,7 @@
23
24
  "name": "Codex",
24
25
  "icon": "/img/codex.png",
25
26
  "command": "codex --no-alt-screen",
27
+ "installCmd": "npm install -g @openai/codex",
26
28
  "isAgent": true,
27
29
  "canResume": true,
28
30
  "resumeCommand": "codex resume {{sessionId}} --no-alt-screen",
@@ -45,6 +47,7 @@
45
47
  "name": "Gemini CLI",
46
48
  "icon": "/img/gemini.png",
47
49
  "command": "gemini",
50
+ "installCmd": "npm install -g @google/gemini-cli",
48
51
  "isAgent": true,
49
52
  "canResume": true,
50
53
  "resumeCommand": "gemini --resume {{sessionId}}",
@@ -67,6 +70,7 @@
67
70
  "name": "OpenCode",
68
71
  "icon": "/img/opencode.png",
69
72
  "command": "opencode",
73
+ "installCmd": "npm install -g opencode-ai",
70
74
  "isAgent": true,
71
75
  "canResume": true,
72
76
  "resumeCommand": "opencode --session {{sessionId}}",
package/handlers.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const { readFileSync, writeFileSync, mkdirSync, existsSync, copyFileSync, unlinkSync } = require('fs');
2
2
  const { join, dirname } = require('path');
3
+ const { execFileSync } = require('child_process');
3
4
  const os = require('os');
4
5
  const config = require('./config');
5
6
  const sessions = require('./sessions');
@@ -10,6 +11,17 @@ for (const p of presets) if (p.presetId === 'shell') p.command = defaultShell;
10
11
  const transcript = require('./transcript');
11
12
  const plugins = require('./plugin-loader');
12
13
 
14
+ // Check which agent binaries are available on PATH
15
+ const whichCmd = process.platform === 'win32' ? 'where' : 'which';
16
+ function checkAvailability() {
17
+ for (const p of presets) {
18
+ if (p.presetId === 'shell') { p.available = true; continue; }
19
+ try { execFileSync(whichCmd, [binName(p.command)], { stdio: 'ignore' }); p.available = true; }
20
+ catch { p.available = false; }
21
+ }
22
+ }
23
+ checkAvailability();
24
+
13
25
  let cfg = config.load();
14
26
  if (detectTelemetryConfig(cfg)) config.save(cfg);
15
27
 
@@ -80,6 +92,11 @@ function onConnection(ws) {
80
92
  ws.send(JSON.stringify({ type: 'config', config: cfg }));
81
93
  break;
82
94
 
95
+ case 'checkAvailability':
96
+ checkAvailability();
97
+ ws.send(JSON.stringify({ type: 'presets', presets }));
98
+ break;
99
+
83
100
  case 'config.update':
84
101
  cfg = { ...cfg, ...msg.config };
85
102
  detectTelemetryConfig(cfg);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clideck",
3
- "version": "1.22.2",
3
+ "version": "1.22.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": {
Binary file
Binary file
Binary file
Binary file
Binary file
package/public/index.html CHANGED
@@ -6,6 +6,45 @@
6
6
  <link rel="icon" type="image/png" href="/img/clideck-logo-icon.png">
7
7
  <link rel="stylesheet" href="/xterm.css">
8
8
  <link rel="stylesheet" href="/tailwind.css">
9
+ <style>
10
+ :root {
11
+ --color-project-header-bg: #191c21;
12
+ --color-unread-pill-bg: rgba(59, 130, 246, 0.15);
13
+ --color-filter-unread-bg: transparent;
14
+ --color-list-row-hover: color-mix(in srgb, var(--color-chat-hover) 55%, transparent);
15
+ --color-list-row-active: var(--color-chat-active);
16
+ --color-session-icon-bg: #242626;
17
+ }
18
+ .light {
19
+ --color-project-header-bg: #f6f5f5;
20
+ --color-unread-pill-bg: #f6f5f5;
21
+ --color-filter-unread-bg: #f6f5f5;
22
+ --color-session-icon-bg: #f7f5f3;
23
+ }
24
+ .session-row,
25
+ .resumable-row {
26
+ margin: 2px 8px;
27
+ border-radius: 10px;
28
+ transition: background-color 0.18s ease;
29
+ }
30
+ .session-row:hover,
31
+ .resumable-row:hover {
32
+ background: var(--color-list-row-hover) !important;
33
+ }
34
+ .session-row.active-session {
35
+ background: var(--color-list-row-active) !important;
36
+ }
37
+ .project-group {
38
+ margin: 8px 0 4px;
39
+ }
40
+ .project-group:first-child {
41
+ margin-top: 4px;
42
+ }
43
+ .project-header {
44
+ margin: 0 8px 4px;
45
+ border-radius: 10px;
46
+ }
47
+ </style>
9
48
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
49
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
50
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@700&display=swap" rel="stylesheet">
@@ -39,7 +78,7 @@
39
78
  <!-- Chats panel -->
40
79
  <div id="panel-chats" class="flex flex-col flex-1 min-h-0">
41
80
  <div class="flex items-center justify-between px-3 pt-3 pb-2">
42
- <span class="text-sm font-bold text-slate-200 tracking-tight" style="font-family:'JetBrains Mono',monospace">CliDeck</span>
81
+ <span class="text-sm font-bold tracking-tight" style="font-family:'JetBrains Mono',monospace"><span style="color:var(--color-text)">cli</span><span style="color:var(--color-dim)">deck</span></span>
43
82
  <div class="flex items-center gap-1">
44
83
  <span id="save-indicator" class="save-indicator" title="Sessions saved">
45
84
  <svg class="save-tick" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
@@ -58,9 +97,9 @@
58
97
  </div>
59
98
  <div class="flex bg-slate-800/30 rounded-lg p-[3px]">
60
99
  <button class="filter-tab flex-1 text-[11px] font-medium py-[5px] rounded-md transition-all bg-slate-700/60 text-slate-200" data-tab="all">All</button>
61
- <button class="filter-tab flex-1 text-[11px] font-medium py-[5px] rounded-md transition-all text-slate-500 hover:text-slate-400 flex items-center justify-center gap-1" data-tab="unread">
100
+ <button class="filter-tab flex-1 text-[11px] font-medium py-[5px] rounded-md transition-all text-slate-500 hover:text-slate-400 flex items-center justify-center gap-1" data-tab="unread" style="background:var(--color-filter-unread-bg)">
62
101
  Unread
63
- <span id="unread-badge" class="hidden min-w-[16px] h-4 px-1 rounded-full bg-blue-500/15 text-blue-400 text-[10px] font-semibold inline-flex items-center justify-center">0</span>
102
+ <span id="unread-badge" class="hidden min-w-[16px] h-4 px-1 rounded-full text-blue-400 text-[10px] font-semibold inline-flex items-center justify-center" style="background:var(--color-unread-pill-bg)">0</span>
64
103
  </button>
65
104
  </div>
66
105
  </div>
package/public/js/app.js CHANGED
@@ -2,7 +2,7 @@ import { state, send } from './state.js';
2
2
  import { esc, binName } from './utils.js';
3
3
  import { addTerminal, removeTerminal, select, startRename, startProjectRename, setSessionTheme, openMenu, closeMenu, setStatus, updateMuteIndicator, updatePreview, markUnread, applyFilter, setTab, renderResumable, regroupSessions, toggleProjectCollapse, setSessionProject, estimateSize, restartComplete, positionMenu } from './terminals.js';
4
4
  import { renderSettings } from './settings.js';
5
- import { openCreator, closeCreator } from './creator.js';
5
+ import { openCreator, closeCreator, refreshCreator } from './creator.js';
6
6
  import { handleDirsResponse, openFolderPicker } from './folder-picker.js';
7
7
  import { confirmClose } from './confirm.js';
8
8
  import { applyTheme } from './profiles.js';
@@ -42,6 +42,7 @@ function connect() {
42
42
  case 'presets':
43
43
  state.presets = msg.presets;
44
44
  renderSettings();
45
+ refreshCreator();
45
46
  break;
46
47
  case 'sessions.resumable':
47
48
  state.resumable = msg.list;
@@ -385,6 +386,18 @@ function showTelemetrySetup(commandId, sessionId) {
385
386
 
386
387
  // --- Project context menu ---
387
388
  let projectMenuCleanup = null;
389
+
390
+ function resumeDormantSessions(ids, label) {
391
+ const uniqueIds = [...new Set(ids)].filter(Boolean);
392
+ if (!uniqueIds.length) return;
393
+ showToast(`Starting ${uniqueIds.length} dormant session${uniqueIds.length > 1 ? 's' : ''}${label ? ` from ${label}` : ''}…`, { duration: 3000 });
394
+ uniqueIds.forEach((id, index) => {
395
+ setTimeout(() => {
396
+ if (state.resumable.some(s => s.id === id)) send({ type: 'session.resume', id });
397
+ }, index * 1000);
398
+ });
399
+ }
400
+
388
401
  function openProjectMenu(projectId, anchorEl) {
389
402
  if (projectMenuCleanup) projectMenuCleanup();
390
403
  const proj = (state.cfg.projects || []).find(p => p.id === projectId);
@@ -407,6 +420,10 @@ function openProjectMenu(projectId, anchorEl) {
407
420
  <svg class="w-4 h-4 flex-shrink-0 text-slate-400" 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>
408
421
  Rename
409
422
  </button>
423
+ <button class="pm-action flex items-center gap-2 w-full px-3 py-2 text-sm ${hasDormant ? 'text-slate-300 hover:bg-slate-700 cursor-pointer' : 'text-slate-600 cursor-default'} transition-colors text-left" data-action="start-dormant" ${hasDormant ? '' : 'disabled'}>
424
+ <svg class="w-4 h-4 flex-shrink-0 ${hasDormant ? 'text-slate-400' : 'text-slate-600'}" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="7 5 19 12 7 19 7 5"/></svg>
425
+ Start all dormant sessions
426
+ </button>
410
427
  <button class="pm-action flex items-center gap-2 w-full px-3 py-2 text-sm ${hasDormant ? 'text-slate-300 hover:bg-slate-700 cursor-pointer' : 'text-slate-600 cursor-default'} transition-colors text-left" data-action="clear-dormant" ${hasDormant ? '' : 'disabled'}>
411
428
  <svg class="w-4 h-4 flex-shrink-0 ${hasDormant ? 'text-slate-400' : 'text-slate-600'}" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 4H8l-7 8 7 8h13a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z"/><line x1="18" y1="9" x2="12" y2="15"/><line x1="12" y1="9" x2="18" y2="15"/></svg>
412
429
  Clear dormant sessions
@@ -433,6 +450,13 @@ function openProjectMenu(projectId, anchorEl) {
433
450
  startProjectRename(projectId);
434
451
  return;
435
452
  }
453
+ if (btn.dataset.action === 'start-dormant') {
454
+ const ids = [...document.querySelectorAll(`.project-group[data-project-id="${projectId}"] .project-sessions [data-resumable-id]`)]
455
+ .map(el => el.dataset.resumableId);
456
+ if (!ids.length) return;
457
+ resumeDormantSessions(ids, `"${proj?.name || 'project'}"`);
458
+ return;
459
+ }
436
460
  if (btn.dataset.action === 'clear-dormant') {
437
461
  const ids = state.resumable.filter(s => s.projectId === projectId).map(s => s.id);
438
462
  if (!ids.length) return;
@@ -2,6 +2,7 @@ import { state, send } from './state.js';
2
2
  import { esc, agentIcon, binName } from './utils.js';
3
3
  import { openFolderPicker } from './folder-picker.js';
4
4
  import { estimateSize } from './terminals.js';
5
+ import { showToast } from './toast.js';
5
6
 
6
7
  const ADJECTIVES = [
7
8
  'Blue', 'Red', 'Green', 'Purple', 'Golden', 'Silver', 'Coral', 'Amber',
@@ -76,6 +77,8 @@ export function openCreator() {
76
77
  // Close project creator if open
77
78
  document.getElementById('project-creator')?.remove();
78
79
  if (!state.presets.length) return;
80
+ // Recheck binary availability on the server
81
+ send({ type: 'checkAvailability' });
79
82
 
80
83
  const fallbackName = randomName();
81
84
  const presets = sortedPresets();
@@ -101,11 +104,18 @@ export function openCreator() {
101
104
  </button>
102
105
  </div>
103
106
  <div class="space-y-0.5">
104
- ${presets.map(p => `
105
- <button class="preset-btn w-full flex items-center gap-2.5 px-3 py-2 rounded-md hover:bg-slate-700/70 text-sm text-slate-300 transition-colors text-left" data-preset="${p.presetId}">
106
- ${agentIcon(p.icon, 24)}
107
- <span>${esc(p.name)}</span>
108
- </button>`).join('')}
107
+ ${presets.map(p => {
108
+ const hasConfigured = state.cfg.commands.some(c => binName(c.command) === binName(p.command) && c.enabled !== false);
109
+ const missing = p.available === false && !hasConfigured;
110
+ return `
111
+ <button class="preset-btn w-full flex items-center gap-2.5 px-3 py-2 rounded-md hover:bg-slate-700/70 text-sm transition-colors text-left ${missing ? 'text-slate-500' : 'text-slate-300'}" data-preset="${p.presetId}">
112
+ <span class="${missing ? 'opacity-40' : ''}">${agentIcon(p.icon, 24)}</span>
113
+ <span class="flex-1 min-w-0">
114
+ <span>${esc(p.name)}</span>
115
+ ${missing ? `<span class="block text-[10px] text-slate-600 truncate">${esc(p.installCmd || 'Not installed')}</span>` : ''}
116
+ </span>
117
+ </button>`;
118
+ }).join('')}
109
119
  </div>`;
110
120
 
111
121
  const list = document.getElementById('session-list');
@@ -187,6 +197,14 @@ export function openCreator() {
187
197
  if (!btn) return;
188
198
  const preset = state.presets.find(p => p.presetId === btn.dataset.preset);
189
199
  if (!preset) return;
200
+ const hasConfigured = state.cfg.commands.some(c => binName(c.command) === binName(preset.command) && c.enabled !== false);
201
+ if (preset.available === false && !hasConfigured && preset.installCmd) {
202
+ navigator.clipboard.writeText(preset.installCmd).then(
203
+ () => showToast(`Install command copied: <code class="text-slate-200">${esc(preset.installCmd)}</code>`, { html: true, duration: 4000 }),
204
+ () => showToast(`Run: ${preset.installCmd}`, { duration: 4000 }),
205
+ );
206
+ return;
207
+ }
190
208
  const name = nameInput.value.trim() || fallbackName;
191
209
  const cwd = cwdInput.value.trim() || undefined;
192
210
  const projectSelect = card.querySelector('#creator-project');
@@ -196,6 +214,13 @@ export function openCreator() {
196
214
  });
197
215
  }
198
216
 
217
+ export function refreshCreator() {
218
+ if (document.getElementById('session-creator')) {
219
+ closeCreator();
220
+ openCreator();
221
+ }
222
+ }
223
+
199
224
  export function closeCreator() {
200
225
  document.getElementById('session-creator')?.remove();
201
226
  }
@@ -1,5 +1,5 @@
1
1
  import { state, send } from './state.js';
2
- import { esc } from './utils.js';
2
+ import { esc, resolveIconPath } from './utils.js';
3
3
  import { resolveTheme, resolveAccent, applyTheme } from './profiles.js';
4
4
  import { attachToTerminal } from './hotkeys.js';
5
5
  import { closeDropdown } from './prompts.js';
@@ -97,7 +97,7 @@ function startBounce(container) {
97
97
  function iconHtml(commandId) {
98
98
  const icon = state.cfg.commands.find(c => c.id === commandId)?.icon || 'terminal';
99
99
  if (icon.startsWith('/'))
100
- return `<img src="${esc(icon)}" class="w-5 h-5 object-contain" draggable="false">`;
100
+ return `<img src="${esc(resolveIconPath(icon))}" class="w-5 h-5 object-contain" draggable="false">`;
101
101
  return TERMINAL_SVG;
102
102
  }
103
103
 
@@ -290,10 +290,10 @@ export function addTerminal(id, name, themeId, commandId, projectId, muted, last
290
290
  themeId = themeId || state.cfg.defaultTheme || 'default';
291
291
 
292
292
  const item = document.createElement('div');
293
- item.className = 'group flex items-center gap-2 px-2.5 py-2 cursor-pointer transition-colors select-none';
293
+ item.className = 'group session-row flex items-center gap-2 px-2.5 py-2 cursor-pointer transition-colors select-none';
294
294
  item.dataset.id = id;
295
295
  item.innerHTML = `
296
- <div class="session-icon w-8 h-8 rounded-full bg-slate-800 flex items-center justify-center flex-shrink-0 overflow-hidden pointer-events-none">
296
+ <div class="session-icon w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 overflow-hidden pointer-events-none" style="background:var(--color-session-icon-bg)">
297
297
  ${iconHtml(commandId)}
298
298
  </div>
299
299
  <div class="flex-1 min-w-0 pointer-events-none">
@@ -698,7 +698,7 @@ export function regroupSessions() {
698
698
 
699
699
  const collapsed = proj.collapsed;
700
700
  header.innerHTML = `
701
- <div class="group project-header flex items-center gap-1.5 px-2.5 py-1.5 cursor-pointer hover:bg-slate-800/30 transition-colors select-none" data-project-id="${proj.id}">
701
+ <div class="group project-header flex items-center gap-1.5 px-2.5 py-1.5 cursor-pointer hover:bg-slate-800/30 transition-colors select-none" data-project-id="${proj.id}" style="background:var(--color-project-header-bg)">
702
702
  <span class="project-chevron ${collapsed ? 'collapsed' : ''} text-slate-500">${CHEVRON_SVG}</span>
703
703
  <span class="w-2 h-2 rounded-full flex-shrink-0" style="background:${projectColor(proj)}"></span>
704
704
  <span class="project-name flex-1 text-[11px] font-semibold uppercase tracking-wider text-slate-500 truncate">${esc(proj.name)}</span>
@@ -797,7 +797,7 @@ function buildResumableRow(s) {
797
797
  row.className = 'group resumable-row flex items-center gap-2 px-2.5 py-2 cursor-pointer hover:bg-slate-800/30 transition-colors';
798
798
  row.dataset.resumableId = s.id;
799
799
  row.innerHTML = `
800
- <div class="w-8 h-8 rounded-full bg-slate-800/50 flex items-center justify-center flex-shrink-0 overflow-hidden opacity-40">
800
+ <div class="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 overflow-hidden opacity-40" style="background:var(--color-session-icon-bg)">
801
801
  ${iconHtml(s.commandId)}
802
802
  </div>
803
803
  <div class="flex-1 min-w-0">
@@ -807,7 +807,7 @@ function buildResumableRow(s) {
807
807
  </div>
808
808
  <div class="flex items-center gap-1 mt-0.5">
809
809
  <span class="flex-1 text-xs text-slate-600 truncate">${s.lastPreview ? esc(s.lastPreview) : esc(label) + (path ? ' · ' + esc(path) : '')}</span>
810
- <button class="resume-btn opacity-0 group-hover:opacity-100 text-slate-600 hover:text-emerald-400 flex-shrink-0 transition-all flex items-center gap-0.5 text-[11px] font-medium" title="Resume session">
810
+ <button class="resume-btn opacity-60 group-hover:opacity-100 text-slate-500 hover:text-emerald-400 flex-shrink-0 transition-all flex items-center gap-0.5 text-[11px] font-medium" title="Resume session">
811
811
  Resume${RESUME_SVG}
812
812
  </button>
813
813
  </div>
@@ -875,6 +875,7 @@ export function setTab(tab) {
875
875
  const base = 'filter-tab flex-1 text-[11px] font-medium py-[5px] rounded-md transition-all';
876
876
  const extra = btn.dataset.tab === 'unread' ? ' flex items-center justify-center gap-1' : '';
877
877
  btn.className = base + extra + (active ? ' bg-slate-700/60 text-slate-200' : ' text-slate-500 hover:text-slate-400');
878
+ btn.style.background = !active && btn.dataset.tab === 'unread' ? 'var(--color-filter-unread-bg)' : '';
878
879
  });
879
880
  applyFilter();
880
881
  }
@@ -15,10 +15,26 @@ export function debounce(fn, ms) {
15
15
 
16
16
  const TERMINAL_SVG = `<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>`;
17
17
 
18
+ const ICON_VARIANTS = {
19
+ '/img/claude-code.png': { all: '/img/claude-all.png' },
20
+ '/img/codex.png': { dark: '/img/codex-dark.png', light: '/img/codex-light.png' },
21
+ '/img/gemini.png': { all: '/img/gemini-all.png' },
22
+ '/img/opencode.png': { all: '/img/opencode-all.png' },
23
+ };
24
+
25
+ export function resolveIconPath(icon) {
26
+ if (!icon || !icon.startsWith('/')) return icon;
27
+ const canonical = icon.replace(/-(light|dark|all)(?=\.[a-z]+$)/, '');
28
+ const variants = ICON_VARIANTS[canonical];
29
+ if (!variants) return icon;
30
+ const isLight = document.documentElement.classList.contains('light');
31
+ return (isLight ? variants.light : variants.dark) || variants.all || icon;
32
+ }
33
+
18
34
  export function agentIcon(icon, px = 32) {
19
35
  const s = `width:${px}px;height:${px}px`;
20
36
  if (icon && icon.startsWith('/')) {
21
- return `<img src="${esc(icon)}" style="${s}" class="rounded object-cover flex-shrink-0" alt="">`;
37
+ return `<img src="${esc(resolveIconPath(icon))}" style="${s}" class="rounded object-cover flex-shrink-0" alt="">`;
22
38
  }
23
39
  if (icon === 'terminal') {
24
40
  return `<div style="${s}" class="rounded bg-slate-700 flex items-center justify-center text-slate-400 flex-shrink-0">${TERMINAL_SVG}</div>`;
package/server.js CHANGED
@@ -107,16 +107,16 @@ server.listen(PORT, '127.0.0.1', () => {
107
107
  const v = require('./package.json').version;
108
108
  const url = `http://localhost:${PORT}`;
109
109
  console.log(`
110
- \x1b[38;5;141m ╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╸\x1b[0m
110
+ \x1b[38;5;105m ╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╸\x1b[0m
111
111
 
112
- \x1b[38;5;141m ██████╗\x1b[38;5;105m██╗ \x1b[38;5;69m██╗\x1b[38;5;33m██████╗ \x1b[38;5;38m███████╗\x1b[38;5;44m ██████╗\x1b[38;5;50m██╗ ██╗\x1b[0m
113
- \x1b[38;5;141m ██╔════╝\x1b[38;5;105m██║ \x1b[38;5;69m██║\x1b[38;5;33m██╔══██╗\x1b[38;5;38m██╔════╝\x1b[38;5;44m██╔════╝\x1b[38;5;50m██║ ██╔╝\x1b[0m
114
- \x1b[38;5;141m ██║ \x1b[38;5;105m██║ \x1b[38;5;69m██║\x1b[38;5;33m██║ ██║\x1b[38;5;38m█████╗ \x1b[38;5;44m██║ \x1b[38;5;50m█████╔╝ \x1b[0m
115
- \x1b[38;5;141m ██║ \x1b[38;5;105m██║ \x1b[38;5;69m██║\x1b[38;5;33m██║ ██║\x1b[38;5;38m██╔══╝ \x1b[38;5;44m██║ \x1b[38;5;50m██╔═██╗ \x1b[0m
116
- \x1b[38;5;141m ╚██████╗\x1b[38;5;105m███████╗\x1b[38;5;69m██║\x1b[38;5;33m██████╔╝\x1b[38;5;38m███████╗\x1b[38;5;44m╚██████╗\x1b[38;5;50m██║ ██╗\x1b[0m
117
- \x1b[38;5;141m ╚═════╝\x1b[38;5;105m╚══════╝\x1b[38;5;69m╚═╝\x1b[38;5;33m╚═════╝ \x1b[38;5;38m╚══════╝\x1b[38;5;44m ╚═════╝\x1b[38;5;50m╚═╝ ╚═╝\x1b[0m
112
+ \x1b[38;5;239m ██████╗\x1b[38;5;242m██╗ \x1b[38;5;245m██╗\x1b[38;5;105m██████╗ \x1b[38;5;141m███████╗\x1b[38;5;147m ██████╗\x1b[38;5;183m██╗ ██╗\x1b[0m
113
+ \x1b[38;5;239m ██╔════╝\x1b[38;5;242m██║ \x1b[38;5;245m██║\x1b[38;5;105m██╔══██╗\x1b[38;5;141m██╔════╝\x1b[38;5;147m██╔════╝\x1b[38;5;183m██║ ██╔╝\x1b[0m
114
+ \x1b[38;5;239m ██║ \x1b[38;5;242m██║ \x1b[38;5;245m██║\x1b[38;5;105m██║ ██║\x1b[38;5;141m█████╗ \x1b[38;5;147m██║ \x1b[38;5;183m█████╔╝ \x1b[0m
115
+ \x1b[38;5;239m ██║ \x1b[38;5;242m██║ \x1b[38;5;245m██║\x1b[38;5;105m██║ ██║\x1b[38;5;141m██╔══╝ \x1b[38;5;147m██║ \x1b[38;5;183m██╔═██╗ \x1b[0m
116
+ \x1b[38;5;239m ╚██████╗\x1b[38;5;242m███████╗\x1b[38;5;245m██║\x1b[38;5;105m██████╔╝\x1b[38;5;141m███████╗\x1b[38;5;147m╚██████╗\x1b[38;5;183m██║ ██╗\x1b[0m
117
+ \x1b[38;5;239m ╚═════╝\x1b[38;5;242m╚══════╝\x1b[38;5;245m╚═╝\x1b[38;5;105m╚═════╝ \x1b[38;5;141m╚══════╝\x1b[38;5;147m ╚═════╝\x1b[38;5;183m╚═╝ ╚═╝\x1b[0m
118
118
 
119
- \x1b[38;5;141m ╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╸\x1b[0m
119
+ \x1b[38;5;105m ╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╸\x1b[0m
120
120
 
121
121
  \x1b[38;5;245m v${v}\x1b[0m
122
122
 
Binary file
Binary file
Binary file