bitpull 0.1.28 → 0.1.30
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 +4 -2
- package/bitpull.cjs +19193 -18666
- package/package.json +1 -1
- package/ui/ide-terminal.js +1 -1
- package/ui/terminal.js +66 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bitpull",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"description": "BitPull (bitpull.cloud): share signed, AI-readable code, computing power and open AI models with AI tools through MCP. CLI and MCP server.",
|
|
5
5
|
"mcpName": "io.github.SamuealPK/bitpull",
|
|
6
6
|
"bin": {
|
package/ui/ide-terminal.js
CHANGED
|
@@ -12,7 +12,7 @@ function register() {
|
|
|
12
12
|
let current = null;
|
|
13
13
|
const start = () => {
|
|
14
14
|
if (term || !host || !opts || !opts.folder) return;
|
|
15
|
-
term = mountTerminal(host, { cwd: opts.folder, token: opts.token, t: opts.t, fontSize: opts.phone ? 12 : 13 });
|
|
15
|
+
term = mountTerminal(host, { cwd: opts.folder, token: opts.token, t: opts.t, fontSize: opts.phone ? 12 : 13, keep: !!opts.phone });
|
|
16
16
|
};
|
|
17
17
|
ide.terminal.register({
|
|
18
18
|
mount(container, o) {
|
package/ui/terminal.js
CHANGED
|
@@ -88,11 +88,25 @@ export function mountTerminalSetting(el, { token, t = (k) => k, onChange } = {})
|
|
|
88
88
|
terminalStatus(token).then(render, () => render({ enabled: false, available: false }));
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/** This phone's name for its shells on the computer: the same every time the page opens. */
|
|
92
|
+
function keptName() {
|
|
93
|
+
try {
|
|
94
|
+
let n = localStorage.getItem("bitpull_term_keep");
|
|
95
|
+
if (!n || !/^[A-Za-z0-9_-]{16,64}$/.test(n)) {
|
|
96
|
+
n = Array.from(crypto.getRandomValues(new Uint8Array(18)), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
97
|
+
localStorage.setItem("bitpull_term_keep", n);
|
|
98
|
+
}
|
|
99
|
+
return n;
|
|
100
|
+
} catch {
|
|
101
|
+
return Array.from(crypto.getRandomValues(new Uint8Array(18)), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
91
105
|
/**
|
|
92
106
|
* Mounts the terminal in `el`. `cwd` must be a folder the person authorised; `t` translates.
|
|
93
107
|
* Returns the controls the page and the Projects view use.
|
|
94
108
|
*/
|
|
95
|
-
export function mountTerminal(el, { cwd, token, t = (k) => k, fontSize = 13 } = {}) {
|
|
109
|
+
export function mountTerminal(el, { cwd, token, t = (k) => k, fontSize = 13, keep = false } = {}) {
|
|
96
110
|
const ended = (code) => String(t("term.exited", { code })).replace("{code}", String(code));
|
|
97
111
|
addCss();
|
|
98
112
|
el.replaceChildren();
|
|
@@ -126,6 +140,9 @@ export function mountTerminal(el, { cwd, token, t = (k) => k, fontSize = 13 } =
|
|
|
126
140
|
const waiting = new Map();
|
|
127
141
|
let nextRef = 1;
|
|
128
142
|
const byId = new Map();
|
|
143
|
+
// On a phone the shells wait on the computer for an hour when the phone goes away, under this name.
|
|
144
|
+
const keepName = keep ? keptName() : null;
|
|
145
|
+
let firstReady = true;
|
|
129
146
|
|
|
130
147
|
const message = (key, withEnable) => {
|
|
131
148
|
panesEl.replaceChildren();
|
|
@@ -186,7 +203,9 @@ export function mountTerminal(el, { cwd, token, t = (k) => k, fontSize = 13 } =
|
|
|
186
203
|
term.loadAddon(fit);
|
|
187
204
|
term.open(box);
|
|
188
205
|
const fast = () => {
|
|
189
|
-
|
|
206
|
+
// On Windows the app's window (WebView2) can draw nothing at all with WebGL on some graphics cards,
|
|
207
|
+
// without saying so: the ordinary drawing always shows the text.
|
|
208
|
+
if (!webgl || /Windows/i.test(navigator.userAgent)) return;
|
|
190
209
|
try {
|
|
191
210
|
const gl = new webgl();
|
|
192
211
|
// The graphics card can take the context back (sleep, driver update): then the ordinary drawing resumes.
|
|
@@ -201,11 +220,16 @@ export function mountTerminal(el, { cwd, token, t = (k) => k, fontSize = 13 } =
|
|
|
201
220
|
return { term, fit, box, readonly };
|
|
202
221
|
};
|
|
203
222
|
|
|
204
|
-
const startShell = (pane) => {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
223
|
+
const startShell = (pane, existing) => {
|
|
224
|
+
if (existing) {
|
|
225
|
+
pane.id = existing;
|
|
226
|
+
byId.set(existing, pane);
|
|
227
|
+
} else {
|
|
228
|
+
pane.ref = nextRef++;
|
|
229
|
+
pane.go = () => ws.send(JSON.stringify({ t: "open", ref: pane.ref, cwd, cols: pane.term.cols, rows: pane.term.rows }));
|
|
230
|
+
waiting.set(pane.ref, pane);
|
|
231
|
+
if (ready) pane.go();
|
|
232
|
+
}
|
|
209
233
|
pane.term.onData((d) => {
|
|
210
234
|
// "Ctrl" pressed on a phone's extra keys: the next letter becomes Ctrl+letter.
|
|
211
235
|
if (ctrlNext && d.length === 1) {
|
|
@@ -224,7 +248,7 @@ export function mountTerminal(el, { cwd, token, t = (k) => k, fontSize = 13 } =
|
|
|
224
248
|
if (ws && ws.readyState <= 1) return;
|
|
225
249
|
ready = false;
|
|
226
250
|
ws = new WebSocket(`${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/api/terminal`);
|
|
227
|
-
ws.onopen = () => ws.send(JSON.stringify({ t: "auth", token }));
|
|
251
|
+
ws.onopen = () => ws.send(JSON.stringify({ t: "auth", token, ...(keepName ? { keep: keepName } : {}) }));
|
|
228
252
|
ws.onmessage = (e) => {
|
|
229
253
|
let m;
|
|
230
254
|
try {
|
|
@@ -234,7 +258,14 @@ export function mountTerminal(el, { cwd, token, t = (k) => k, fontSize = 13 } =
|
|
|
234
258
|
}
|
|
235
259
|
if (m.t === "ready") {
|
|
236
260
|
ready = true;
|
|
261
|
+
if (keepName) pickUp(m.kept || []);
|
|
237
262
|
for (const p of waiting.values()) p.go();
|
|
263
|
+
if (keepName && firstReady && !tabs.length) newTab();
|
|
264
|
+
firstReady = false;
|
|
265
|
+
} else if (m.t === "attached") {
|
|
266
|
+
// The computer sends the screen again as it is now: it replaces what this page still showed.
|
|
267
|
+
byId.get(m.id)?.term.reset();
|
|
268
|
+
fitAll();
|
|
238
269
|
} else if (m.t === "opened") {
|
|
239
270
|
const p = waiting.get(m.ref);
|
|
240
271
|
if (!p) return;
|
|
@@ -250,6 +281,14 @@ export function mountTerminal(el, { cwd, token, t = (k) => k, fontSize = 13 } =
|
|
|
250
281
|
byId.delete(m.id);
|
|
251
282
|
p.id = undefined;
|
|
252
283
|
}
|
|
284
|
+
} else if (m.t === "error" && m.error === "no_terminal" && typeof m.id === "number") {
|
|
285
|
+
// Gone while the phone was away (more than an hour, or the computer restarted).
|
|
286
|
+
const p = byId.get(m.id);
|
|
287
|
+
if (p) {
|
|
288
|
+
byId.delete(m.id);
|
|
289
|
+
p.id = undefined;
|
|
290
|
+
p.term.write(`\r\n\x1b[2m${t("term.gone")}\x1b[0m\r\n`);
|
|
291
|
+
}
|
|
253
292
|
} else if (m.t === "error") {
|
|
254
293
|
const p = waiting.get(m.ref);
|
|
255
294
|
waiting.delete(m.ref);
|
|
@@ -260,6 +299,8 @@ export function mountTerminal(el, { cwd, token, t = (k) => k, fontSize = 13 } =
|
|
|
260
299
|
ws.onclose = (e) => {
|
|
261
300
|
ready = false;
|
|
262
301
|
if (disposed) return;
|
|
302
|
+
// A phone's connection comes back by itself: its shells are still there.
|
|
303
|
+
if (keepName && e.code !== 4409 && e.code !== 4410 && e.code !== 4401) setTimeout(() => !disposed && connect(), 2000);
|
|
263
304
|
if (e.code === 4409 || e.code === 1006) {
|
|
264
305
|
terminalStatus(token).then((s) => {
|
|
265
306
|
if (!s.available) message("term.unavailable", false);
|
|
@@ -269,6 +310,20 @@ export function mountTerminal(el, { cwd, token, t = (k) => k, fontSize = 13 } =
|
|
|
269
310
|
};
|
|
270
311
|
};
|
|
271
312
|
|
|
313
|
+
/** Shows again the shells this phone left running: those of this page first, then the others in this folder. */
|
|
314
|
+
function pickUp(kept) {
|
|
315
|
+
for (const p of byId.values()) ws.send(JSON.stringify({ t: "attach", id: p.id, cols: p.term.cols, rows: p.term.rows }));
|
|
316
|
+
for (const k of kept) {
|
|
317
|
+
if (byId.has(k.id) || k.cwd !== cwd) continue;
|
|
318
|
+
const pane = makePane(false);
|
|
319
|
+
const tab = { title: "", panes: [pane] };
|
|
320
|
+
tabs.push(tab);
|
|
321
|
+
show(tab);
|
|
322
|
+
startShell(pane, k.id);
|
|
323
|
+
ws.send(JSON.stringify({ t: "attach", id: k.id, cols: pane.term.cols, rows: pane.term.rows }));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
272
327
|
function newTab() {
|
|
273
328
|
const pane = makePane(false);
|
|
274
329
|
const tab = { title: "", panes: [pane] };
|
|
@@ -337,7 +392,7 @@ export function mountTerminal(el, { cwd, token, t = (k) => k, fontSize = 13 } =
|
|
|
337
392
|
bar.append(tabsEl, button("term.new", "tip.term.new", () => newTab()), button("term.split", "tip.term.split", () => split()), button("term.close", "tip.term.close", () => close()));
|
|
338
393
|
const resize = new ResizeObserver(() => fitAll());
|
|
339
394
|
resize.observe(panesEl);
|
|
340
|
-
// Closing the page ends its shells: the server closes them with the connection too.
|
|
395
|
+
// Closing the page ends its shells: the server closes them with the connection too. A phone's wait an hour.
|
|
341
396
|
window.addEventListener("pagehide", dispose);
|
|
342
397
|
|
|
343
398
|
terminalStatus(token).then(
|
|
@@ -346,7 +401,8 @@ export function mountTerminal(el, { cwd, token, t = (k) => k, fontSize = 13 } =
|
|
|
346
401
|
if (!s.available) return message("term.unavailable", false);
|
|
347
402
|
if (!s.enabled) return message("term.off", true);
|
|
348
403
|
connect();
|
|
349
|
-
|
|
404
|
+
// On a phone, the first answer says whether shells are waiting; a new one opens only if none is.
|
|
405
|
+
if (!keepName) newTab();
|
|
350
406
|
},
|
|
351
407
|
() => message("term.failed", false),
|
|
352
408
|
);
|