@vibelord/lanes 1.1.1 → 1.3.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.
Files changed (71) hide show
  1. package/README.md +36 -5
  2. package/bin/lanes-install.mjs +166 -13
  3. package/lib/plan.mjs +113 -3
  4. package/package.json +1 -1
  5. package/payload/lanes/README.md +47 -23
  6. package/payload/lanes/VERSION +1 -1
  7. package/payload/lanes/bin/lanes +2753 -202
  8. package/payload/lanes/client/ghostty/lanes-palette-light.conf +19 -19
  9. package/payload/lanes/client/ghostty/lanes-palette.conf +19 -19
  10. package/payload/lanes/client/lanes-launch +289 -40
  11. package/payload/lanes/client/lanes-window +15 -3
  12. package/payload/lanes/container/hooks/claude-settings.json +1 -0
  13. package/payload/lanes/container/lane +147 -92
  14. package/payload/lanes/container/lane-edit +100 -0
  15. package/payload/lanes/container/lane-mcp +1150 -0
  16. package/payload/lanes/container/lane-signal +109 -29
  17. package/payload/lanes/container/lane_core.py +1318 -0
  18. package/payload/lanes/container/vimrc +31 -0
  19. package/payload/lanes/docs/ARCHITECTURE.md +793 -16
  20. package/payload/lanes/docs/DESIGN.md +127 -46
  21. package/payload/lanes/docs/INSTALL.md +175 -18
  22. package/payload/lanes/docs/RUNBOOK.md +968 -76
  23. package/payload/lanes/docs/SHARING.md +3 -2
  24. package/payload/lanes/docs/USERGUIDE.md +967 -150
  25. package/payload/lanes/holders.lock +5 -0
  26. package/payload/lanes/install/enable-docs.sh +78 -0
  27. package/payload/lanes/install/enable-web.sh +2 -0
  28. package/payload/lanes/install/host-install.sh +197 -7
  29. package/payload/lanes/install/services.py +148 -18
  30. package/payload/lanes/install.sh +68 -7
  31. package/payload/lanes/libexec/agent_state.py +74 -1
  32. package/payload/lanes/libexec/cursor_sync.py +6 -0
  33. package/payload/lanes/libexec/device_sync.py +47 -10
  34. package/payload/lanes/libexec/docs_store.py +995 -0
  35. package/payload/lanes/libexec/docsd.py +2010 -0
  36. package/payload/lanes/libexec/economy_poll.py +1 -0
  37. package/payload/lanes/libexec/enrold.py +41 -22
  38. package/payload/lanes/libexec/keys.py +54 -23
  39. package/payload/lanes/libexec/lanes_lib.py +620 -22
  40. package/payload/lanes/libexec/mdlite.py +575 -0
  41. package/payload/lanes/libexec/members.py +21 -2
  42. package/payload/lanes/libexec/notify.py +169 -29
  43. package/payload/lanes/libexec/registrations.py +216 -18
  44. package/payload/lanes/libexec/session_actions.py +182 -14
  45. package/payload/lanes/libexec/sidebar +11 -3
  46. package/payload/lanes/libexec/sidebar.py +2232 -365
  47. package/payload/lanes/libexec/statusd.py +231 -12
  48. package/payload/lanes/libexec/studio_host.py +8 -1
  49. package/payload/lanes/libexec/tokens.py +148 -35
  50. package/payload/lanes/libexec/tokens.sh +86 -2
  51. package/payload/lanes/libexec/update.py +1044 -0
  52. package/payload/lanes/libexec/uploads.py +538 -0
  53. package/payload/lanes/libexec/users.py +614 -0
  54. package/payload/lanes/libexec/view.sh +34 -6
  55. package/payload/lanes/systemd/lanes-docsd.service +29 -0
  56. package/payload/lanes/systemd/lanes-ensure.service +3 -0
  57. package/payload/lanes/systemd/lanes-gc.service +1 -1
  58. package/payload/lanes/systemd/lanes-update.service +17 -0
  59. package/payload/lanes/systemd/lanes-update.timer +17 -0
  60. package/payload/lanes/web/Caddyfile +24 -1
  61. package/payload/lanes/web/README.md +165 -28
  62. package/payload/lanes/web/browser.js +4 -8
  63. package/payload/lanes/web/docs.css +52 -0
  64. package/payload/lanes/web/docs.js +36 -0
  65. package/payload/lanes/web/mobile.css +17 -17
  66. package/payload/lanes/web/mobile.js +45 -32
  67. package/payload/lanes/web/upload.css +17 -0
  68. package/payload/lanes/web/upload.js +84 -0
  69. package/payload/lanes/workspaces.example/README.md +3 -1
  70. package/payload/lanes/zellij/config.kdl +106 -56
  71. package/payload/manifest.json +6 -3
@@ -3,20 +3,20 @@ html.lanes-mobile {
3
3
  --lanes-surface: #101318;
4
4
  --lanes-surface-raised: #191e25;
5
5
  --lanes-line: #2c333d;
6
- --lanes-accent-wash: #1a2721;
6
+ --lanes-accent-wash: #1c3326;
7
7
  --lanes-accent-line: #4b6955;
8
8
  --lanes-press: #26332c;
9
9
  --lanes-chip: #ffffff0f;
10
- --lanes-ink: #edf1f5;
11
- --lanes-ink-dim: #9ca7b5;
12
- --lanes-ink-faint: #6b7683;
13
- --lanes-accent: #b4e3bf;
10
+ --lanes-ink: #f2f5f8;
11
+ --lanes-ink-dim: #a6b1be;
12
+ --lanes-ink-faint: #6f7a87;
13
+ --lanes-accent: #8fe3a6;
14
14
  --lanes-on-accent: #101318;
15
15
  --lanes-on-accent-urgent: #7a1a12;
16
- --lanes-tone-urgent: #f28b7d;
17
- --lanes-tone-attention: #e5b84a;
18
- --lanes-tone-working: #7fb3f2;
19
- --lanes-tone-done: #6fd39a;
16
+ --lanes-tone-urgent: #ff7b6e;
17
+ --lanes-tone-attention: #ffc233;
18
+ --lanes-tone-working: #6fb6ff;
19
+ --lanes-tone-done: #5fdc8a;
20
20
  --lanes-scrim: #000000a6;
21
21
  --lanes-shadow: 0 -16px 80px #0007;
22
22
  color-scheme: dark;
@@ -29,16 +29,16 @@ html.lanes-mobile:root[data-zj-theme="light"] {
29
29
  --lanes-accent-line: #acc9b4;
30
30
  --lanes-press: #d8e9db;
31
31
  --lanes-chip: #edf3ec;
32
- --lanes-ink: #17241b;
33
- --lanes-ink-dim: #56616d;
34
- --lanes-ink-faint: #7f8a96;
35
- --lanes-accent: #2f6b45;
32
+ --lanes-ink: #111a14;
33
+ --lanes-ink-dim: #4a5661;
34
+ --lanes-ink-faint: #6f7b87;
35
+ --lanes-accent: #1f6f3f;
36
36
  --lanes-on-accent: #ffffff;
37
37
  --lanes-on-accent-urgent: #ffd3cc;
38
- --lanes-tone-urgent: #b7322a;
39
- --lanes-tone-attention: #8a5a00;
40
- --lanes-tone-working: #2456a8;
41
- --lanes-tone-done: #1b7142;
38
+ --lanes-tone-urgent: #c41c1c;
39
+ --lanes-tone-attention: #a04d00;
40
+ --lanes-tone-working: #1a56c4;
41
+ --lanes-tone-done: #0d7338;
42
42
  --lanes-scrim: #1a222b80;
43
43
  --lanes-shadow: 0 -16px 80px #0003;
44
44
  color-scheme: light;
@@ -10,7 +10,7 @@ if (typeof module === 'object') module.exports = {laneShellMode};
10
10
  width: innerWidth, height: innerHeight});
