ai-or-die 0.1.84 → 0.1.85

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/bin/ai-or-die.js CHANGED
@@ -89,10 +89,10 @@ async function main() {
89
89
  }
90
90
 
91
91
  // Handle authentication logic
92
- // Tunnel mode disables auth — the tunnel controls access. Mesh KEEPS auth
93
- // on (ACL + Bearer layered); --mesh wins over --tunnel here.
92
+ // Tunnel mode disables auth — the tunnel controls access even with --mesh.
93
+ // (--mesh alone keeps the Bearer token on; --tunnel always wins.)
94
94
  let authToken = null;
95
- let noAuth = options.disableAuth === true || (options.tunnel === true && !options.mesh);
95
+ let noAuth = options.disableAuth === true || options.tunnel === true;
96
96
 
97
97
  if (!noAuth) {
98
98
  if (options.auth) {
@@ -144,7 +144,8 @@ async function main() {
144
144
  console.log(`Plan: ${options.plan}`);
145
145
  console.log(`Aliases: Claude → "${serverOptions.claudeAlias}", Codex → "${serverOptions.codexAlias}", Copilot → "${serverOptions.copilotAlias}", Gemini → "${serverOptions.geminiAlias}", Terminal → "${serverOptions.terminalAlias}"`);
146
146
 
147
- // Display authentication status
147
+ // Display authentication status. --tunnel disables auth (tunnel controls
148
+ // access) even with --mesh; --mesh alone keeps the Bearer token on.
148
149
  if (options.tunnel) {
149
150
  console.log('\n🌍 TUNNEL MODE — authentication disabled (tunnel controls access)');
150
151
  } else if (noAuth) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-or-die",
3
- "version": "0.1.84",
3
+ "version": "0.1.85",
4
4
  "description": "Universal AI coding terminal — Claude, Copilot, Gemini & more in your browser",
5
5
  "main": "src/server.js",
6
6
  "bin": {
@@ -53,12 +53,29 @@ class MeshManager {
53
53
  async start() {
54
54
  console.log('\n Connecting mesh (Tailscale userspace)...');
55
55
  this.stopping = false;
56
- if (!fs.existsSync(this.sidecar)) { this._printMissing(); return; }
56
+ if (!fs.existsSync(this.sidecar)) {
57
+ if (!(await this._ensureSidecar())) { this._printMissing(); return; }
58
+ }
57
59
  if (!this._authKey && !this._enrolled()) { this._printNotEnrolled(); return; }
58
60
  try { fs.mkdirSync(this.stateDir, { recursive: true }); } catch (_) {}
59
61
  await this._spawn();
60
62
  }
61
63
 
64
+ /** Download + verify the sidecar from the matching release. Best-effort. */
65
+ async _ensureSidecar() {
66
+ try {
67
+ const { ensureSidecar } = require('./utils/sidecar-installer');
68
+ let version = '0.0.0';
69
+ try { version = require('../package.json').version; } catch (_) {}
70
+ console.log(' [mesh] fetching sidecar binary...');
71
+ await ensureSidecar(version, this.sidecar);
72
+ return fs.existsSync(this.sidecar);
73
+ } catch (e) {
74
+ if (this.dev) console.error(' [mesh] sidecar fetch failed:', e.message);
75
+ return false;
76
+ }
77
+ }
78
+
62
79
  getStatus() {
63
80
  return { running: this.proc !== null && !this.stopping && !!this.dnsName, publicUrl: this.dnsName ? `https://${this.dnsName}` : null };
64
81
  }
@@ -0,0 +1,109 @@
1
+ 'use strict';
2
+
3
+ // Sidecar installer — fetches the prebuilt aiordie-mesh tsnet binary for the
4
+ // current platform from the matching GitHub release and verifies it against the
5
+ // release's published SHA-256 checksums before use. Mirrors the download/verify
6
+ // shape of gguf-model-manager (resumable not needed — the binary is ~20MB).
7
+ //
8
+ // Asset convention (published by .github/workflows/release-on-main.yml):
9
+ // aiordie-mesh-<plat>-<arch>[.exe] plat: windows|linux|darwin arch: amd64|arm64
10
+ // aiordie-mesh-checksums.txt "<sha256> <assetname>" per line
11
+
12
+ const fs = require('fs');
13
+ const fsp = require('fs').promises;
14
+ const path = require('path');
15
+ const os = require('os');
16
+ const crypto = require('crypto');
17
+
18
+ const REPO = 'animeshkundu/ai-or-die';
19
+ const PLAT = { win32: 'windows', linux: 'linux', darwin: 'darwin' };
20
+ const ARCH = { x64: 'amd64', arm64: 'arm64' };
21
+
22
+ function assetName() {
23
+ const plat = PLAT[process.platform];
24
+ const arch = ARCH[process.arch];
25
+ if (!plat || !arch) return null;
26
+ return `aiordie-mesh-${plat}-${arch}${process.platform === 'win32' ? '.exe' : ''}`;
27
+ }
28
+
29
+ function sidecarPath() {
30
+ const localApp = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
31
+ const base = process.platform === 'win32' ? path.join(localApp, 'ai-or-die') : path.join(os.homedir(), '.ai-or-die');
32
+ return path.join(base, 'bin', `aiordie-mesh${process.platform === 'win32' ? '.exe' : ''}`);
33
+ }
34
+
35
+ async function _sha256(file) {
36
+ return new Promise((resolve, reject) => {
37
+ const h = crypto.createHash('sha256');
38
+ const s = fs.createReadStream(file);
39
+ s.on('data', (c) => h.update(c));
40
+ s.on('end', () => resolve(h.digest('hex')));
41
+ s.on('error', reject);
42
+ });
43
+ }
44
+
45
+ async function _fetchText(url, ms = 15000) {
46
+ const ac = new AbortController();
47
+ const t = setTimeout(() => ac.abort(), ms);
48
+ try {
49
+ const r = await fetch(url, { signal: ac.signal });
50
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
51
+ return await r.text();
52
+ } finally { clearTimeout(t); }
53
+ }
54
+
55
+ async function _download(url, tmp, ms = 120000) {
56
+ const ac = new AbortController();
57
+ const t = setTimeout(() => ac.abort(), ms);
58
+ try {
59
+ const r = await fetch(url, { signal: ac.signal });
60
+ if (!r.ok || !r.body) throw new Error(`HTTP ${r.status}`);
61
+ // Exclusive create — never follow/overwrite a pre-planted file or symlink.
62
+ const out = fs.createWriteStream(tmp, { flags: 'wx' });
63
+ const reader = r.body.getReader();
64
+ for (;;) {
65
+ const { done, value } = await reader.read();
66
+ if (done) break;
67
+ await new Promise((res, rej) => out.write(value, (e) => (e ? rej(e) : res())));
68
+ }
69
+ await new Promise((res) => out.end(res));
70
+ } finally { clearTimeout(t); }
71
+ }
72
+
73
+ // Ensure the sidecar binary exists locally AND matches the release checksum;
74
+ // download + verify if missing or stale. Returns the path; throws on failure.
75
+ async function ensureSidecar(version, dest = sidecarPath()) {
76
+ const name = assetName();
77
+ if (!name) throw new Error(`unsupported platform ${process.platform}/${process.arch}`);
78
+
79
+ // Resolve the expected hash first so an existing file is also verified.
80
+ const baseUrl = `https://github.com/${REPO}/releases/download/v${version}`;
81
+ const sums = await _fetchText(`${baseUrl}/aiordie-mesh-checksums.txt`);
82
+ const line = sums.split('\n').find((l) => {
83
+ const f = l.trim().split(/\s+/)[1]; // exact filename column, not endsWith
84
+ return f === name;
85
+ });
86
+ if (!line) throw new Error(`no checksum for ${name} in release v${version}`);
87
+ const want = line.trim().split(/\s+/)[0].toLowerCase();
88
+
89
+ // Existing file: trust only if it matches; otherwise replace it.
90
+ if (fs.existsSync(dest)) {
91
+ if ((await _sha256(dest)).toLowerCase() === want) return dest;
92
+ try { await fsp.unlink(dest); } catch (_) {}
93
+ }
94
+
95
+ await fsp.mkdir(path.dirname(dest), { recursive: true });
96
+ const tmp = `${dest}.${crypto.randomBytes(8).toString('hex')}.incomplete`;
97
+ try {
98
+ await _download(`${baseUrl}/${name}`, tmp);
99
+ // Verify BEFORE the bytes ever reach the runnable path (no TOCTOU window).
100
+ if ((await _sha256(tmp)).toLowerCase() !== want) throw new Error(`checksum mismatch for ${name}`);
101
+ if (process.platform !== 'win32') { try { await fsp.chmod(tmp, 0o755); } catch (_) {} }
102
+ await fsp.rename(tmp, dest);
103
+ } finally {
104
+ try { await fsp.unlink(tmp); } catch (_) {}
105
+ }
106
+ return dest;
107
+ }
108
+
109
+ module.exports = { ensureSidecar, sidecarPath, assetName };