offsite 0.0.3 → 0.0.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.
Files changed (114) hide show
  1. package/README.md +18 -7
  2. package/apps/server/dist/main.mjs +5767 -0
  3. package/apps/server/dist/main.mjs.map +1 -0
  4. package/apps/web/dist/assets/{angular-html-ji4UO7hp.js → angular-html-prQpjz7s.js} +1 -1
  5. package/apps/web/dist/assets/{angular-ts-CkobLhDa.js → angular-ts-BE4HJ5Uh.js} +1 -1
  6. package/apps/web/dist/assets/{apl-DAjPJTs4.js → apl-CJ-NIQ-7.js} +1 -1
  7. package/apps/web/dist/assets/{astro-DlwA-zmc.js → astro-CVkyXRVY.js} +1 -1
  8. package/apps/web/dist/assets/{blade-wTlHdbgB.js → blade-CQFMHse_.js} +1 -1
  9. package/apps/web/dist/assets/{c-HyCIpwKL.js → c-gqNWYRL8.js} +1 -1
  10. package/apps/web/dist/assets/{chapel-SOYLpFSa.js → chapel-CQ9_BV4j.js} +1 -1
  11. package/apps/web/dist/assets/{cobol-DlvLD04V.js → cobol-D-9v184m.js} +1 -1
  12. package/apps/web/dist/assets/{coffee-CGp5oJTY.js → coffee-BE0tIOLL.js} +1 -1
  13. package/apps/web/dist/assets/{cpp-CzmH13Nm.js → cpp-CzOLC-NF.js} +1 -1
  14. package/apps/web/dist/assets/{crystal-zS14MbR_.js → crystal-f1GcBNi_.js} +1 -1
  15. package/apps/web/dist/assets/{css-DQEQn59R.js → css-DlqRAumc.js} +1 -1
  16. package/apps/web/dist/assets/{edge-Bkv0KzZU.js → edge-DQkkPJaW.js} +1 -1
  17. package/apps/web/dist/assets/{elixir-B8tse0js.js → elixir-ClG78o74.js} +1 -1
  18. package/apps/web/dist/assets/{elm-F2InAxd7.js → elm-DZDmCmuw.js} +1 -1
  19. package/apps/web/dist/assets/{erb-CAIamwOm.js → erb-B1mu0-DN.js} +1 -1
  20. package/apps/web/dist/assets/{git-rebase-U0z47rUi.js → git-rebase-x1gs-rK2.js} +1 -1
  21. package/apps/web/dist/assets/{glimmer-js-Bg_zPoN-.js → glimmer-js-Cjos1igW.js} +1 -1
  22. package/apps/web/dist/assets/{glimmer-ts-v3suqAPH.js → glimmer-ts-CrR-PFvn.js} +1 -1
  23. package/apps/web/dist/assets/{glsl-8io-Pcmd.js → glsl-DR0G8e99.js} +1 -1
  24. package/apps/web/dist/assets/{graphql-B_q9cV-C.js → graphql-DKmrBZt7.js} +1 -1
  25. package/apps/web/dist/assets/{hack-DosfNWDX.js → hack-Brs_j9Xv.js} +1 -1
  26. package/apps/web/dist/assets/{haml-DcBtkGn5.js → haml-8Lc-lF96.js} +1 -1
  27. package/apps/web/dist/assets/{handlebars-2F0KxPFu.js → handlebars-Ds8WdPdv.js} +1 -1
  28. package/apps/web/dist/assets/{html-DrWZZyp3.js → html-C0gvYDho.js} +1 -1
  29. package/apps/web/dist/assets/{html-derivative-BaV8vDp-.js → html-derivative-CU9dkErg.js} +1 -1
  30. package/apps/web/dist/assets/{http-Dwo7FxJA.js → http-BE0w0Fty.js} +1 -1
  31. package/apps/web/dist/assets/{hurl-9r6SjmMP.js → hurl-DqZk6YcW.js} +1 -1
  32. package/apps/web/dist/assets/index-0B-Gdd7b.css +2 -0
  33. package/apps/web/dist/assets/index-rgKS_aMw.js +1148 -0
  34. package/apps/web/dist/assets/{java-CAkt7S2h.js → java-PSSQ0YmM.js} +1 -1
  35. package/apps/web/dist/assets/{javascript-CMo8GEjU.js → javascript-Ci3r4rzs.js} +1 -1
  36. package/apps/web/dist/assets/{jinja-XhRt7Tzv.js → jinja-lLTko9Wl.js} +1 -1
  37. package/apps/web/dist/assets/{jison-BXZQEb4C.js → jison-1BpHD5UK.js} +1 -1
  38. package/apps/web/dist/assets/{json-SyJDQmRp.js → json-BAUn76M4.js} +1 -1
  39. package/apps/web/dist/assets/{jsx-B76-zKzH.js → jsx-B8sFp1vz.js} +1 -1
  40. package/apps/web/dist/assets/{julia-Ca5lbuMA.js → julia-C4TZ4EL9.js} +1 -1
  41. package/apps/web/dist/assets/{just-CDt2RojJ.js → just-D5qIrqkp.js} +1 -1
  42. package/apps/web/dist/assets/{latex-D9bybvWo.js → latex-BL7XYxFg.js} +1 -1
  43. package/apps/web/dist/assets/{liquid-DMp-tByG.js → liquid-DBx9Hq7B.js} +1 -1
  44. package/apps/web/dist/assets/{lua-zNnfh_4o.js → lua-BqjicQyd.js} +1 -1
  45. package/apps/web/dist/assets/{marko-Ce5B11L0.js → marko-CHgy7yaY.js} +1 -1
  46. package/apps/web/dist/assets/{mdc-Btapt9E8.js → mdc-DOloBwb8.js} +1 -1
  47. package/apps/web/dist/assets/{nginx-Bzjverzh.js → nginx-g7I1HPwV.js} +1 -1
  48. package/apps/web/dist/assets/{nim-D6zpNhUX.js → nim-O6zJV1LD.js} +1 -1
  49. package/apps/web/dist/assets/{org-DTy-FvFD.js → org-HLg4-757.js} +1 -1
  50. package/apps/web/dist/assets/{perl-BV8Dxusy.js → perl-B3cDKTai.js} +1 -1
  51. package/apps/web/dist/assets/{php-BjkIa26S.js → php-2eDApoU3.js} +1 -1
  52. package/apps/web/dist/assets/{pug-DdfmTHk6.js → pug-itzGn-nK.js} +1 -1
  53. package/apps/web/dist/assets/{qml-BWLYImhB.js → qml-sU0VtUmf.js} +1 -1
  54. package/apps/web/dist/assets/{r-CKc9klse.js → r-C6mhPqgr.js} +1 -1
  55. package/apps/web/dist/assets/{razor-YcF2a59Y.js → razor-CSzRtbW7.js} +1 -1
  56. package/apps/web/dist/assets/{regexp-D3esVJ1X.js → regexp-BmdBJRV9.js} +1 -1
  57. package/apps/web/dist/assets/{rst-DVaj4AQD.js → rst-BRxUd6sp.js} +1 -1
  58. package/apps/web/dist/assets/{ruby-DBJQEVev.js → ruby-CQHHOfmX.js} +1 -1
  59. package/apps/web/dist/assets/{sas-NSuLkGD1.js → sas-DlaL8b25.js} +1 -1
  60. package/apps/web/dist/assets/{scss-TMy8m-so.js → scss-Cc71ABWp.js} +1 -1
  61. package/apps/web/dist/assets/{shellscript-BVuIsFkY.js → shellscript-CzA_LIIm.js} +1 -1
  62. package/apps/web/dist/assets/{shellsession-CV7MHIu6.js → shellsession-DESJlgp3.js} +1 -1
  63. package/apps/web/dist/assets/{soy-DDY0EoKE.js → soy-Nkvq88YN.js} +1 -1
  64. package/apps/web/dist/assets/{sql-CoZqqtiv.js → sql-CtPxrO7T.js} +1 -1
  65. package/apps/web/dist/assets/{stata-B8C1noIb.js → stata-BacEfVTr.js} +1 -1
  66. package/apps/web/dist/assets/{surrealql-BYiUJt-V.js → surrealql-CYW10JNe.js} +1 -1
  67. package/apps/web/dist/assets/{svelte-DGeZ8dPs.js → svelte-D3_tCKGs.js} +1 -1
  68. package/apps/web/dist/assets/{templ-Dn21SE_C.js → templ-BGKIfY_b.js} +1 -1
  69. package/apps/web/dist/assets/{tex-Da9GBCsM.js → tex-BkkYOaXX.js} +1 -1
  70. package/apps/web/dist/assets/{ts-tags-BuL1T6K3.js → ts-tags-B02tnbpZ.js} +1 -1
  71. package/apps/web/dist/assets/{tsx-DiLCnfgl.js → tsx-D3MB1IUf.js} +1 -1
  72. package/apps/web/dist/assets/{twig-Cx1peiH-.js → twig-CPhw2wGK.js} +1 -1
  73. package/apps/web/dist/assets/{typescript-pVxvHJ4R.js → typescript-B7U4faGm.js} +1 -1
  74. package/apps/web/dist/assets/{typst-CJQbitzj.js → typst-CN9EZswy.js} +1 -1
  75. package/apps/web/dist/assets/{vue-CXKyVnbB.js → vue-YH8dfjo_.js} +1 -1
  76. package/apps/web/dist/assets/{vue-html-AQjnBMTh.js → vue-html-NwX2xzgp.js} +1 -1
  77. package/apps/web/dist/assets/{vue-vine-DYxt9AbK.js → vue-vine-DoRmnjLj.js} +1 -1
  78. package/apps/web/dist/assets/{xml-CEJ01uJ1.js → xml-DgfgLpPc.js} +1 -1
  79. package/apps/web/dist/assets/{xsl-CULFBy6v.js → xsl-CEwQRUK_.js} +1 -1
  80. package/apps/web/dist/assets/{yaml-BNf68xM_.js → yaml-CEFOzF95.js} +1 -1
  81. package/apps/web/dist/icon.svg +5 -0
  82. package/apps/web/dist/index.html +2 -2
  83. package/apps/web/dist/sw.js +19 -8
  84. package/bin/offsite.js +1 -1
  85. package/package.json +5 -1
  86. package/apps/server/dist/args.js +0 -11
  87. package/apps/server/dist/claude.js +0 -245
  88. package/apps/server/dist/config.js +0 -191
  89. package/apps/server/dist/content.js +0 -172
  90. package/apps/server/dist/db.js +0 -13
  91. package/apps/server/dist/files.js +0 -50
  92. package/apps/server/dist/git.js +0 -133
  93. package/apps/server/dist/history.js +0 -137
  94. package/apps/server/dist/hub.js +0 -10
  95. package/apps/server/dist/main.js +0 -70
  96. package/apps/server/dist/projects.js +0 -92
  97. package/apps/server/dist/protocol.js +0 -20
  98. package/apps/server/dist/push.js +0 -152
  99. package/apps/server/dist/run.js +0 -375
  100. package/apps/server/dist/runs.js +0 -102
  101. package/apps/server/dist/schemes.js +0 -262
  102. package/apps/server/dist/sessions.js +0 -99
  103. package/apps/server/dist/socket.js +0 -193
  104. package/apps/server/dist/storage.js +0 -71
  105. package/apps/server/dist/terminal/alerts.js +0 -145
  106. package/apps/server/dist/terminal/process.js +0 -207
  107. package/apps/server/dist/terminal/shell-check.js +0 -91
  108. package/apps/server/dist/terminal/shell-integration.js +0 -112
  109. package/apps/server/dist/terminal/terminal.js +0 -402
  110. package/apps/web/dist/assets/index-CIBt6t2M.css +0 -2
  111. package/apps/web/dist/assets/index-CPpMMYyZ.js +0 -902
  112. /package/apps/server/src/{terminal → services/terminal}/shell-integration/bash/offsite.bash +0 -0
  113. /package/apps/server/src/{terminal → services/terminal}/shell-integration/fish/offsite.fish +0 -0
  114. /package/apps/server/src/{terminal → services/terminal}/shell-integration/zsh/.zshenv +0 -0