11
11
  const root = document.documentElement;
12
12
  // BEGIN lanes tokens (generated by libexec/tokens.py --js; do not edit by hand)
13
- const LANES_ANSI = {dark: ['#7d8896', '#f28b7d', '#6fd39a', '#e5b84a', '#7fb3f2', '#c9a6ff', '#6fd6d0', '#9ca7b5', '#8a95a3', '#ff9d96', '#8fe0a3', '#f7cf7f', '#a5cbff', '#dcc6ff', '#9ae6e0', '#edf1f5'], light: ['#3b4a42', '#b7322a', '#1b7142', '#8a5a00', '#2456a8', '#7a3fb8', '#0f7b7b', '#56616d', '#5f6d66', '#c2332a', '#1b7a38', '#8f5a00', '#2860c4', '#8a4dc8', '#0f7272', '#17241b']};
13
+ const LANES_ANSI = {dark: ['#8792a0', '#ff7b6e', '#5fdc8a', '#ffc233', '#6fb6ff', '#c58bff', '#3fd9cf', '#a6b1be', '#97a3b1', '#ff9a8e', '#86f0ad', '#ffd866', '#98cbff', '#dcb2ff', '#7deee6', '#f2f5f8'], light: ['#2f3b34', '#c41c1c', '#0d7338', '#a04d00', '#1a56c4', '#8228c4', '#006f73', '#4a5661', '#55625b', '#d0221a', '#0e7a3a', '#a85200', '#1f5fd8', '#9133d0', '#00797c', '#111a14']};
14
14
  // END lanes tokens
15
15
  root.dataset.lanesShell = mode;
16
16
  if (mode !== 'phone') {
@@ -22,7 +22,7 @@ if (typeof module === 'object') module.exports = {laneShellMode};
22
22
  root.classList.add('lanes-mobile');
23
23
  if (mode === 'tablet') {
24
24
  root.classList.add('lanes-tablet');
25
- try {localStorage.getItem('zellij:mobile-ui') || localStorage.setItem('zellij:mobile-ui', 'on');} catch (_) {}
25
+ try {localStorage.getItem('zellij:mobile-ui') || localStorage.setItem('zellij:mobile-ui', 'on');} catch {}
26
26
  }
27
27
 
28
28
  const elementFocus = HTMLElement.prototype.focus;
@@ -37,9 +37,9 @@ if (typeof module === 'object') module.exports = {laneShellMode};
37
37
  const theme = localStorage.getItem('zellij:mobile-theme') || 'dark';
38
38
  localStorage.setItem('zellij:mobile-theme', theme);
39
39
  root.dataset.zjTheme = theme;
40
- } catch (_) {}
40
+ } catch {}
41
41
  const icons = {
42
- workspaces: '<rect x="3" y="3" width="7" height="7" rx="2"/><rect x="14" y="3" width="7" height="7" rx="2"/><rect x="3" y="14" width="7" height="7" rx="2"/><rect x="14" y="14" width="7" height="7" rx="2"/>',
42
+ workspaces: [[3, 3], [14, 3], [3, 14], [14, 14]].map(([x, y]) => `<rect x="${x}" y="${y}" width="7" height="7" rx="2"/>`).join(''),
43
43
  lanes: '<path d="M8 5h13M8 12h13M8 19h13"/><path d="M3 5h.01M3 12h.01M3 19h.01"/>',
44
44
  panes: '<rect x="3" y="4" width="18" height="16" rx="3"/><path d="M12 4v16"/>',
45
45
  keyboard: '<rect x="2" y="5" width="20" height="14" rx="3"/><path d="M6 9h.01M10 9h.01M14 9h.01M18 9h.01M6 13h.01M10 13h.01M14 13h.01M18 13h.01M8 16h8"/>',
@@ -69,14 +69,14 @@ if (typeof module === 'object') module.exports = {laneShellMode};
69
69
  return el;
70
70
  };
71
71
  let shell, heading, subtitle, dock, sheet, list, search, count, lanesButton, panesButton, pasteKey, typingField, quickRow, quickOn;
72
- let laneFilterBar, laneFilterInput, laneFilterPrev = '', sentAt = 0;
72
+ let laneFilterBar, laneFilterInput, laneFilterPrev = '', sentAt = 0, filterTab, owed = new Set();
73
73
  let api, term, previousCards = '', sheetOpen = false, returnFocus, menuWas, hintN = 0, timer;
74
74
  const queue = [];
75
75
  let fontSize = 16;
76
76
  try {
77
77
  const stored = Number(localStorage.getItem('lanes:font-size'));
78
78
  if (stored >= 14 && stored <= 24) fontSize = stored;
79
- } catch (_) {}
79
+ } catch {}
80
80
  const state = () => api?.getState();
81
81
  const data = () => state()?.data;
82
82
  const nativeClick = selector => document.querySelector(selector)?.click();
@@ -85,10 +85,9 @@ if (typeof module === 'object') module.exports = {laneShellMode};
85
85
 
