airloom 0.1.32 → 0.1.34

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 (2) hide show
  1. package/dist/index.js +348 -64
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -652,15 +652,28 @@ var AnthropicAdapter = class {
652
652
  messages: chatMsgs
653
653
  };
654
654
  if (systemMsg) body.system = systemMsg;
655
- const response = await fetch("https://api.anthropic.com/v1/messages", {
656
- method: "POST",
657
- headers: {
658
- "Content-Type": "application/json",
659
- "x-api-key": this.apiKey,
660
- "anthropic-version": "2023-06-01"
661
- },
662
- body: JSON.stringify(body)
663
- });
655
+ const controller = new AbortController();
656
+ const timeout = setTimeout(() => controller.abort(), 6e4);
657
+ let response;
658
+ try {
659
+ response = await fetch("https://api.anthropic.com/v1/messages", {
660
+ method: "POST",
661
+ headers: {
662
+ "Content-Type": "application/json",
663
+ "x-api-key": this.apiKey,
664
+ "anthropic-version": "2023-06-01"
665
+ },
666
+ body: JSON.stringify(body),
667
+ signal: controller.signal
668
+ });
669
+ } catch (err) {
670
+ clearTimeout(timeout);
671
+ const msg = err.name === "AbortError" ? "Request timed out" : err.message;
672
+ stream.write(`[Error: ${msg}]`);
673
+ stream.end();
674
+ return;
675
+ }
676
+ clearTimeout(timeout);
664
677
  if (!response.ok) {
665
678
  const error = await response.text();
666
679
  stream.write(`[Error: ${response.status} ${error}]`);
@@ -714,14 +727,27 @@ var OpenAIAdapter = class {
714
727
  this.baseUrl = config.baseUrl || "https://api.openai.com/v1";
715
728
  }
716
729
  async streamResponse(messages, stream) {
717
- const response = await fetch(`${this.baseUrl}/chat/completions`, {
718
- method: "POST",
719
- headers: {
720
- "Content-Type": "application/json",
721
- Authorization: `Bearer ${this.apiKey}`
722
- },
723
- body: JSON.stringify({ model: this.model, stream: true, messages })
724
- });
730
+ const controller = new AbortController();
731
+ const timeout = setTimeout(() => controller.abort(), 6e4);
732
+ let response;
733
+ try {
734
+ response = await fetch(`${this.baseUrl}/chat/completions`, {
735
+ method: "POST",
736
+ headers: {
737
+ "Content-Type": "application/json",
738
+ Authorization: `Bearer ${this.apiKey}`
739
+ },
740
+ body: JSON.stringify({ model: this.model, stream: true, messages }),
741
+ signal: controller.signal
742
+ });
743
+ } catch (err) {
744
+ clearTimeout(timeout);
745
+ const msg = err.name === "AbortError" ? "Request timed out" : err.message;
746
+ stream.write(`[Error: ${msg}]`);
747
+ stream.end();
748
+ return;
749
+ }
750
+ clearTimeout(timeout);
725
751
  if (!response.ok) {
726
752
  const error = await response.text();
727
753
  stream.write(`[Error: ${response.status} ${error}]`);
@@ -850,7 +876,7 @@ function resolveExecutable(command, envPath = process.env.PATH ?? "") {
850
876
  }
851
877
  for (const dir of envPath.split(delimiter)) {
852
878
  if (!dir) continue;
853
- const candidate = join(dir.replace(/^~(?=$|\/)/, process.env.HOME ?? "~"), command);
879
+ const candidate = join(dir.replace(/^~(?=$|\/)/, process.env.HOME || process.env.USERPROFILE || "~"), command);
854
880
  if (existsSync(candidate)) return candidate;
855
881
  }
856
882
  return null;
@@ -1123,7 +1149,8 @@ function resolveExecutable2(command, envPath = process.env.PATH ?? "") {
1123
1149
  }
1124
1150
  for (const dir of envPath.split(delimiter2)) {
1125
1151
  if (!dir) continue;
1126
- const candidate = join3(dir.replace(/^~(?=$|\/)/, process.env.HOME ?? "~"), command);
1152
+ const home = process.env.HOME || process.env.USERPROFILE || "~";
1153
+ const candidate = join3(dir.replace(/^~(?=$|\/)/, home), command);
1127
1154
  if (existsSync2(candidate)) return candidate;
1128
1155
  }
1129
1156
  return null;
@@ -1138,14 +1165,16 @@ function parseCommand(command) {
1138
1165
  function getDefaultTerminalCommand(explicitCommand) {
1139
1166
  const configured = explicitCommand?.trim() || process.env.AIRLOOM_TERMINAL_COMMAND?.trim();
1140
1167
  if (configured) return parseCommand(configured);
1168
+ const shell = process.env.SHELL;
1169
+ if (shell) {
1170
+ const name = basename(shell);
1171
+ if (name === "bash" || name === "zsh" || name === "sh") return { file: shell, args: ["-il"] };
1172
+ return { file: shell, args: ["-i"] };
1173
+ }
1141
1174
  if (process.platform === "win32") {
1142
- const file = process.env.COMSPEC || "powershell.exe";
1143
- return { file, args: [] };
1175
+ return { file: process.env.COMSPEC || "powershell.exe", args: [] };
1144
1176
  }
1145
- const shell = process.env.SHELL || "/bin/bash";
1146
- const name = basename(shell);
1147
- if (name === "bash" || name === "zsh" || name === "sh") return { file: shell, args: ["-il"] };
1148
- return { file: shell, args: ["-i"] };
1177
+ return { file: "/bin/bash", args: ["-il"] };
1149
1178
  }
1150
1179
  var AdaptiveOutputBatcher = class {
1151
1180
  constructor(onFlush, fastInterval = 16, slowInterval = 80, interactiveWindow = 250, maxBytes = 4096) {
@@ -1220,8 +1249,8 @@ var TerminalSession = class {
1220
1249
  const file = resolveExecutable2(command.file) ?? command.file;
1221
1250
  const cwd = process.cwd();
1222
1251
  log(`[host] PTY spawn: ${file} ${command.args.join(" ")} (${this.cols}x${this.rows}) node=${process.version}`);
1223
- const env2 = { ...process.env, TERM: "xterm-256color" };
1224
- const spawnOpts = { name: "xterm-256color", cols: this.cols, rows: this.rows, cwd, env: env2 };
1252
+ const env = { ...process.env, TERM: "xterm-256color" };
1253
+ const spawnOpts = { name: "xterm-256color", cols: this.cols, rows: this.rows, cwd, env };
1225
1254
  let nodePty;
1226
1255
  try {
1227
1256
  nodePty = requireNodePty();
@@ -2176,8 +2205,8 @@ function parseRelayUrl(value) {
2176
2205
  }
2177
2206
  return parsed.toString();
2178
2207
  }
2179
- function parseHostBind(value, isDev) {
2180
- const bind = value?.trim() || (isDev ? "0.0.0.0" : DEFAULT_HOST_BIND);
2208
+ function parseHostBind(value, isDev, isSSH) {
2209
+ const bind = value?.trim() || (isDev || isSSH ? "0.0.0.0" : DEFAULT_HOST_BIND);
2181
2210
  if (bind === "localhost" || isIP(bind) !== 0) return bind;
2182
2211
  if (/^[A-Za-z0-9.-]+$/.test(bind)) return bind;
2183
2212
  throw new Error("HOST_BIND must be localhost, an IP address, or a hostname");
@@ -2186,11 +2215,12 @@ function isLoopbackBind(bind) {
2186
2215
  return bind === "127.0.0.1" || bind === "::1" || bind === "localhost";
2187
2216
  }
2188
2217
  function parseHostEnv(cliPort, isDev = false) {
2218
+ const isSSH = !!(process.env.SSH_CONNECTION || process.env.SSH_TTY || process.env.SSH_CLIENT);
2189
2219
  const relayUrl = parseRelayUrl(process.env.RELAY_URL);
2190
2220
  const ablyApiKey = process.env.ABLY_API_KEY ?? (relayUrl ? void 0 : DEFAULT_ABLY_KEY);
2191
2221
  const ablyTokenTtlMs = parseInteger("ABLY_TOKEN_TTL", process.env.ABLY_TOKEN_TTL, DEFAULT_ABLY_TOKEN_TTL_MS, 6e4, 31 * 24 * 60 * 60 * 1e3);
2192
2222
  const hostPort = cliPort ?? parseInteger("HOST_PORT", process.env.HOST_PORT, DEFAULT_HOST_PORT, 0, 65535);
2193
- const hostBind = parseHostBind(process.env.HOST_BIND, isDev);
2223
+ const hostBind = parseHostBind(process.env.HOST_BIND, isDev, isSSH);
2194
2224
  const viewerUrl = parseViewerUrl(process.env.VIEWER_URL);
2195
2225
  return {
2196
2226
  viewerUrl,
@@ -2200,7 +2230,8 @@ function parseHostEnv(cliPort, isDev = false) {
2200
2230
  hostPort,
2201
2231
  hostBind,
2202
2232
  useAbly: !!ablyApiKey,
2203
- isDefaultAblyKey: !!ablyApiKey && ablyApiKey === DEFAULT_ABLY_KEY
2233
+ isDefaultAblyKey: !!ablyApiKey && ablyApiKey === DEFAULT_ABLY_KEY,
2234
+ isSSH
2204
2235
  };
2205
2236
  }
2206
2237
 
@@ -2236,13 +2267,213 @@ function loadOrCreateAblySessionToken() {
2236
2267
  return ablySessionToken;
2237
2268
  }
2238
2269
 
2270
+ // src/daemon.ts
2271
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync, readdirSync, openSync, closeSync } from "node:fs";
2272
+ import { homedir as homedir3 } from "node:os";
2273
+ import { join as join5 } from "node:path";
2274
+ import { spawn as spawn2 } from "node:child_process";
2275
+ var SESSIONS_DIR = join5(homedir3(), ".config", "airloom", "sessions");
2276
+ function ensureDir() {
2277
+ mkdirSync3(SESSIONS_DIR, { recursive: true });
2278
+ }
2279
+ function sessionPath(name) {
2280
+ return join5(SESSIONS_DIR, `${name}.json`);
2281
+ }
2282
+ function logFilePath(name) {
2283
+ return join5(SESSIONS_DIR, `${name}.log`);
2284
+ }
2285
+ function validateName(name) {
2286
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
2287
+ throw new Error(`Invalid session name "${name}". Use only letters, numbers, hyphens, and underscores.`);
2288
+ }
2289
+ }
2290
+ function readSession(name) {
2291
+ try {
2292
+ const raw = readFileSync3(sessionPath(name), "utf-8");
2293
+ const data = JSON.parse(raw);
2294
+ if (!data || typeof data.pid !== "number") return null;
2295
+ return data;
2296
+ } catch {
2297
+ return null;
2298
+ }
2299
+ }
2300
+ function writeSession(name, info) {
2301
+ ensureDir();
2302
+ writeFileSync3(sessionPath(name), JSON.stringify(info, null, 2) + "\n", { mode: 384 });
2303
+ }
2304
+ function removeSession(name) {
2305
+ try {
2306
+ unlinkSync(sessionPath(name));
2307
+ } catch {
2308
+ }
2309
+ }
2310
+ function isAlive(pid) {
2311
+ try {
2312
+ process.kill(pid, 0);
2313
+ return true;
2314
+ } catch {
2315
+ return false;
2316
+ }
2317
+ }
2318
+ function listAllSessions() {
2319
+ ensureDir();
2320
+ let files;
2321
+ try {
2322
+ files = readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
2323
+ } catch {
2324
+ return [];
2325
+ }
2326
+ const results = [];
2327
+ for (const file of files) {
2328
+ const name = file.replace(/\.json$/, "");
2329
+ const info = readSession(name);
2330
+ if (!info) continue;
2331
+ if (!isAlive(info.pid)) {
2332
+ removeSession(name);
2333
+ continue;
2334
+ }
2335
+ results.push({ name, info });
2336
+ }
2337
+ return results;
2338
+ }
2339
+ function formatAge(ms) {
2340
+ const sec = Math.floor(ms / 1e3);
2341
+ if (sec < 60) return `${sec}s ago`;
2342
+ const min = Math.floor(sec / 60);
2343
+ if (min < 60) return `${min}m ago`;
2344
+ const hr = Math.floor(min / 60);
2345
+ if (hr < 24) return `${hr}h ${min % 60}m ago`;
2346
+ return `${Math.floor(hr / 24)}d ago`;
2347
+ }
2348
+ async function handleStart(name, hostArgs) {
2349
+ validateName(name);
2350
+ const existing = readSession(name);
2351
+ if (existing && isAlive(existing.pid)) {
2352
+ console.error(`Session "${name}" is already running (PID ${existing.pid}, port ${existing.port})`);
2353
+ console.error(`Host UI: ${existing.controlUrl}`);
2354
+ process.exit(1);
2355
+ }
2356
+ if (existing) removeSession(name);
2357
+ ensureDir();
2358
+ const logFile = logFilePath(name);
2359
+ const logFd = openSync(logFile, "w");
2360
+ const child = spawn2(
2361
+ process.execPath,
2362
+ [...process.execArgv, process.argv[1], ...hostArgs, "--_daemon"],
2363
+ {
2364
+ detached: true,
2365
+ stdio: ["ignore", logFd, logFd, "ipc"],
2366
+ cwd: process.cwd(),
2367
+ env: process.env
2368
+ }
2369
+ );
2370
+ try {
2371
+ const info = await new Promise((resolve4, reject) => {
2372
+ const timer = setTimeout(() => {
2373
+ reject(new Error(`Timed out waiting for daemon to start (30s). Check log: ${logFile}`));
2374
+ }, 3e4);
2375
+ child.on("message", (msg) => {
2376
+ const m = msg;
2377
+ if (m.type === "ready") {
2378
+ clearTimeout(timer);
2379
+ resolve4({
2380
+ pid: child.pid,
2381
+ port: m.port,
2382
+ controlUrl: m.controlUrl,
2383
+ viewerUrl: m.viewerUrl,
2384
+ pairingCode: m.pairingCode,
2385
+ cwd: process.cwd(),
2386
+ startedAt: Date.now(),
2387
+ logFile
2388
+ });
2389
+ }
2390
+ });
2391
+ child.on("error", (err) => {
2392
+ clearTimeout(timer);
2393
+ reject(err);
2394
+ });
2395
+ child.on("exit", (code) => {
2396
+ clearTimeout(timer);
2397
+ reject(new Error(`Daemon exited with code ${code}. Check log: ${logFile}`));
2398
+ });
2399
+ });
2400
+ writeSession(name, info);
2401
+ child.disconnect();
2402
+ child.unref();
2403
+ closeSync(logFd);
2404
+ console.log(`
2405
+ Airloom session "${name}" started (PID ${info.pid})
2406
+ `);
2407
+ console.log(`Pairing Code: ${info.pairingCode}`);
2408
+ console.log(`Viewer URL: ${info.viewerUrl}`);
2409
+ console.log(`Host UI: ${info.controlUrl}`);
2410
+ console.log(`Log: ${info.logFile}
2411
+ `);
2412
+ } catch (err) {
2413
+ closeSync(logFd);
2414
+ try {
2415
+ child.kill();
2416
+ } catch {
2417
+ }
2418
+ throw err;
2419
+ }
2420
+ }
2421
+ function handleStop(nameOrNull, all) {
2422
+ if (all) {
2423
+ const sessions = listAllSessions();
2424
+ if (sessions.length === 0) {
2425
+ console.log("No running sessions.");
2426
+ return;
2427
+ }
2428
+ for (const { name, info: info2 } of sessions) {
2429
+ try {
2430
+ process.kill(info2.pid, "SIGTERM");
2431
+ } catch {
2432
+ }
2433
+ removeSession(name);
2434
+ console.log(`Stopped "${name}" (PID ${info2.pid})`);
2435
+ }
2436
+ return;
2437
+ }
2438
+ const target = nameOrNull || "default";
2439
+ const info = readSession(target);
2440
+ if (!info) {
2441
+ console.error(`No session named "${target}" found.`);
2442
+ process.exit(1);
2443
+ }
2444
+ if (!isAlive(info.pid)) {
2445
+ removeSession(target);
2446
+ console.log(`Session "${target}" was not running (cleaned up stale entry).`);
2447
+ return;
2448
+ }
2449
+ try {
2450
+ process.kill(info.pid, "SIGTERM");
2451
+ } catch {
2452
+ }
2453
+ removeSession(target);
2454
+ console.log(`Stopped "${target}" (PID ${info.pid})`);
2455
+ }
2456
+ function handleList() {
2457
+ const sessions = listAllSessions();
2458
+ if (sessions.length === 0) {
2459
+ console.log("No running sessions.");
2460
+ return;
2461
+ }
2462
+ console.log("NAME PORT PID STARTED CWD");
2463
+ for (const { name, info } of sessions) {
2464
+ const age = formatAge(Date.now() - info.startedAt);
2465
+ console.log(
2466
+ name.padEnd(16) + String(info.port).padEnd(7) + String(info.pid).padEnd(9) + age.padEnd(17) + info.cwd
2467
+ );
2468
+ }
2469
+ }
2470
+
2239
2471
  // src/index.ts
2240
2472
  var QRCode = null;
2241
2473
  async function getQRCode() {
2242
2474
  if (!QRCode) QRCode = await import("qrcode");
2243
2475
  return QRCode;
2244
2476
  }
2245
- log("[host] Module loaded");
2246
2477
  function parseArgs(argv) {
2247
2478
  const args = {};
2248
2479
  const rest = argv.slice(2);
@@ -2272,7 +2503,15 @@ function printHelp() {
2272
2503
  Airloom \u2014 Run AI on your computer, control it from your phone.
2273
2504
 
2274
2505
  Usage:
2275
- airloom [options]
2506
+ airloom [options] Start in foreground (default)
2507
+ airloom start [options] Start as a background daemon
2508
+ airloom stop [name] Stop a background session
2509
+ airloom stop --all Stop all background sessions
2510
+ airloom list List running background sessions
2511
+
2512
+ Background options:
2513
+ --name <name> Session name (default: "default").
2514
+ Allows multiple independent sessions.
2276
2515
 
2277
2516
  Options:
2278
2517
  --cli <command> CLI command to use as the AI adapter.
@@ -2297,22 +2536,6 @@ Environment variables:
2297
2536
  HOST_BIND Host bind address (default: 127.0.0.1).
2298
2537
  `.trimStart());
2299
2538
  }
2300
- var cliArgs = parseArgs(process.argv);
2301
- if (cliArgs.help) {
2302
- printHelp();
2303
- process.exit(0);
2304
- }
2305
- var IS_DEV = !process.env.VIEWER_URL && !new URL(import.meta.url).pathname.includes("node_modules");
2306
- var env = parseHostEnv(cliArgs.port, IS_DEV);
2307
- var VIEWER_URL = env.viewerUrl;
2308
- var RELAY_URL = env.relayUrl;
2309
- var ABLY_API_KEY = env.ablyApiKey;
2310
- var ABLY_TOKEN_TTL = env.ablyTokenTtlMs;
2311
- var HOST_PORT = env.hostPort;
2312
- var HOST_BIND = env.hostBind;
2313
- var useAbly = env.useAbly;
2314
- var isDefaultKey = env.isDefaultAblyKey;
2315
- var __dirname = dirname2(fileURLToPath(import.meta.url));
2316
2539
  function getLanIP() {
2317
2540
  for (const ifaces of Object.values(networkInterfaces())) {
2318
2541
  for (const iface of ifaces ?? []) {
@@ -2321,14 +2544,31 @@ function getLanIP() {
2321
2544
  }
2322
2545
  return void 0;
2323
2546
  }
2324
- function resolveViewerDir() {
2325
- const prod = resolve3(__dirname, "viewer");
2547
+ function resolveViewerDir(base) {
2548
+ const prod = resolve3(base, "viewer");
2326
2549
  if (existsSync4(prod)) return prod;
2327
- const dev = resolve3(__dirname, "../../viewer/dist");
2550
+ const dev = resolve3(base, "../../viewer/dist");
2328
2551
  if (existsSync4(dev)) return dev;
2329
2552
  return void 0;
2330
2553
  }
2331
2554
  async function main() {
2555
+ const cliArgs = parseArgs(process.argv);
2556
+ if (cliArgs.help) {
2557
+ printHelp();
2558
+ process.exit(0);
2559
+ }
2560
+ const IS_DEV = !process.env.VIEWER_URL && !new URL(import.meta.url).pathname.includes("node_modules");
2561
+ const env = parseHostEnv(cliArgs.port, IS_DEV);
2562
+ const VIEWER_URL = env.viewerUrl;
2563
+ const RELAY_URL = env.relayUrl;
2564
+ const ABLY_API_KEY = env.ablyApiKey;
2565
+ const ABLY_TOKEN_TTL = env.ablyTokenTtlMs;
2566
+ const HOST_PORT = env.hostPort;
2567
+ const HOST_BIND = env.hostBind;
2568
+ const useAbly = env.useAbly;
2569
+ const isDefaultKey = env.isDefaultAblyKey;
2570
+ const isDaemonChild = process.argv.includes("--_daemon");
2571
+ const __dirname = dirname2(fileURLToPath(import.meta.url));
2332
2572
  console.log("Airloom - Host");
2333
2573
  console.log("==============\n");
2334
2574
  if (useAbly) {
@@ -2480,7 +2720,7 @@ async function main() {
2480
2720
  }
2481
2721
  }
2482
2722
  }
2483
- const viewerDir = resolveViewerDir();
2723
+ const viewerDir = resolveViewerDir(__dirname);
2484
2724
  if (viewerDir) {
2485
2725
  log(`[host] Viewer files: ${viewerDir}`);
2486
2726
  } else {
@@ -2517,16 +2757,23 @@ async function main() {
2517
2757
  if (lanViewerUrl) console.log(`LAN Viewer: ${lanViewerUrl}`);
2518
2758
  }
2519
2759
  if (!useAbly) console.log(`Relay: ${RELAY_URL}`);
2520
- const localUrl = `http://localhost:${port}`;
2521
- const controlUrl = encodeControlUrl(localUrl, controlToken);
2760
+ const controlBase = isLoopbackBind(HOST_BIND) ? `http://localhost:${port}` : lanBaseUrl;
2761
+ const controlUrl = encodeControlUrl(controlBase, controlToken);
2522
2762
  console.log(`Host UI: ${controlUrl}`);
2523
- const isSSH = !!(process.env.SSH_CONNECTION || process.env.SSH_TTY || process.env.SSH_CLIENT);
2524
- if (isSSH) {
2525
- console.log("\n (SSH session detected \u2014 open the Host UI URL above in a local browser)");
2763
+ if (isDaemonChild && typeof process.send === "function") {
2764
+ process.send({ type: "ready", port, controlUrl, viewerUrl: qrTarget, pairingCode: displayCode });
2765
+ }
2766
+ if (isDaemonChild) {
2767
+ } else if (env.isSSH) {
2768
+ if (isLoopbackBind(HOST_BIND)) {
2769
+ console.log("\n (SSH session detected but server is bound to localhost \u2014 set HOST_BIND=0.0.0.0 to allow remote access)");
2770
+ } else {
2771
+ console.log("\n (SSH session \u2014 open the Host UI URL above in a browser on your local machine)");
2772
+ }
2526
2773
  } else {
2527
- import("node:child_process").then(({ exec }) => {
2774
+ import("node:child_process").then(({ execFile }) => {
2528
2775
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2529
- exec(`${cmd} ${controlUrl}`);
2776
+ execFile(cmd, [controlUrl]);
2530
2777
  }).catch(() => {
2531
2778
  });
2532
2779
  }
@@ -2593,7 +2840,44 @@ async function main() {
2593
2840
  process.on("SIGINT", shutdown);
2594
2841
  process.on("SIGTERM", shutdown);
2595
2842
  }
2596
- main().catch((err) => {
2597
- logError("Fatal error:", err);
2598
- process.exit(1);
2599
- });
2843
+ var _cmd = process.argv[2];
2844
+ if (_cmd === "start" || _cmd === "stop" || _cmd === "list") {
2845
+ (async () => {
2846
+ const _args = process.argv.slice(3);
2847
+ if (_cmd === "list") {
2848
+ handleList();
2849
+ return;
2850
+ }
2851
+ if (_cmd === "stop") {
2852
+ let stopName = null;
2853
+ let stopAll = false;
2854
+ for (const a of _args) {
2855
+ if (a === "--all") stopAll = true;
2856
+ else if (!a.startsWith("-")) stopName = a;
2857
+ }
2858
+ handleStop(stopName, stopAll);
2859
+ return;
2860
+ }
2861
+ let startName = "default";
2862
+ const hostArgs = [];
2863
+ for (let i = 0; i < _args.length; i++) {
2864
+ const a = _args[i];
2865
+ if (a === "--name" && i + 1 < _args.length) {
2866
+ startName = _args[++i];
2867
+ } else if (a.startsWith("--name=")) {
2868
+ startName = a.slice(7);
2869
+ } else {
2870
+ hostArgs.push(a);
2871
+ }
2872
+ }
2873
+ await handleStart(startName, hostArgs);
2874
+ })().catch((err) => {
2875
+ console.error(err.message);
2876
+ process.exit(1);
2877
+ });
2878
+ } else {
2879
+ main().catch((err) => {
2880
+ logError("Fatal error:", err);
2881
+ process.exit(1);
2882
+ });
2883
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airloom",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "description": "Run AI on your computer, control it from your phone. E2E encrypted.",
5
5
  "type": "module",
6
6
  "bin": {