tilt-live 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cloudycotton
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,14 @@
1
- # Temporary Holding Version
1
+ # tilt-live
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ See and control any Linux desktop, right in your browser.
4
+
5
+ ```sh
6
+ npx tilt-live
7
+ ```
8
+
9
+ Open the link it prints, click the cursor button, and the desktop is yours.
10
+
11
+ - `npx tilt-live --display :1`: every flag goes to [tilt](https://github.com/cloudycotton/tilt/blob/main/docs/guide.md#flags).
12
+ - Always runs the latest release (checksum-verified). Pin one with `TILT_VERSION=0.2.0`, skip the check with `TILT_NO_UPDATE=1`.
13
+ - Keeps its token in `~/.config/tilt-live/token` unless you pass `--token`, `--token-file` or `--no-auth`.
14
+ - Linux on x86_64 or arm64, Node 18+. MIT licensed.
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ // tilt-live: runs the latest tilt release (https://github.com/cloudycotton/tilt), downloading or
3
+ // updating it first when a newer one is out, and passes every argument through to it.
4
+ //
5
+ // TILT_VERSION=0.2.0 run that release instead of the latest
6
+ // TILT_NO_UPDATE=1 do not look for updates (run the newest one already downloaded)
7
+ //
8
+ // With no --token, --token-file or --no-auth (nor their TILT_* variables), it makes a token,
9
+ // keeps it in ~/.config/tilt-live/token and prints the link to open.
10
+ 'use strict';
11
+
12
+ const crypto = require('node:crypto');
13
+ const fs = require('node:fs');
14
+ const os = require('node:os');
15
+ const path = require('node:path');
16
+ const { spawn } = require('node:child_process');
17
+
18
+ // TILT_RELEASES points elsewhere for tests: a URL with GitHub's /latest and /download layout.
19
+ const RELEASES = process.env.TILT_RELEASES || 'https://github.com/cloudycotton/tilt/releases';
20
+ const CHECK_TIMEOUT_MS = 2000;
21
+ const DOWNLOAD_TIMEOUT_MS = 120_000;
22
+ const KEEP_VERSIONS = 2;
23
+ const TRIPLES = { x64: 'x86_64-unknown-linux-musl', arm64: 'aarch64-unknown-linux-musl' };
24
+
25
+ const home = os.homedir();
26
+ const cacheDir = path.join(process.env.XDG_CACHE_HOME || path.join(home, '.cache'), 'tilt-live');
27
+ const configDir = path.join(process.env.XDG_CONFIG_HOME || path.join(home, '.config'), 'tilt-live');
28
+
29
+ function fail(msg) {
30
+ process.stderr.write(`tilt-live: ${msg}\n`);
31
+ process.exit(1);
32
+ }
33
+
34
+ const cmp = (a, b) => {
35
+ const pa = a.split(/[.-]/).map(Number);
36
+ const pb = b.split(/[.-]/).map(Number);
37
+ for (let i = 0; i < 3; i++) if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0);
38
+ return 0;
39
+ };
40
+
41
+ /** Versions already downloaded, newest first. */
42
+ function cached() {
43
+ try {
44
+ return fs.readdirSync(cacheDir)
45
+ .filter((v) => /^\d+\.\d+\.\d+$/.test(v) && fs.existsSync(path.join(cacheDir, v, 'tilt')))
46
+ .sort(cmp)
47
+ .reverse();
48
+ } catch {
49
+ return [];
50
+ }
51
+ }
52
+
53
+ /** The newest release's version, from the redirect of /releases/latest; null when unknown. */
54
+ async function latest() {
55
+ try {
56
+ const res = await fetch(`${RELEASES}/latest`, {
57
+ redirect: 'manual',
58
+ signal: AbortSignal.timeout(CHECK_TIMEOUT_MS),
59
+ });
60
+ const m = /\/tag\/v?(\d+\.\d+\.\d+)$/.exec(res.headers.get('location') || '');
61
+ return m ? m[1] : null;
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+
67
+ async function download(version, triple) {
68
+ const base = `${RELEASES}/download/v${version}`;
69
+ const get = async (name) => {
70
+ const res = await fetch(`${base}/${name}`, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
71
+ if (!res.ok) throw new Error(`${name}: HTTP ${res.status}`);
72
+ return Buffer.from(await res.arrayBuffer());
73
+ };
74
+ const name = `tilt-${triple}`;
75
+ const [bin, sums] = await Promise.all([get(name), get('SHA256SUMS')]);
76
+ const want = sums.toString().split('\n').map((l) => l.trim().split(/\s+/)).find((f) => f[1] === name)?.[0];
77
+ const got = crypto.createHash('sha256').update(bin).digest('hex');
78
+ if (want !== got) throw new Error(`${name}: checksum mismatch`);
79
+ const dir = path.join(cacheDir, version);
80
+ fs.mkdirSync(dir, { recursive: true });
81
+ const tmp = path.join(dir, `tilt.${process.pid}`);
82
+ fs.writeFileSync(tmp, bin, { mode: 0o755 });
83
+ fs.renameSync(tmp, path.join(dir, 'tilt'));
84
+ for (const old of cached().slice(KEEP_VERSIONS)) fs.rmSync(path.join(cacheDir, old), { recursive: true, force: true });
85
+ }
86
+
87
+ /** The binary to run: the release asked for, else the latest, downloaded if need be. */
88
+ async function binary() {
89
+ const triple = TRIPLES[process.arch];
90
+ if (process.platform !== 'linux' || !triple) fail('tilt runs on Linux, on x86_64 or arm64');
91
+ const have = cached();
92
+ let version = process.env.TILT_VERSION;
93
+ if (!version && !process.env.TILT_NO_UPDATE) version = await latest();
94
+ version ||= have[0];
95
+ if (!version) fail('cannot find the latest tilt release (check the network, or set TILT_VERSION)');
96
+ if (!have.includes(version)) {
97
+ process.stderr.write(`tilt-live: downloading tilt ${version}...\n`);
98
+ try {
99
+ await download(version, triple);
100
+ } catch (e) {
101
+ if (!have[0]) fail(`download failed: ${e.message}`);
102
+ process.stderr.write(`tilt-live: download failed (${e.message}); running ${have[0]}\n`);
103
+ version = have[0];
104
+ }
105
+ }
106
+ return path.join(cacheDir, version, 'tilt');
107
+ }
108
+
109
+ /** Adds --token-file with a kept token when no access control was given, and says where to go. */
110
+ function withAuth(args) {
111
+ const given = args.some((a) => /^--(token|token-file|no-auth)(=|$)/.test(a))
112
+ || ['TILT_TOKEN', 'TILT_TOKEN_FILE', 'TILT_NO_AUTH'].some((v) => process.env[v]);
113
+ const info = ['-h', '--help', '-V', '--version'].some((a) => args.includes(a));
114
+ if (given || info) return args;
115
+ const file = path.join(configDir, 'token');
116
+ let token = '';
117
+ try { token = fs.readFileSync(file, 'utf8').trim(); } catch { /* none yet */ }
118
+ if (!token) {
119
+ token = crypto.randomBytes(16).toString('hex');
120
+ fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
121
+ fs.writeFileSync(file, `${token}\n`, { mode: 0o600 });
122
+ }
123
+ const bind = (args.find((a) => a.startsWith('--bind='))?.slice(7))
124
+ || (args.includes('--bind') ? args[args.indexOf('--bind') + 1] : null)
125
+ || process.env.TILT_BIND || '0.0.0.0:6090';
126
+ const at = bind.lastIndexOf(':');
127
+ const [host, port] = [bind.slice(0, at).replace(/^\[|\]$/g, ''), bind.slice(at + 1)];
128
+ // Bound to every interface: the machine's own addresses; else the one it is bound to.
129
+ const hosts = host === '0.0.0.0' || host === '::'
130
+ ? ['localhost', ...Object.values(os.networkInterfaces()).flat()
131
+ .filter((i) => i && i.family === 'IPv4' && !i.internal).map((i) => i.address)]
132
+ : [host.includes(':') ? `[${host}]` : host];
133
+ process.stderr.write(`tilt-live: open ${hosts.map((h) => `http://${h}:${port}/#token=${token}`).join('\n or ')}\n`);
134
+ return [...args, '--token-file', file];
135
+ }
136
+
137
+ (async () => {
138
+ const bin = await binary();
139
+ const args = withAuth(process.argv.slice(2));
140
+ const child = spawn(bin, args, { stdio: 'inherit' });
141
+ for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGQUIT']) process.on(sig, () => child.kill(sig));
142
+ child.on('error', (e) => fail(e.message));
143
+ child.on('exit', (code, sig) => process.exit(code ?? (sig ? 128 + os.constants.signals[sig] : 1)));
144
+ })();
package/package.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
2
  "name": "tilt-live",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "See and control any Linux desktop, right in your browser (runs the latest tilt release)",
5
+ "keywords": [
6
+ "vnc",
7
+ "remote-desktop",
8
+ "x11",
9
+ "xvfb",
10
+ "webcodecs",
11
+ "h264",
12
+ "screen-sharing",
13
+ "e2b"
14
+ ],
15
+ "homepage": "https://github.com/cloudycotton/tilt",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/cloudycotton/tilt.git",
19
+ "directory": "npm"
20
+ },
21
+ "license": "MIT",
22
+ "bin": {
23
+ "tilt-live": "bin/tilt-live.js"
24
+ },
25
+ "files": [
26
+ "bin"
27
+ ],
28
+ "os": [
29
+ "linux"
30
+ ],
31
+ "cpu": [
32
+ "x64",
33
+ "arm64"
34
+ ],
35
+ "engines": {
36
+ "node": ">=18"
37
+ },
38
+ "scripts": {
39
+ "test": "node --test test/launcher.test.mjs"
40
+ }
41
+ }