86
86
  function viewport() {
87
87
  const vv = window.visualViewport;
88
- root.style.setProperty('--lanes-vh', `${vv?.height ?? innerHeight}px`);
89
- root.style.setProperty('--lanes-vw', `${vv?.width ?? innerWidth}px`);
90
- root.style.setProperty('--lanes-offset-top', `${vv?.offsetTop ?? 0}px`);
91
- root.style.setProperty('--lanes-offset-left', `${vv?.offsetLeft ?? 0}px`);
88
+ const px = (k, v) => root.style.setProperty(`--lanes-${k}`, `${v}px`);
89
+ px('vh', vv?.height ?? innerHeight); px('vw', vv?.width ?? innerWidth);
90
+ px('offset-top', vv?.offsetTop ?? 0); px('offset-left', vv?.offsetLeft ?? 0);
92
91
  const occluded = !!vv && innerHeight - vv.height - vv.offsetTop > 120;
93
92
  const focused = kbd()?.isFocused || document.activeElement?.matches('input, textarea');
94
93
  setKeyboard(occluded && focused);
@@ -110,14 +109,14 @@ if (typeof module === 'object') module.exports = {laneShellMode};
110
109
  label.append(make('span', '', 'Type here'));
111
110
  label.setAttribute('aria-hidden', 'true');
112
111
  host.removeAttribute('aria-hidden');
113
- host.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;opacity:1;pointer-events:auto;';
112
+ const fill = 'position:absolute;inset:0;width:100%;height:100%;opacity:1;pointer-events:auto;';
113
+ host.style.cssText = fill;
114
114
  capture.removeAttribute('aria-hidden');
115
115
  aria(capture, 'Type in terminal');
116
116
  for (const a of ['type text', 'autocomplete off', 'autocapitalize none']) capture.setAttribute(...a.split(' '));
117
117
  capture.tabIndex = 0;
118
- capture.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;box-sizing:border-box;font-size:16px;' +
119
- 'border:0;border-radius:12px;padding:0;margin:0;opacity:1;background:transparent;color:transparent;' +
120
- 'caret-color:transparent;outline:none;pointer-events:auto;user-select:text;-webkit-user-select:text;';
118
+ capture.style.cssText = fill + 'box-sizing:border-box;font-size:16px;border:0;border-radius:12px;padding:0;margin:0;' +
119
+ 'background:transparent;color:transparent;caret-color:transparent;outline:none;user-select:text;-webkit-user-select:text;';
121
120
  field.append(label, host);
122
121
  capture.addEventListener('beforeinput', ev => {
123
122
  const t = ev.data ?? ev.dataTransfer?.getData('text'), r = ev.getTargetRanges?.()[0];
@@ -289,7 +288,7 @@ if (typeof module === 'object') module.exports = {laneShellMode};
289
288
  else if (Date.now() - sentAt < 60) return arm();
290
289
  else queue.shift();
291
290
  }
292
- } catch (_) { queue.length = 0; }
291
+ } catch { queue.length = 0; }
293
292
  }
294
293
  const paste = t => currentTab()?.name.endsWith(' ☰') ? hint('Open a lane to paste') :
295
294
  (send('\x1b[200~' + t.replace(/\x1b+(?=\[201~)/g, '').replace(/\r?\n/g, '\r') + '\x1b[201~'), 1);
@@ -300,12 +299,12 @@ if (typeof module === 'object') module.exports = {laneShellMode};
300
299
  let i = 0;
301
300
  while (i < p.length && p[i] === q[i]) i++;
302
301
  laneFilterPrev = s;
303
- if (!s) send('\x1b', null);
302
+ if (!s) { clearLaneFilter(); refresh(); }
304
303
  else if (!p.length) send('\x1b', null, '/', s);
305
304
  else send('\x7f'.repeat(p.length - i) + q.slice(i).join(''));
306
305
  }
307
306
  async function clip(f, ok) {
308
- try { ok(await f(navigator.clipboard)); } catch (_) { hint('Clipboard blocked'); }
307
+ try { ok(await f(navigator.clipboard)); } catch { hint('Clipboard blocked'); }
309
308
  }
310
309
  function screen() {
311
310
  if (term.hasSelection()) return term.getSelection();
@@ -333,6 +332,10 @@ if (typeof module === 'object') module.exports = {laneShellMode};
333
332
  }
334
333
  return row;
335
334
  }
335
+ function clearLaneFilter() {
336
+ owed.add(filterTab);
337
+ laneFilterPrev = laneFilterInput.value = '';
338
+ }
336
339
  function buildLaneFilterBar() {
337
340
  const bar = make('div', 'lanes-filter-bar');
338
341
  aria(bar, 'Filter lanes');
@@ -344,24 +347,26 @@ if (typeof module === 'object') module.exports = {laneShellMode};
344
347
  aria(laneFilterInput, 'Filter lanes in this workspace');
345
348
  laneFilterInput.addEventListener('input', () => applyLaneFilter(laneFilterInput.value));
346
349
  laneFilterInput.addEventListener('keydown', ev => {
347
- if (ev.key === 'Escape') { ev.preventDefault(); send('\x1b', null); laneFilterPrev = laneFilterInput.value = ''; laneFilterInput.blur(); }
348
- if (ev.key === 'Enter') { ev.preventDefault(); laneFilterInput.blur(); }
350
+ if (ev.key === 'Escape') { clearLaneFilter(); refresh(); }
351
+ if (/^E(scape|nter)$/.test(ev.key)) { ev.preventDefault(); laneFilterInput.blur(); }
349
352
  });
350
353
  bar.append(laneFilterInput);
351
354
  return bar;
352
355
  }
353
- function updateLaneFilterBar(isPicker) {
354
- if (!laneFilterBar) return;
356
+ function updateLaneFilterBar(tab, isPicker) {
355
357
  laneFilterBar.hidden = !isPicker;
356
- if (isPicker) return;
357
- const wait = queue.indexOf(null);
358
- if (wait >= 0) queue.length = wait;
359
- laneFilterPrev = laneFilterInput.value = '';
358
+ if (tab !== filterTab) {
359
+ const wait = queue.indexOf(null);
360
+ if (wait >= 0) queue.length = wait;
361
+ if (laneFilterPrev) clearLaneFilter();
362
+ }
363
+ if (owed.delete(tab)) send('\x1b', null);
364
+ if (isPicker) filterTab = tab;
360
365
  }
361
366
  function applyFont() {
362
367
  term.options.fontSize = fontSize;
363
368
  term.options.lineHeight = 1.1;
364
- try {localStorage.setItem('lanes:font-size', String(fontSize));} catch (_) {}
369
+ try {localStorage.setItem('lanes:font-size', String(fontSize));} catch {}
365
370
  text(document.getElementById('lanes-font-value'), `${fontSize} px`);
366
371
  resize();
367
372
  }
@@ -376,9 +381,14 @@ if (typeof module === 'object') module.exports = {laneShellMode};
376
381
  return t;
377
382
  }