@@ -1,402 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { mkdirSync, statSync, writeFileSync } from 'node:fs';
3
- import path from 'node:path';
4
- import { decodeFrame, encodeFrame, FRAME_INPUT, FRAME_OUTPUT, FRAME_REPLAY, FRAME_REPLY, } from '../protocol.js';
5
- import { pushActive, sendPush } from '../push.js';
6
- import { home } from '../storage.js';
7
- import { ALERT_DELAY, Alerts, Scanner } from './alerts.js';
8
- import { busyProgram, currentDirectory, gitBranch, startShell, } from './process.js';
9
- const HISTORY_LIMIT = 2 * 1024 * 1024;
10
- const OUTPUT_LIMIT = 1024 * 1024;
11
- const STALL_LIMIT = 10_000;
12
- const SILENCE_LIMIT = 90_000;
13
- const KILL_DELAY = 3000;
14
- const pasteTypes = {
15
- 'image/png': 'png',
16
- 'image/jpeg': 'jpg',
17
- 'image/gif': 'gif',
18
- 'image/webp': 'webp',
19
- };
20
- const instance = `${Date.now()}-${randomUUID()}`;
21
- const projects = new Map();
22
- const terminals = new Map();
23
- const connections = new Map();
24
- const paused = new Set();
25
- const branchFailures = new Set();
26
- let nextTab = 0;
27
- let draining = null;
28
- let watching = false;
29
- if (process.platform !== 'win32')
30
- setInterval(() => void watchBranches(), 1000);
31
- export async function terminalRequest(client, message) {
32
- if (message.type === 'attach') {
33
- attach(client);
34
- return;
35
- }
36
- const connection = connections.get(client);
37
- if (connection === undefined)
38
- throw new Error('the terminal is not attached');
39
- await request(connection, message);
40
- }
41
- export function terminalInput(client, frame) {
42
- const connection = connections.get(client);
43
- if (connection === undefined) {
44
- console.warn('terminal input from a browser that is not attached');
45
- return;
46
- }
47
- input(connection, frame);
48
- }
49
- export function detachTerminal(client) {
50
- const connection = connections.get(client);
51
- if (connection === undefined)
52
- return;
53
- connections.delete(client);
54
- for (const terminal of terminals.values())
55
- if (terminal.owner === connection)
56
- terminal.owner = null;
57
- }
58
- export async function busyTerminal(id) {
59
- const terminal = terminals.get(id);
60
- if (terminal === undefined)
61
- return { program: null };
62
- try {
63
- return { program: await busyProgram(terminal.pty.pid) };
64
- }
65
- catch (error) {
66
- console.warn(`could not tell if terminal ${id} is busy: ${error}`);
67
- return { error: String(error) };
68
- }
69
- }
70
- export function savePastedImage(type, data) {
71
- const extension = pasteTypes[type];
72
- if (extension === undefined)
73
- throw new Error('pasted image must be a PNG, JPEG, GIF or WebP image');
74
- const directory = path.join(home, 'paste');
75
- mkdirSync(directory, { recursive: true });
76
- const file = path.join(directory, `${Date.now()}-${randomUUID()}.${extension}`);
77
- writeFileSync(file, data);
78
- return file;
79
- }
80
- function attach(client) {
81
- detachTerminal(client);
82
- const connection = { client, sizes: new Map() };
83
- connections.set(client, connection);
84
- send(connection, {
85
- type: 'active',
86
- layout: layout(),
87
- instance,
88
- branches: Object.fromEntries([...terminals].flatMap(([id, terminal]) => terminal.branch === null ? [] : [[id, terminal.branch]])),
89
- });
90
- for (const project of projects.values())
91
- for (const tab of project.tabs)
92
- forward(connection, tab.id, true, Buffer.concat(terminals.get(tab.id)?.history ?? []));
93
- }
94
- async function request(connection, message) {
95
- switch (message.type) {
96
- case 'resize': {
97
- const terminal = terminals.get(message.id);
98
- if (terminal === undefined)
99
- return;
100
- connection.sizes.set(message.id, {
101
- cols: message.cols,
102
- rows: message.rows,
103
- });
104
- terminal.owner = connection;
105
- terminal.pty.resize(message.cols, message.rows);
106
- return;
107
- }
108
- case 'createTab':
109
- await createTab(message.cwd);
110
- break;
111
- case 'closeTab':
112
- closeTab(message.id);
113
- break;
114
- case 'renameTab': {
115
- const tab = findTab(message.id);
116
- if (tab === undefined)
117
- return;
118
- tab.customName = message.name === '' ? null : message.name;
119
- break;
120
- }
121
- case 'minimizeTab': {
122
- const project = [...projects.values()].find((item) => item.tabs.some((tab) => tab.id === message.id));
123
- if (project === undefined)
124
- return;
125
- if (!project.tabs.some((tab) => tab.id !== message.id && !tab.minimized))
126
- return;
127
- const index = project.tabs.findIndex((tab) => tab.id === message.id);
128
- project.tabs[index].minimized = true;
129
- if (project.activeTab === message.id) {
130
- const next = nearestVisible(project.tabs, index);
131
- project.activeTab = next === undefined ? null : project.tabs[next].id;
132
- }
133
- break;
134
- }
135
- case 'select': {
136
- const project = projects.get(message.cwd) ?? {
137
- tabs: [],
138
- activeTab: null,
139
- };
140
- projects.set(message.cwd, project);
141
- const tab = project.tabs.find((item) => item.id === message.tabId);
142
- if (tab !== undefined) {
143
- tab.minimized = false;
144
- project.activeTab = tab.id;
145
- }
146
- if (project.tabs.length === 0)
147
- await createTab(message.cwd);
148
- break;
149
- }
150
- }
151
- publish();
152
- }
153
- function input(connection, bytes) {
154
- let frame;
155
- try {
156
- frame = decodeFrame(bytes);
157
- }
158
- catch (error) {
159
- console.warn(`ignoring terminal input: ${error}`);
160
- return;
161
- }
162
- const terminal = terminals.get(frame.id);
163
- if (terminal === undefined) {
164
- console.warn(`input for terminal ${frame.id}, which does not exist`);
165
- return;
166
- }
167
- if (frame.kind === FRAME_REPLY) {
168
- if (terminal.owner !== null && terminal.owner !== connection)
169
- return;
170
- }
171
- else if (frame.kind === FRAME_INPUT) {
172
- const size = connection.sizes.get(frame.id);
173
- if (terminal.owner !== connection && size !== undefined) {
174
- terminal.owner = connection;
175
- terminal.pty.resize(size.cols, size.rows);
176
- }
177
- }
178
- else {
179
- console.warn(`ignoring terminal input of unknown kind ${frame.kind}`);
180
- return;
181
- }
182
- terminal.pty.write(Buffer.from(frame.data));
183
- }
184
- async function createTab(cwd) {
185
- if (!statSync(cwd, { throwIfNoEntry: false })?.isDirectory()) {
186
- console.error(`${cwd} is not a folder, terminal not created`);
187
- return;
188
- }
189
- const project = projects.get(cwd) ?? { tabs: [], activeTab: null };
190
- projects.set(cwd, project);
191
- const active = project.activeTab === null ? undefined : terminals.get(project.activeTab);
192
- let directory = cwd;
193
- if (active !== undefined) {
194
- try {
195
- directory = await currentDirectory(active.pty.pid);
196
- }
197
- catch (error) {
198
- console.warn(`starting in the project folder, could not read the folder of the active terminal: ${error}`);
199
- }
200
- }
201
- nextTab += 1;
202
- const id = nextTab;
203
- let pty;
204
- try {
205
- pty = startShell(directory);
206
- }
207
- catch (error) {
208
- console.error(`could not start terminal ${id}: ${error}`);
209
- return;
210
- }
211
- const terminal = {
212
- pty,
213
- history: [],
214
- historySize: 0,
215
- branch: null,
216
- owner: null,
217
- alerts: new Alerts(),
218
- scanner: new Scanner(),
219
- };
220
- terminals.set(id, terminal);
221
- project.tabs.push({ id, customName: null, minimized: false });
222
- project.activeTab = id;
223
- pty.onData((data) => output(id, terminal, data));
224
- pty.onExit(() => {
225
- terminals.delete(id);
226
- paused.delete(pty);
227
- branchFailures.delete(id);
228
- if (closeTab(id))
229
- publish();
230
- });
231
- }
232
- function closeTab(id) {
233
- const project = [...projects.values()].find((item) => item.tabs.some((tab) => tab.id === id));
234
- if (project === undefined)
235
- return false;
236
- const index = project.tabs.findIndex((tab) => tab.id === id);
237
- project.tabs.splice(index, 1);
238
- if (project.activeTab === id) {
239
- let next = nearestVisible(project.tabs, index);
240
- if (next === undefined && project.tabs.length > 0)
241
- next = Math.min(index, project.tabs.length - 1);
242
- if (next !== undefined)
243
- project.tabs[next].minimized = false;
244
- project.activeTab = next === undefined ? null : project.tabs[next].id;
245
- }
246
- const terminal = terminals.get(id);
247
- if (terminal === undefined)
248
- return true;
249
- terminal.pty.kill();
250
- if (process.platform !== 'win32')
251
- setTimeout(() => {
252
- if (terminals.get(id) === terminal)
253
- terminal.pty.kill('SIGKILL');
254
- }, KILL_DELAY);
255
- return true;
256
- }
257
- function output(id, terminal, data) {
258
- terminal.history.push(data);
259
- terminal.historySize += data.length;
260
- while (terminal.historySize > HISTORY_LIMIT && terminal.history.length > 1) {
261
- terminal.historySize -= terminal.history[0].length;
262
- terminal.history.shift();
263
- }
264
- for (const connection of connections.values())
265
- forward(connection, id, false, data);
266
- scan(id, terminal, data);
267
- if ([...connections.values()].some((connection) => connection.client.socket.bufferedAmount > OUTPUT_LIMIT)) {
268
- terminal.pty.pause();
269
- paused.add(terminal.pty);
270
- draining ??= drain();
271
- }
272
- }
273
- function drain() {
274
- const progress = new Map();
275
- return setInterval(() => {
276
- let full = false;
277
- for (const connection of connections.values()) {
278
- const queued = connection.client.socket.bufferedAmount;
279
- if (queued <= OUTPUT_LIMIT)
280
- continue;
281
- const now = Date.now();
282
- const last = progress.get(connection) ?? { queued: Infinity, at: now };
283
- if (queued < last.queued)
284
- last.at = now;
285
- last.queued = queued;
286
- progress.set(connection, last);
287
- if (now - last.at > STALL_LIMIT) {
288
- console.warn('a browser is not reading fast enough, disconnecting it');
289
- connection.client.socket.terminate();
290
- continue;
291
- }
292
- full = true;
293
- }
294
- if (full)
295
- return;
296
- if (draining !== null)
297
- clearInterval(draining);
298
- draining = null;
299
- for (const pty of paused)
300
- pty.resume();
301
- paused.clear();
302
- }, 10);
303
- }
304
- function scan(id, terminal, data) {
305
- if (!pushActive()) {
306
- terminal.scanner = new Scanner();
307
- return;
308
- }
309
- const alert = terminal.scanner.scan(data);
310
- if (alert === null || browserOpen())
311
- return;
312
- const started = alert.type === 'bell'
313
- ? terminal.alerts.startBell()
314
- : terminal.alerts.startNotification();
315
- if (!started)
316
- return;
317
- const windowTitle = terminal.scanner.title;
318
- setTimeout(() => push(id, alert, windowTitle), ALERT_DELAY);
319
- }
320
- function push(id, alert, windowTitle) {
321
- const terminal = terminals.get(id);
322
- if (terminal === undefined)
323
- return;
324
- let title = '';
325
- let body;
326
- if (alert.type === 'bell') {
327
- if (!terminal.alerts.finishBell())
328
- return;
329
- body = 'Bell';
330
- }
331
- else {
332
- title = alert.title;
333
- body = alert.body;
334
- }
335
- if (browserOpen())
336
- return;
337
- const entry = [...projects].find(([, project]) => project.tabs.some((tab) => tab.id === id));
338
- if (entry === undefined)
339
- return;
340
- const tab = findTab(id);
341
- if (title === '')
342
- title = tab?.customName ?? (windowTitle || 'Terminal');
343
- void sendPush({ title, body, cwd: entry[0], tabId: id });
344
- }
345
- function browserOpen() {
346
- return [...connections.values()].some((connection) => Date.now() - connection.client.heardAt < SILENCE_LIMIT);
347
- }
348
- async function watchBranches() {
349
- if (watching)
350
- return;
351
- watching = true;
352
- for (const [id, terminal] of terminals) {
353
- let branch;
354
- try {
355
- branch = await gitBranch(await currentDirectory(terminal.pty.pid));
356
- }
357
- catch (error) {
358
- if (!branchFailures.has(id))
359
- console.warn(`could not read the git branch of terminal ${id}: ${error}`);
360
- branchFailures.add(id);
361
- continue;
362
- }
363
- branchFailures.delete(id);
364
- if (terminal.branch === branch || terminals.get(id) !== terminal)
365
- continue;
366
- terminal.branch = branch;
367
- for (const connection of connections.values())
368
- send(connection, { type: 'branch', id, branch });
369
- }
370
- watching = false;
371
- }
372
- function findTab(id) {
373
- for (const project of projects.values()) {
374
- const tab = project.tabs.find((item) => item.id === id);
375
- if (tab !== undefined)
376
- return tab;
377
- }
378
- return undefined;
379
- }
380
- function nearestVisible(tabs, index) {
381
- for (let position = index; position < tabs.length; position++)
382
- if (!tabs[position].minimized)
383
- return position;
384
- for (let position = Math.min(index, tabs.length) - 1; position >= 0; position--)
385
- if (!tabs[position].minimized)
386
- return position;
387
- return undefined;
388
- }
389
- function layout() {
390
- return Object.fromEntries(projects);
391
- }
392
- function publish() {
393
- const event = { type: 'layout', layout: layout() };
394
- for (const connection of connections.values())
395
- send(connection, event);
396
- }
397
- function forward(connection, id, replay, data) {
398
- connection.client.socket.send(encodeFrame(replay ? FRAME_REPLAY : FRAME_OUTPUT, id, data));
399
- }
400
- function send(connection, event) {
401
- connection.client.send({ type: 'terminal', event });
402
- }