@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.
- package/README.md +36 -5
- package/bin/lanes-install.mjs +166 -13
- package/lib/plan.mjs +113 -3
- package/package.json +1 -1
- package/payload/lanes/README.md +47 -23
- package/payload/lanes/VERSION +1 -1
- package/payload/lanes/bin/lanes +2753 -202
- package/payload/lanes/client/ghostty/lanes-palette-light.conf +19 -19
- package/payload/lanes/client/ghostty/lanes-palette.conf +19 -19
- package/payload/lanes/client/lanes-launch +289 -40
- package/payload/lanes/client/lanes-window +15 -3
- package/payload/lanes/container/hooks/claude-settings.json +1 -0
- package/payload/lanes/container/lane +147 -92
- package/payload/lanes/container/lane-edit +100 -0
- package/payload/lanes/container/lane-mcp +1150 -0
- package/payload/lanes/container/lane-signal +109 -29
- package/payload/lanes/container/lane_core.py +1318 -0
- package/payload/lanes/container/vimrc +31 -0
- package/payload/lanes/docs/ARCHITECTURE.md +793 -16
- package/payload/lanes/docs/DESIGN.md +127 -46
- package/payload/lanes/docs/INSTALL.md +175 -18
- package/payload/lanes/docs/RUNBOOK.md +968 -76
- package/payload/lanes/docs/SHARING.md +3 -2
- package/payload/lanes/docs/USERGUIDE.md +967 -150
- package/payload/lanes/holders.lock +5 -0
- package/payload/lanes/install/enable-docs.sh +78 -0
- package/payload/lanes/install/enable-web.sh +2 -0
- package/payload/lanes/install/host-install.sh +197 -7
- package/payload/lanes/install/services.py +148 -18
- package/payload/lanes/install.sh +68 -7
- package/payload/lanes/libexec/agent_state.py +74 -1
- package/payload/lanes/libexec/cursor_sync.py +6 -0
- package/payload/lanes/libexec/device_sync.py +47 -10
- package/payload/lanes/libexec/docs_store.py +995 -0
- package/payload/lanes/libexec/docsd.py +2010 -0
- package/payload/lanes/libexec/economy_poll.py +1 -0
- package/payload/lanes/libexec/enrold.py +41 -22
- package/payload/lanes/libexec/keys.py +54 -23
- package/payload/lanes/libexec/lanes_lib.py +620 -22
- package/payload/lanes/libexec/mdlite.py +575 -0
- package/payload/lanes/libexec/members.py +21 -2
- package/payload/lanes/libexec/notify.py +169 -29
- package/payload/lanes/libexec/registrations.py +216 -18
- package/payload/lanes/libexec/session_actions.py +182 -14
- package/payload/lanes/libexec/sidebar +11 -3
- package/payload/lanes/libexec/sidebar.py +2232 -365
- package/payload/lanes/libexec/statusd.py +231 -12
- package/payload/lanes/libexec/studio_host.py +8 -1
- package/payload/lanes/libexec/tokens.py +148 -35
- package/payload/lanes/libexec/tokens.sh +86 -2
- package/payload/lanes/libexec/update.py +1044 -0
- package/payload/lanes/libexec/uploads.py +538 -0
- package/payload/lanes/libexec/users.py +614 -0
- package/payload/lanes/libexec/view.sh +34 -6
- package/payload/lanes/systemd/lanes-docsd.service +29 -0
- package/payload/lanes/systemd/lanes-ensure.service +3 -0
- package/payload/lanes/systemd/lanes-gc.service +1 -1
- package/payload/lanes/systemd/lanes-update.service +17 -0
- package/payload/lanes/systemd/lanes-update.timer +17 -0
- package/payload/lanes/web/Caddyfile +24 -1
- package/payload/lanes/web/README.md +165 -28
- package/payload/lanes/web/browser.js +4 -8
- package/payload/lanes/web/docs.css +52 -0
- package/payload/lanes/web/docs.js +36 -0
- package/payload/lanes/web/mobile.css +17 -17
- package/payload/lanes/web/mobile.js +45 -32
- package/payload/lanes/web/upload.css +17 -0
- package/payload/lanes/web/upload.js +84 -0
- package/payload/lanes/workspaces.example/README.md +3 -1
- package/payload/lanes/zellij/config.kdl +106 -56
- 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: #
|
|
6
|
+
--lanes-accent-wash: #1c3326;
|
|
7
7
|
--lanes-accent-line: #4b6955;
|
|
8
8
|
--lanes-press: #26332c;
|
|
9
9
|
--lanes-chip: #ffffff0f;
|
|
10
|
-
--lanes-ink: #
|
|
11
|
-
--lanes-ink-dim: #
|
|
12
|
-
--lanes-ink-faint: #
|
|
13
|
-
--lanes-accent: #
|
|
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: #
|
|
17
|
-
--lanes-tone-attention: #
|
|
18
|
-
--lanes-tone-working: #
|
|
19
|
-
--lanes-tone-done: #
|
|
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: #
|
|
33
|
-
--lanes-ink-dim: #
|
|
34
|
-
--lanes-ink-faint: #
|
|
35
|
-
--lanes-accent: #
|
|
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: #
|
|
39
|
-
--lanes-tone-attention: #
|
|
40
|
-
--lanes-tone-working: #
|
|
41
|
-
--lanes-tone-done: #
|
|
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: ['#
|
|
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:
|
|
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(
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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 = '
|
|
119
|
-
'
|
|
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
|
|
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)
|
|
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
|
|
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') {
|
|
348
|
-
if (ev.key
|
|
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 (
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
198
|
-
//
|
|
199
|
-
// at most 3 viewports)
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
//
|
|
205
|
-
|
|
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 "#
|
|
380
|
-
text_selected { base "#
|
|
381
|
-
ribbon_selected { base "#101318"; background "#
|
|
382
|
-
ribbon_unselected { base "#
|
|
383
|
-
table_title { base "#
|
|
384
|
-
table_cell_selected { base "#
|
|
385
|
-
table_cell_unselected { base "#
|
|
386
|
-
list_selected { base "#
|
|
387
|
-
list_unselected { base "#
|
|
388
|
-
frame_selected { base "#
|
|
389
|
-
frame_highlight { base "#
|
|
390
|
-
exit_code_success { base "#
|
|
391
|
-
exit_code_error { base "#
|
|
392
|
-
multiplayer_user_colors { player_1 "#
|
|
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 "#
|
|
396
|
-
text_selected { base "#
|
|
397
|
-
ribbon_selected { base "#ffffff"; background "#
|
|
398
|
-
ribbon_unselected { base "#
|
|
399
|
-
table_title { base "#
|
|
400
|
-
table_cell_selected { base "#
|
|
401
|
-
table_cell_unselected { base "#
|
|
402
|
-
list_selected { base "#
|
|
403
|
-
list_unselected { base "#
|
|
404
|
-
frame_selected { base "#
|
|
405
|
-
frame_highlight { base "#
|
|
406
|
-
exit_code_success { base "#
|
|
407
|
-
exit_code_error { base "#
|
|
408
|
-
multiplayer_user_colors { player_1 "#
|
|
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 "#
|
|
418
|
-
black "#
|
|
419
|
-
red "#
|
|
420
|
-
green "#
|
|
421
|
-
yellow "#
|
|
422
|
-
blue "#
|
|
423
|
-
magenta "#
|
|
424
|
-
cyan "#
|
|
425
|
-
white "#
|
|
426
|
-
bright_black "#
|
|
427
|
-
bright_red "#
|
|
428
|
-
bright_green "#
|
|
429
|
-
bright_yellow "#
|
|
430
|
-
bright_blue "#
|
|
431
|
-
bright_magenta "#
|
|
432
|
-
bright_cyan "#
|
|
433
|
-
bright_white "#
|
|
434
|
-
selection_background "#
|
|
435
|
-
selection_foreground "#
|
|
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
|
package/payload/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibelord/lanes",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"commit": "
|
|
5
|
-
"digest": "sha256:
|
|
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",
|