378
383
  const applyTheme = () => { if (term) term.options.theme = lanesTheme(); };
384
+ function menuRow(label, tag = 'div', more = '') {
385
+ const row = make(tag, 'lanes-font-controls' + more);
386
+ row.append(make('span', '', label));
387
+ document.querySelector('.zj-mobile-menu')?.prepend(row);
388
+ return row;
389
+ }
379
390
  function installFontControls() {
380
- const row = make('div', 'lanes-font-controls');
381
- row.append(make('span', '', 'Text size'));
391
+ const row = menuRow('Text size');
382
392
  const smaller = make('button', '', 'A−'), larger = make('button', '', 'A+');
383
393
  aria(smaller, 'Smaller terminal text');
384
394
  aria(larger, 'Larger terminal text');
@@ -386,7 +396,9 @@ if (typeof module === 'object') module.exports = {laneShellMode};
386
396
  smaller.addEventListener('click', () => {fontSize = Math.max(14, fontSize - 1); applyFont();});
387
397
  larger.addEventListener('click', () => {fontSize = Math.min(24, fontSize + 1); applyFont();});
388
398
  row.append(smaller, value, larger);
389
- document.querySelector('.zj-mobile-menu')?.prepend(row);
399
+ Object.assign(menuRow('Docs', 'a', ' lanes-session-link'), {href: '/lanes-docs/', target: '_blank'});
400
+ fetch('/enrol/site', {credentials: 'same-origin'}).then(r => r.json())
401
+ .then(({version: v}) => /^\d[\w.+-]{0,31}$/.test(v) && menuRow(`Lanes v${v}`)).catch(() => {});
390
402
  }
391
403
  function refresh() {
392
404
  if (!api || !shell) return;
@@ -397,7 +409,7 @@ if (typeof module === 'object') module.exports = {laneShellMode};
397
409
  const name = tab?.name.replace(/ ☰$/, '') || 'Lanes';
398
410
  const picker = tab?.name.endsWith(' ☰');
399
411
  const pane = data()?.panes.find(p => p.pane_id === data()?.active_pane?.pane_id && !p.is_plugin);
400
- const counts = parseCounts(pickerTitle(name));
412
+ const title = pickerTitle(name), counts = parseCounts(title);
401
413
  const chips = countParts(counts).map(([glyph, n]) => glyph + n).join(' ');
402
414
  text(heading, name);
403
415
  if (!hintN) text(subtitle, picker ? chips || 'Choose a lane to continue' : pane?.title || 'Your workspace');
@@ -405,12 +417,13 @@ if (typeof module === 'object') module.exports = {laneShellMode};
405
417
  panesButton.hidden = pasteKey.hidden = !!picker;
406
418
  const needs = counts ? counts.approval + counts.input : 0;
407
419
  if (needs) lanesButton.dataset.count = String(needs); else delete lanesButton.dataset.count;
408
- updateLaneFilterBar(picker);
420
+ updateLaneFilterBar(tab?.name, picker);
409
421
  send();
410
- const quick = active && !picker;
422
+ const quick = active && (!picker || / · [›>] \w+$/.test(title) && 2);
411
423
  if (quick !== quickOn) {
412
424
  quickOn = quick;
413
425
  quickRow.hidden = !quick;
426
+ for (const key of quickRow.children) key.hidden = quick > 1 && !['⏎', 'y', 'Esc'].includes(key.textContent);
414
427
  root.style.setProperty('--lanes-quick', quick ? '48px' : '0px');
415
428
  resize();
416
429
  }
@@ -0,0 +1,17 @@
1
+ /* Lanes docs page: the drop area (plan W10 W5, LNS-106), phone first; colours: docs.css's token block. */
2
+ .docs-upload { margin: 1em 0; }
3
+ .docs-upload label, .docs-upload select, .docs-upload button { display: block; width: 100%; }
4
+ .docs-upload select, .docs-upload button, .docs-upload input { min-height: 44px; font: inherit; }
5
+ .docs-upload select, .docs-upload button {
6
+ margin: .4em 0; padding: 8px; border: 1px solid var(--lanes-line); border-radius: 8px;
7
+ background: var(--lanes-surface-raised); color: var(--lanes-ink);
8
+ }
9
+ .docs-drop {
10
+ margin: .6em 0; padding: 20px 12px; text-align: center; color: var(--lanes-ink-dim);
11
+ border: 2px dashed var(--lanes-line); border-radius: 8px;
12
+ }
13
+ .docs-drop.over { border-color: var(--lanes-accent); color: var(--lanes-ink); }
14
+ .docs-drop input { display: block; width: 100%; margin-top: 8px; }
15
+ .docs-drop::after { content: attr(data-count); display: block; color: var(--lanes-accent); }
16
+ .docs-sent { padding-left: 1.2em; overflow-wrap: anywhere; }
17
+ .docs-sent progress { width: 100%; }
@@ -0,0 +1,84 @@
1
+ // Lanes docs page: the drop area (plan W10 W5, LNS-106). The form works without this; here a drop lands anywhere on
2
+ // the area, the size is checked before sending, progress shows and each file's receipt is listed. web/README.md.
3
+ (function () {
4
+ 'use strict';
5
+ var form = document.querySelector('form.docs-upload');
6
+ if (!form) return;
7
+ var area = form.querySelector('.docs-drop'), input = form.querySelector('input[type=file]'),
8
+ list = form.querySelector('.docs-sent'), button = form.querySelector('button'),
9
+ max = +form.getAttribute('data-max') || 0;
10
+
11
+ function add(text, href, name) {
12
+ var li = document.createElement('li'), a;
13
+ if (href) {
14
+ a = li.appendChild(document.createElement('a'));
15
+ a.setAttribute('href', href);
16
+ a.textContent = name;
17
+ }
18
+ li.appendChild(document.createTextNode(text));
19
+ return list.appendChild(li);
20
+ }
21
+
22
+ function count() {
23
+ area.setAttribute('data-count', input.files.length ? input.files.length + ' chosen' : '');
24
+ }
25
+
26
+ function over(on) {
27
+ return function (e) {
28
+ e.preventDefault();
29
+ area.classList.toggle('over', on);
30
+ };
31
+ }
32
+
33
+ area.addEventListener('dragenter', over(true));
34
+ area.addEventListener('dragover', over(true));
35
+ area.addEventListener('dragleave', over(false));
36
+ area.addEventListener('drop', function (e) {
37
+ over(false)(e);
38
+ input.files = e.dataTransfer.files;
39
+ count();
40
+ });
41
+ input.addEventListener('change', count);
42
+
43
+ form.addEventListener('submit', function (e) {
44
+ var files = input.files, total = 0, i, x, row, bar;
45
+ e.preventDefault();
46
+ if (!files.length) return;
47
+ for (i = 0; i < files.length; i++) total += files[i].size + 1024;
48
+ if (max && total > max) {
49
+ add('Too large: ' + Math.ceil(total / 1048576) + ' MiB; an upload may be ' + max / 1048576 + ' MiB.');
50
+ return;
51
+ }
52
+ x = new XMLHttpRequest();
53
+ row = add('');
54
+ bar = row.appendChild(document.createElement('progress'));
55
+ function done(said) {
56
+ list.removeChild(row);
57
+ button.disabled = false;
58
+ if (said) add(said);
59
+ }
60
+ x.upload.onprogress = function (p) {
61
+ if (p.lengthComputable) bar.value = p.loaded / p.total;
62
+ };
63
+ x.onload = function () {
64
+ var r = {};
65
+ try {
66
+ r = JSON.parse(x.responseText);
67
+ } catch (err) { /* not JSON: said below */ }
68
+ done('');
69
+ (r.receipts || []).forEach(function (t) {
70
+ add(' · ' + t.bytes + ' B · sha256 ' + t.sha256 + ' · ' + t.landed, 'd/' + t.id, t.name);
71
+ });
72
+ if (x.status !== 200) add('Refused: ' + (r.error || x.status));
73
+ input.value = '';
74
+ count();
75
+ };
76
+ x.onerror = function () {
77
+ done('The upload did not reach the server; try again.');
78
+ };
79
+ x.open('POST', form.getAttribute('action'));
80
+ x.setRequestHeader('Accept', 'application/json');
81
+ button.disabled = true;
82
+ x.send(new FormData(form));
83
+ });
84
+ }());
@@ -24,7 +24,9 @@ A lane is whatever the registry says it is: `lanes sync` (every two minutes, rea
24
24
  terminal into a lane with a stable id `<board>--<slug of the terminal name>` (with the repository `prefix`). A
25
25
  `displayNames` entry, which the sidebar `R` key and `lanes rename <lane-id> <name>` write into the repository's block
26
26
  of `workspaces.<board>.json` (first write keeps a `.bak-<UTC>` copy beside it), changes only the shown name, never the
27
- id. Add a terminal to `.vscode/terminals.json` for an editor-defined lane.
27
+ id. The repository can also name a lane itself with `lanes.terminals[<name>].displayName` in its `terminals.json`;
28
+ the manifest's `displayNames` entry wins over it, and a `displayName` another lane on the board already answers to is
29
+ ignored (the lane keeps its title). Add a terminal to `.vscode/terminals.json` for an editor-defined lane.
28
30
  Alternatively, use **+** beside a discovered session to register its existing binding and ID in a workspace
29
31
  using the same container. Sync includes these local registrations; they never run a create command.
30
32
  Repository declarations take precedence when they declare the same binding. Legacy Docker shells cannot be adopted.
@@ -194,15 +194,48 @@ keybinds clear-defaults=true {
194
194
  height "1%"
195
195
  }
196
196
  }
197
- // These two still hand a key to the SIDEBAR (a filter and the upload box live there, so the focus must end
198
- // on it). WHY THREE MoveFocus "left": a workspace tab is [sidebar | view main | view b | view c] (wide views,
199
- // at most 3 viewports) and WriteChars types into whichever pane holds the focus BY THEN; three lefts reach
200
- // the sidebar from every viewport and zellij does not wrap at the left edge (tests/view.py). Known limit: a
201
- // tab WITHOUT a sidebar (Host, Throttle, the mobile view tab) has nothing to the left, so there the
202
- // character still goes to the pane itself.
203
- bind "Ctrl Alt /" { MoveFocus "left"; MoveFocus "left"; MoveFocus "left"; WriteChars "/"; }
204
- // Arm the sidebar's upload box, then drop files anywhere on the window (a drop = a paste into the focused pane).
205
- bind "Ctrl Alt u" { MoveFocus "left"; MoveFocus "left"; MoveFocus "left"; WriteChars "U"; }
197
+ // These two hand a key to the SIDEBAR (its filter and its upload strip need the keyboard there) and type
198
+ // nothing (cockpit v3 S4). Three MoveFocus "left" first: a workspace tab is [sidebar | view main | view b |
199
+ // view c] (wide views, at most 3 viewports), three lefts reach the sidebar from every viewport and zellij does
200
+ // not wrap at the left edge (tests/view.py). Then `lanes sidebar-key filter|upload --focused`, in a floating
201
+ // pane that closes when it exits (the next/prev block above), writes views/<device>/<ws>.sidebar.cmd for the
202
+ // sidebar of this tab; that sidebar acts on it within a tick, and always before the next key it reads. When
203
+ // the floating pane closes the focus is back on the sidebar. They used to end in WriteChars "/" / "U", which
204
+ // typed into whatever pane held the focus by then: on a tab without a sidebar (Host, Throttle, the phone's
205
+ // view tab) the pane itself, a shell or an agent's input. Such a tab now gets nothing.
206
+ bind "Ctrl Alt /" {
207
+ MoveFocus "left"
208
+ MoveFocus "left"
209
+ MoveFocus "left"
210
+ Run "lanes" "sidebar-key" "filter" "--focused" {
211
+ floating true
212
+ close_on_exit true
213
+ name "Lanes"
214
+ x "0%"
215
+ y "0%"
216
+ width "1%"
217
+ height "1%"
218
+ }
219
+ }
220
+ // Arm the sidebar's upload strip, then drop files anywhere on the window (a drop = a paste into the focused pane).
221
+ bind "Ctrl Alt u" {
222
+ MoveFocus "left"
223
+ MoveFocus "left"
224
+ MoveFocus "left"
225
+ Run "lanes" "sidebar-key" "upload" "--focused" {
226
+ floating true
227
+ close_on_exit true
228
+ name "Lanes"
229
+ x "0%"
230
+ y "0%"
231
+ width "1%"
232
+ height "1%"
233
+ }
234
+ }
235
+ // Focus the sidebar, nothing else (cockpit v3 S4): the same three lefts, so its keys can be used; the footer then
236
+ // says ` ▌ keys go to sidebar · Esc → lane`, and Esc (or any action) gives the keyboard back to the lane.
237
+ // Ctrl-Alt-s stays the session manager below.
238
+ bind "Ctrl Alt l" { MoveFocus "left"; MoveFocus "left"; MoveFocus "left"; }
206
239
  bind "Ctrl Alt right" { GoToNextTab; }
207
240
  bind "Ctrl Alt s" {
208
241
  LaunchOrFocusPlugin "session-manager" {
@@ -216,6 +249,12 @@ keybinds clear-defaults=true {
216
249
  // children are the pinned style, and test_ctrl_alt_m_is_the_multi_line_floating_run_block pins them. "lanes"
217
250
  // is found on the view server's PATH
218
251
  // (bin/lanes clean_env: this checkout's bin/), because this file is read in place, never rendered per host.
252
+ // Quick edit (W9 E1, LNS-95): Ctrl-Alt-e opens the focused lane's AGENTS.md, CLAUDE.md or .env.local in an editor
253
+ // inside its container, in the same floating geometry; the pane closes when the editor exits. Like the preview it
254
+ // is off while locked, so a locked view hands Ctrl-Alt-e to the lane. test_keybinds FloatingRunPanes pins both.
255
+ // Run has no single-instance form (LaunchOrFocus is for plugins only) and this bind fires whichever pane has the
256
+ // focus, so `lanes edit` is its own guard (DEF-1793): when another Edit pane is open it focuses or names that one
257
+ // and opens nothing. Its EDIT_PANE is this `name`; test_server_cli EditWiring pins the two together.
219
258
  shared_except "locked" {
220
259
  bind "Ctrl Alt m" {
221
260
  Run "lanes" "preview" "--focused" {
@@ -228,6 +267,17 @@ keybinds clear-defaults=true {
228
267
  height "90%"
229
268
  }
230
269
  }
270
+ bind "Ctrl Alt e" {
271
+ Run "lanes" "edit" "--focused" {
272
+ floating true
273
+ close_on_exit true
274
+ name "Edit"
275
+ x "10%"
276
+ y "5%"
277
+ width "80%"
278
+ height "90%"
279
+ }
280
+ }
231
281
  }
232
282
  shared_except "normal" "locked" {
233
283
  bind "Alt left" { MoveFocusOrTab "left"; }
@@ -376,36 +426,36 @@ osc8_hyperlinks true
376
426
  // BEGIN lanes tokens (generated by libexec/tokens.py --kdl; do not edit by hand)
377
427
  themes {
378
428
  lanes-dark {
379
- text_unselected { base "#edf1f5"; background "#101318"; emphasis_0 "#e5b84a"; emphasis_1 "#6fd39a"; emphasis_2 "#b4e3bf"; emphasis_3 "#7fb3f2"; }
380
- text_selected { base "#edf1f5"; background "#1a2721"; emphasis_0 "#e5b84a"; emphasis_1 "#6fd39a"; emphasis_2 "#b4e3bf"; emphasis_3 "#7fb3f2"; }
381
- ribbon_selected { base "#101318"; background "#b4e3bf"; emphasis_0 "#7a1a12"; emphasis_1 "#101318"; emphasis_2 "#101318"; emphasis_3 "#101318"; }
382
- ribbon_unselected { base "#9ca7b5"; background "#191e25"; emphasis_0 "#f28b7d"; emphasis_1 "#edf1f5"; emphasis_2 "#e5b84a"; emphasis_3 "#7fb3f2"; }
383
- table_title { base "#b4e3bf"; background "#101318"; emphasis_0 "#e5b84a"; emphasis_1 "#6fd39a"; emphasis_2 "#b4e3bf"; emphasis_3 "#7fb3f2"; }
384
- table_cell_selected { base "#edf1f5"; background "#1a2721"; emphasis_0 "#e5b84a"; emphasis_1 "#6fd39a"; emphasis_2 "#b4e3bf"; emphasis_3 "#7fb3f2"; }
385
- table_cell_unselected { base "#edf1f5"; background "#101318"; emphasis_0 "#e5b84a"; emphasis_1 "#6fd39a"; emphasis_2 "#b4e3bf"; emphasis_3 "#7fb3f2"; }
386
- list_selected { base "#edf1f5"; background "#1a2721"; emphasis_0 "#e5b84a"; emphasis_1 "#6fd39a"; emphasis_2 "#b4e3bf"; emphasis_3 "#7fb3f2"; }
387
- list_unselected { base "#edf1f5"; background "#101318"; emphasis_0 "#e5b84a"; emphasis_1 "#6fd39a"; emphasis_2 "#b4e3bf"; emphasis_3 "#7fb3f2"; }
388
- frame_selected { base "#b4e3bf"; background "#101318"; emphasis_0 "#e5b84a"; emphasis_1 "#6fd39a"; emphasis_2 "#7fb3f2"; emphasis_3 "#f28b7d"; }
389
- frame_highlight { base "#e5b84a"; background "#101318"; emphasis_0 "#f28b7d"; emphasis_1 "#6fd39a"; emphasis_2 "#b4e3bf"; emphasis_3 "#7fb3f2"; }
390
- exit_code_success { base "#6fd39a"; background "#101318"; emphasis_0 "#6fd39a"; emphasis_1 "#6fd39a"; emphasis_2 "#6fd39a"; emphasis_3 "#6fd39a"; }
391
- exit_code_error { base "#f28b7d"; background "#101318"; emphasis_0 "#f28b7d"; emphasis_1 "#f28b7d"; emphasis_2 "#f28b7d"; emphasis_3 "#f28b7d"; }
392
- multiplayer_user_colors { player_1 "#7fb3f2"; player_2 "#e5b84a"; player_3 "#6fd39a"; player_4 "#f28b7d"; player_5 "#b4e3bf"; player_6 "#c9a6ff"; player_7 "#6fd6d0"; player_8 "#f7cf7f"; player_9 "#8fe0a3"; player_10 "#ff9d96"; }
429
+ text_unselected { base "#f2f5f8"; background "#101318"; emphasis_0 "#ffc233"; emphasis_1 "#5fdc8a"; emphasis_2 "#8fe3a6"; emphasis_3 "#6fb6ff"; }
430
+ text_selected { base "#f2f5f8"; background "#1c3326"; emphasis_0 "#ffc233"; emphasis_1 "#5fdc8a"; emphasis_2 "#8fe3a6"; emphasis_3 "#6fb6ff"; }
431
+ ribbon_selected { base "#101318"; background "#8fe3a6"; emphasis_0 "#7a1a12"; emphasis_1 "#101318"; emphasis_2 "#101318"; emphasis_3 "#101318"; }
432
+ ribbon_unselected { base "#a6b1be"; background "#191e25"; emphasis_0 "#ff7b6e"; emphasis_1 "#f2f5f8"; emphasis_2 "#ffc233"; emphasis_3 "#6fb6ff"; }
433
+ table_title { base "#8fe3a6"; background "#101318"; emphasis_0 "#ffc233"; emphasis_1 "#5fdc8a"; emphasis_2 "#8fe3a6"; emphasis_3 "#6fb6ff"; }
434
+ table_cell_selected { base "#f2f5f8"; background "#1c3326"; emphasis_0 "#ffc233"; emphasis_1 "#5fdc8a"; emphasis_2 "#8fe3a6"; emphasis_3 "#6fb6ff"; }
435
+ table_cell_unselected { base "#f2f5f8"; background "#101318"; emphasis_0 "#ffc233"; emphasis_1 "#5fdc8a"; emphasis_2 "#8fe3a6"; emphasis_3 "#6fb6ff"; }
436
+ list_selected { base "#f2f5f8"; background "#1c3326"; emphasis_0 "#ffc233"; emphasis_1 "#5fdc8a"; emphasis_2 "#8fe3a6"; emphasis_3 "#6fb6ff"; }
437
+ list_unselected { base "#f2f5f8"; background "#101318"; emphasis_0 "#ffc233"; emphasis_1 "#5fdc8a"; emphasis_2 "#8fe3a6"; emphasis_3 "#6fb6ff"; }
438
+ frame_selected { base "#8fe3a6"; background "#101318"; emphasis_0 "#ffc233"; emphasis_1 "#5fdc8a"; emphasis_2 "#6fb6ff"; emphasis_3 "#ff7b6e"; }
439
+ frame_highlight { base "#ffc233"; background "#101318"; emphasis_0 "#ff7b6e"; emphasis_1 "#5fdc8a"; emphasis_2 "#8fe3a6"; emphasis_3 "#6fb6ff"; }
440
+ exit_code_success { base "#5fdc8a"; background "#101318"; emphasis_0 "#5fdc8a"; emphasis_1 "#5fdc8a"; emphasis_2 "#5fdc8a"; emphasis_3 "#5fdc8a"; }
441
+ exit_code_error { base "#ff7b6e"; background "#101318"; emphasis_0 "#ff7b6e"; emphasis_1 "#ff7b6e"; emphasis_2 "#ff7b6e"; emphasis_3 "#ff7b6e"; }
442
+ multiplayer_user_colors { player_1 "#6fb6ff"; player_2 "#ffc233"; player_3 "#5fdc8a"; player_4 "#ff7b6e"; player_5 "#8fe3a6"; player_6 "#c58bff"; player_7 "#3fd9cf"; player_8 "#ffd866"; player_9 "#86f0ad"; player_10 "#ff9a8e"; }
393
443
  }
394
444
  lanes-light {
395
- text_unselected { base "#17241b"; background "#f3f5f2"; emphasis_0 "#8a5a00"; emphasis_1 "#1b7142"; emphasis_2 "#2f6b45"; emphasis_3 "#2456a8"; }
396
- text_selected { base "#17241b"; background "#e5f0e6"; emphasis_0 "#8a5a00"; emphasis_1 "#1b7142"; emphasis_2 "#2f6b45"; emphasis_3 "#2456a8"; }
397
- ribbon_selected { base "#ffffff"; background "#2f6b45"; emphasis_0 "#ffd3cc"; emphasis_1 "#ffffff"; emphasis_2 "#ffffff"; emphasis_3 "#ffffff"; }
398
- ribbon_unselected { base "#56616d"; background "#ffffff"; emphasis_0 "#b7322a"; emphasis_1 "#17241b"; emphasis_2 "#8a5a00"; emphasis_3 "#2456a8"; }
399
- table_title { base "#2f6b45"; background "#f3f5f2"; emphasis_0 "#8a5a00"; emphasis_1 "#1b7142"; emphasis_2 "#2f6b45"; emphasis_3 "#2456a8"; }
400
- table_cell_selected { base "#17241b"; background "#e5f0e6"; emphasis_0 "#8a5a00"; emphasis_1 "#1b7142"; emphasis_2 "#2f6b45"; emphasis_3 "#2456a8"; }
401
- table_cell_unselected { base "#17241b"; background "#f3f5f2"; emphasis_0 "#8a5a00"; emphasis_1 "#1b7142"; emphasis_2 "#2f6b45"; emphasis_3 "#2456a8"; }
402
- list_selected { base "#17241b"; background "#e5f0e6"; emphasis_0 "#8a5a00"; emphasis_1 "#1b7142"; emphasis_2 "#2f6b45"; emphasis_3 "#2456a8"; }
403
- list_unselected { base "#17241b"; background "#f3f5f2"; emphasis_0 "#8a5a00"; emphasis_1 "#1b7142"; emphasis_2 "#2f6b45"; emphasis_3 "#2456a8"; }
404
- frame_selected { base "#2f6b45"; background "#f3f5f2"; emphasis_0 "#8a5a00"; emphasis_1 "#1b7142"; emphasis_2 "#2456a8"; emphasis_3 "#b7322a"; }
405
- frame_highlight { base "#8a5a00"; background "#f3f5f2"; emphasis_0 "#b7322a"; emphasis_1 "#1b7142"; emphasis_2 "#2f6b45"; emphasis_3 "#2456a8"; }
406
- exit_code_success { base "#1b7142"; background "#f3f5f2"; emphasis_0 "#1b7142"; emphasis_1 "#1b7142"; emphasis_2 "#1b7142"; emphasis_3 "#1b7142"; }
407
- exit_code_error { base "#b7322a"; background "#f3f5f2"; emphasis_0 "#b7322a"; emphasis_1 "#b7322a"; emphasis_2 "#b7322a"; emphasis_3 "#b7322a"; }
408
- multiplayer_user_colors { player_1 "#2456a8"; player_2 "#8a5a00"; player_3 "#1b7142"; player_4 "#b7322a"; player_5 "#2f6b45"; player_6 "#7a3fb8"; player_7 "#0f7b7b"; player_8 "#8f5a00"; player_9 "#1b7a38"; player_10 "#c2332a"; }
445
+ text_unselected { base "#111a14"; background "#f3f5f2"; emphasis_0 "#a04d00"; emphasis_1 "#0d7338"; emphasis_2 "#1f6f3f"; emphasis_3 "#1a56c4"; }
446
+ text_selected { base "#111a14"; background "#e5f0e6"; emphasis_0 "#a04d00"; emphasis_1 "#0d7338"; emphasis_2 "#1f6f3f"; emphasis_3 "#1a56c4"; }
447
+ ribbon_selected { base "#ffffff"; background "#1f6f3f"; emphasis_0 "#ffd3cc"; emphasis_1 "#ffffff"; emphasis_2 "#ffffff"; emphasis_3 "#ffffff"; }
448
+ ribbon_unselected { base "#4a5661"; background "#ffffff"; emphasis_0 "#c41c1c"; emphasis_1 "#111a14"; emphasis_2 "#a04d00"; emphasis_3 "#1a56c4"; }
449
+ table_title { base "#1f6f3f"; background "#f3f5f2"; emphasis_0 "#a04d00"; emphasis_1 "#0d7338"; emphasis_2 "#1f6f3f"; emphasis_3 "#1a56c4"; }
450
+ table_cell_selected { base "#111a14"; background "#e5f0e6"; emphasis_0 "#a04d00"; emphasis_1 "#0d7338"; emphasis_2 "#1f6f3f"; emphasis_3 "#1a56c4"; }
451
+ table_cell_unselected { base "#111a14"; background "#f3f5f2"; emphasis_0 "#a04d00"; emphasis_1 "#0d7338"; emphasis_2 "#1f6f3f"; emphasis_3 "#1a56c4"; }
452
+ list_selected { base "#111a14"; background "#e5f0e6"; emphasis_0 "#a04d00"; emphasis_1 "#0d7338"; emphasis_2 "#1f6f3f"; emphasis_3 "#1a56c4"; }
453
+ list_unselected { base "#111a14"; background "#f3f5f2"; emphasis_0 "#a04d00"; emphasis_1 "#0d7338"; emphasis_2 "#1f6f3f"; emphasis_3 "#1a56c4"; }
454
+ frame_selected { base "#1f6f3f"; background "#f3f5f2"; emphasis_0 "#a04d00"; emphasis_1 "#0d7338"; emphasis_2 "#1a56c4"; emphasis_3 "#c41c1c"; }
455
+ frame_highlight { base "#a04d00"; background "#f3f5f2"; emphasis_0 "#c41c1c"; emphasis_1 "#0d7338"; emphasis_2 "#1f6f3f"; emphasis_3 "#1a56c4"; }
456
+ exit_code_success { base "#0d7338"; background "#f3f5f2"; emphasis_0 "#0d7338"; emphasis_1 "#0d7338"; emphasis_2 "#0d7338"; emphasis_3 "#0d7338"; }
457
+ exit_code_error { base "#c41c1c"; background "#f3f5f2"; emphasis_0 "#c41c1c"; emphasis_1 "#c41c1c"; emphasis_2 "#c41c1c"; emphasis_3 "#c41c1c"; }
458
+ multiplayer_user_colors { player_1 "#1a56c4"; player_2 "#a04d00"; player_3 "#0d7338"; player_4 "#c41c1c"; player_5 "#1f6f3f"; player_6 "#8228c4"; player_7 "#006f73"; player_8 "#a85200"; player_9 "#0e7a3a"; player_10 "#d0221a"; }
409
459
  }
410
460
  }
411
461
  theme_dark "lanes-dark"
@@ -414,25 +464,25 @@ web_client {
414
464
  font "monospace"
415
465
  theme {
416
466
  background "#101318"
417
- foreground "#edf1f5"
418
- black "#7d8896"
419
- red "#f28b7d"
420
- green "#6fd39a"
421
- yellow "#e5b84a"
422
- blue "#7fb3f2"
423
- magenta "#c9a6ff"
424
- cyan "#6fd6d0"
425
- white "#9ca7b5"
426
- bright_black "#8a95a3"
427
- bright_red "#ff9d96"
428
- bright_green "#8fe0a3"
429
- bright_yellow "#f7cf7f"
430
- bright_blue "#a5cbff"
431
- bright_magenta "#dcc6ff"
432
- bright_cyan "#9ae6e0"
433
- bright_white "#edf1f5"
434
- selection_background "#1a2721"
435
- selection_foreground "#edf1f5"
467
+ foreground "#f2f5f8"
468
+ black "#8792a0"
469
+ red "#ff7b6e"
470
+ green "#5fdc8a"
471
+ yellow "#ffc233"
472
+ blue "#6fb6ff"
473
+ magenta "#c58bff"
474
+ cyan "#3fd9cf"
475
+ white "#a6b1be"
476
+ bright_black "#97a3b1"
477
+ bright_red "#ff9a8e"
478
+ bright_green "#86f0ad"
479
+ bright_yellow "#ffd866"
480
+ bright_blue "#98cbff"
481
+ bright_magenta "#dcb2ff"
482
+ bright_cyan "#7deee6"
483
+ bright_white "#f2f5f8"
484
+ selection_background "#1c3326"
485
+ selection_foreground "#f2f5f8"
436
486
  }
437
487
  }
438
488
  // END lanes tokens
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@vibelord/lanes",
3
- "version": "1.1.1",
4
- "commit": "34ec2d7ba9a5f13e72c812d3e2fce116e170e260",
5
- "digest": "sha256:c10719aaa2279205460136ed3589f349d1e22dce32ddfb77584586d68ae1b3a4",
3
+ "version": "1.3.0",
4
+ "commit": "cf8bfb3df12fb6210350adb6b3e481f29051ffae",
5
+ "digest": "sha256:7e3cce373458d09b77c588dfb98b00777ceda08df504b319bcdf7d26cc3cc206",
6
6
  "executables": [
7
7
  "bin/lanes",
8
8
  "client/install.sh",
@@ -11,6 +11,8 @@
11
11
  "client/lanes-window",
12
12
  "client/omarchy/discover.sh",
13
13
  "container/lane",
14
+ "container/lane-edit",
15
+ "container/lane-mcp",
14
16
  "container/lane-run",
15
17
  "container/lane-signal",
16
18
  "holders/exec.sh",
@@ -18,6 +20,7 @@
18
20
  "holders/tmux.sh",
19
21
  "holders/zmx.sh",
20
22
  "install.sh",
23
+ "install/enable-docs.sh",
21
24
  "install/enable-studio-host.sh",
22
25
  "install/enable-web.sh",
23
26
  "install/host-install.sh",