@beeuni/agent-tunnel 0.1.2 → 0.1.3

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 CHANGED
@@ -68,7 +68,10 @@ It starts at login, and the supervisor restarts it after every exit (launchd
68
68
  `KeepAlive` with a 10 s throttle, systemd `Restart=always`, a 5 s Windows loop).
69
69
  On Linux, install enables `loginctl enable-linger` so the user service keeps
70
70
  running after logout and starts at boot; when that is not allowed, install
71
- prints a warning. The Windows task runs hidden, on battery, with no time limit.
71
+ prints a warning. The Windows task runs PowerShell under `conhost --headless`
72
+ (no console window, also where Windows Terminal is the default terminal), on
73
+ battery, with no time limit. Every process the agent starts on Windows sets
74
+ `windowsHide`, so no console window opens.
72
75
  The agent itself never stops on its own:
73
76
 
74
77
  - It reconnects with exponential backoff from 1 s to 30 s, ±20 % jitter, forever.
@@ -181,9 +184,10 @@ change to the agent as `tunnel.access.set`:
181
184
  A pairing made from the Beeuni desktop app starts in `ask`: the app shows the
182
185
  prompt. A pairing made with this CLI alone starts in `always`, because nothing
183
186
  on the machine could answer a prompt. This is a deliberate deviation from
184
- "new pairings default to ask". Every new pairing resets `access.json`, so a
185
- grant or `always` from an earlier pairing never carries over, and `logout`
186
- deletes it. The agent home (`config.json`, `access.json`, `desktop-app.json`)
187
+ "new pairings default to ask". The mode and keep awake are the owner's
188
+ machine settings: pairing again (`--reauth`, a rejected or stale credential)
189
+ keeps them, and only a grant or a denial from the earlier pairing is dropped.
190
+ `logout` deletes `access.json`, so the next pairing starts from the default. The agent home (`config.json`, `access.json`, `desktop-app.json`)
187
191
  is always a blocked path: no file or shell call can change these files.
188
192
 
189
193
  The agent reports its state to Beeuni as the signed notification
@@ -233,9 +237,16 @@ you trust with this computer.
233
237
 
234
238
  ## Computer Use driver
235
239
 
236
- Agent Tunnel never downloads or executes a desktop driver. Install `cua-driver`
237
- locally before enabling Computer Use. The tunnel uses an existing binary from
238
- `CUA_DRIVER_BIN`, `~/.local/bin`, `/usr/local/bin`, or `/opt/homebrew/bin`.
240
+ Agent Tunnel never downloads or executes a desktop driver. The Beeuni desktop
241
+ app bundles `cua-driver` on macOS and Windows (x64), and the agent it runs uses
242
+ that copy. A CLI install uses an existing binary from `CUA_DRIVER_BIN`,
243
+ `~/.local/bin`, `/usr/local/bin`, or `/opt/homebrew/bin`.
244
+
245
+ An approved `desktop` registers whenever the driver is present. Missing macOS
246
+ grants do not unregister it: the agent reports `desktopPermissions` and the
247
+ setup asks for them. When the driver is missing or rejected (wrong owner,
248
+ group-writable), `desktop` does not register and the reason is reported as
249
+ `unavailableCapabilities.desktop` in `status --json` and in `machineInfo`.
239
250
  Treat that binary as trusted local code. Agent Tunnel does not verify or update
240
251
  it.
241
252
 
package/dist/agent-cli.js CHANGED
@@ -3516,6 +3516,15 @@ function writePrivateJson(file, home, value) {
3516
3516
  function writeAccess(state, home = agentTunnelHome()) {
3517
3517
  writePrivateJson(accessFilePath(home), home, state);
3518
3518
  }
3519
+ function accessAfterPairing(home, desktopApp) {
3520
+ const kept = existsSync2(accessFilePath(home)) ? readAccess(home) : null;
3521
+ return {
3522
+ mode: kept?.mode ?? (desktopApp ? "ask" : "always"),
3523
+ grantedUntil: null,
3524
+ deniedUntil: null,
3525
+ keepAwake: kept?.keepAwake ?? false
3526
+ };
3527
+ }
3519
3528
  function decideAccess(state, now = Date.now()) {
3520
3529
  if (state.mode === "always")
3521
3530
  return "run";
@@ -3803,7 +3812,7 @@ function machineDisplayName({
3803
3812
  os = platform2(),
3804
3813
  host = hostname(),
3805
3814
  env = process.env,
3806
- run = (command, args) => execFileSync(command, args, { encoding: "utf8", timeout: 1000, stdio: ["ignore", "pipe", "ignore"] })
3815
+ run = (command, args) => execFileSync(command, args, { encoding: "utf8", timeout: 1000, stdio: ["ignore", "pipe", "ignore"], windowsHide: true })
3807
3816
  } = {}) {
3808
3817
  const fallback = host.replace(/\.local$/i, "") || host;
3809
3818
  try {
@@ -3815,7 +3824,7 @@ function machineDisplayName({
3815
3824
  }
3816
3825
  function machineId({
3817
3826
  os = platform2(),
3818
- run = (command, args) => execFileSync(command, args, { encoding: "utf8", timeout: 2000, stdio: ["ignore", "pipe", "ignore"] }),
3827
+ run = (command, args) => execFileSync(command, args, { encoding: "utf8", timeout: 2000, stdio: ["ignore", "pipe", "ignore"], windowsHide: true }),
3819
3828
  read = (path) => readFileSync4(path, "utf8")
3820
3829
  } = {}) {
3821
3830
  const attempt = (source) => {
@@ -4460,7 +4469,8 @@ class TunnelAgent {
4460
4469
  osVersion: release(),
4461
4470
  agentVersion: AGENT_VERSION,
4462
4471
  homeDir: this.config.workingDir,
4463
- allowedPaths: this.config.allowedPaths
4472
+ allowedPaths: this.config.allowedPaths,
4473
+ unavailableCapabilities: Object.fromEntries(this.registry.unavailable)
4464
4474
  }
4465
4475
  }
4466
4476
  });
@@ -4585,7 +4595,7 @@ class TunnelAgent {
4585
4595
  const blocker = keepAwakeCommand(platform3(), process.pid);
4586
4596
  if (!blocker)
4587
4597
  return;
4588
- const child = spawn3(blocker.command, blocker.args, { stdio: "ignore" });
4598
+ const child = spawn3(blocker.command, blocker.args, { stdio: "ignore", windowsHide: true });
4589
4599
  child.on("error", () => {
4590
4600
  this.keepAwakeFailed = true;
4591
4601
  log(`${c.yellow}!${c.reset}`, `Keep awake is unavailable: ${blocker.command} could not start`);
@@ -4753,6 +4763,7 @@ async function printStartupBanner({
4753
4763
  // src/agent/capabilities/index.ts
4754
4764
  class CapabilityRegistry {
4755
4765
  capabilities = new Map;
4766
+ unavailable = new Map;
4756
4767
  register(capability) {
4757
4768
  this.capabilities.set(capability.name, capability);
4758
4769
  }
@@ -4778,7 +4789,7 @@ class CapabilityRegistry {
4778
4789
  // src/agent/capabilities/desktop/cua-driver.ts
4779
4790
  import { spawn as spawn4 } from "child_process";
4780
4791
  import { existsSync as existsSync3, readFileSync as readFileSync6, realpathSync, rmSync as rmSync2, statSync as statSync2 } from "fs";
4781
- import { homedir as homedir3, platform as platform5, tmpdir } from "os";
4792
+ import { homedir as homedir3, platform as platform5, tmpdir, userInfo } from "os";
4782
4793
  import { basename as basename2, dirname as dirname3, join as join5 } from "path";
4783
4794
  var MAX_DRIVER_OUTPUT_BYTES = 5 * 1024 * 1024;
4784
4795
  var DRIVER_ENV_KEYS = [
@@ -4793,7 +4804,19 @@ var DRIVER_ENV_KEYS = [
4793
4804
  "WAYLAND_DISPLAY",
4794
4805
  "XDG_RUNTIME_DIR",
4795
4806
  "XAUTHORITY",
4796
- "DBUS_SESSION_BUS_ADDRESS"
4807
+ "DBUS_SESSION_BUS_ADDRESS",
4808
+ "SystemRoot",
4809
+ "windir",
4810
+ "USERPROFILE",
4811
+ "USERNAME",
4812
+ "USERDOMAIN",
4813
+ "APPDATA",
4814
+ "LOCALAPPDATA",
4815
+ "ProgramData",
4816
+ "TEMP",
4817
+ "TMP",
4818
+ "ComSpec",
4819
+ "PATHEXT"
4797
4820
  ];
4798
4821
  function driverEnvironment(extra = {}) {
4799
4822
  const env = { CUA_DRIVER_RS_TELEMETRY_ENABLED: "0" };
@@ -4856,7 +4879,8 @@ function execFile(cmd, args, timeoutMs = 30000, env = driverEnvironment()) {
4856
4879
  return new Promise((resolve2, reject) => {
4857
4880
  const proc = spawn4(cmd, args, {
4858
4881
  stdio: ["ignore", "pipe", "pipe"],
4859
- env
4882
+ env,
4883
+ windowsHide: true
4860
4884
  });
4861
4885
  let stdout = "";
4862
4886
  let stderr = "";
@@ -4943,9 +4967,8 @@ class CuaDriver {
4943
4967
  constructor(options = {}) {
4944
4968
  this.binary = options.binary ?? null;
4945
4969
  this.embedded = options.embedded;
4946
- const uid = typeof process.getuid === "function" ? process.getuid() : "user";
4947
4970
  const home = serviceLabelFor(agentTunnelHome()).slice(SERVICE_LABEL.length);
4948
- this.socketPath = options.socketPath ?? join5(tmpdir(), `beeuni-cua-${uid}${home}.sock`);
4971
+ this.socketPath = options.socketPath ?? (process.platform === "win32" ? `\\\\.\\pipe\\beeuni-cua-${userInfo().username}${home}` : join5(tmpdir(), `beeuni-cua-${typeof process.getuid === "function" ? process.getuid() : "user"}${home}.sock`));
4949
4972
  this.hostBundleId = options.hostBundleId;
4950
4973
  }
4951
4974
  isEmbedded(binary) {
@@ -4954,10 +4977,11 @@ class CuaDriver {
4954
4977
  mode(binary) {
4955
4978
  if (!this.isEmbedded(binary))
4956
4979
  return { env: driverEnvironment(), socket: [] };
4980
+ const bundleId = this.hostBundleId ?? hostBundleId();
4957
4981
  return {
4958
4982
  env: driverEnvironment({
4959
4983
  CUA_DRIVER_EMBEDDED: "1",
4960
- CUA_DRIVER_HOST_BUNDLE_ID: this.hostBundleId ?? hostBundleId()
4984
+ ...bundleId ? { CUA_DRIVER_HOST_BUNDLE_ID: bundleId } : {}
4961
4985
  }),
4962
4986
  socket: ["--socket", this.socketPath]
4963
4987
  };
@@ -5031,7 +5055,8 @@ class CuaDriver {
5031
5055
  const child = spawn4("open", ["-n", "-g", "-a", "CuaDriver", "--args", "serve"], {
5032
5056
  detached: true,
5033
5057
  stdio: "ignore",
5034
- env: driverEnvironment()
5058
+ env: driverEnvironment(),
5059
+ windowsHide: true
5035
5060
  });
5036
5061
  child.unref();
5037
5062
  await sleep2(750);
@@ -5039,7 +5064,8 @@ class CuaDriver {
5039
5064
  const child = spawn4(bin, ["serve"], {
5040
5065
  detached: true,
5041
5066
  stdio: "ignore",
5042
- env: driverEnvironment()
5067
+ env: driverEnvironment(),
5068
+ windowsHide: true
5043
5069
  });
5044
5070
  child.unref();
5045
5071
  await sleep2(750);
@@ -5056,7 +5082,11 @@ class CuaDriver {
5056
5082
  if (this.daemon && this.daemon.exitCode === null)
5057
5083
  return;
5058
5084
  const { env, socket } = this.mode(bin);
5059
- if (existsSync3(this.socketPath)) {
5085
+ if (process.platform === "win32") {
5086
+ await execFile(bin, ["stop", ...socket], 1e4, env).catch(() => {
5087
+ return;
5088
+ });
5089
+ } else if (existsSync3(this.socketPath)) {
5060
5090
  await execFile(bin, ["stop", ...socket], 1e4, env).catch(() => {
5061
5091
  return;
5062
5092
  });
@@ -5064,7 +5094,8 @@ class CuaDriver {
5064
5094
  }
5065
5095
  const child = spawn4(bin, ["serve", "--embedded", "--no-permissions-gate", ...socket], {
5066
5096
  stdio: "ignore",
5067
- env
5097
+ env,
5098
+ windowsHide: true
5068
5099
  });
5069
5100
  child.unref();
5070
5101
  child.on("exit", () => {
@@ -5075,16 +5106,23 @@ class CuaDriver {
5075
5106
  });
5076
5107
  this.daemon = child;
5077
5108
  process.once("exit", () => child.kill());
5078
- for (let waited = 0;waited < 1e4 && !existsSync3(this.socketPath); waited += 50) {
5109
+ const started = Date.now();
5110
+ while (Date.now() - started < 1e4 && !await this.endpointUp(bin)) {
5079
5111
  if (child.exitCode !== null)
5080
5112
  break;
5081
5113
  await sleep2(50);
5082
5114
  }
5083
- if (!existsSync3(this.socketPath)) {
5115
+ if (!await this.endpointUp(bin)) {
5084
5116
  child.kill();
5085
5117
  throw new Error("cua-driver did not start (no socket after 10 s)");
5086
5118
  }
5087
5119
  }
5120
+ async endpointUp(bin) {
5121
+ if (process.platform !== "win32")
5122
+ return existsSync3(this.socketPath);
5123
+ const { env, socket } = this.mode(bin);
5124
+ return execFile(bin, ["status", ...socket], 5000, env).then(({ stdout }) => !/not\s+running|unhealthy/i.test(stdout), () => false);
5125
+ }
5088
5126
  async permissionOwner() {
5089
5127
  return this.isEmbedded(await this.ensureInstalled()) ? "Beeuni" : "CuaDriver";
5090
5128
  }
@@ -5105,7 +5143,7 @@ class CuaDriver {
5105
5143
  async ensureDaemonReady() {
5106
5144
  const bin = await this.ensureInstalled();
5107
5145
  if (this.isEmbedded(bin)) {
5108
- if (this.daemon?.exitCode === null && existsSync3(this.socketPath))
5146
+ if (this.daemon?.exitCode === null && (process.platform === "win32" ? this.daemonReady : existsSync3(this.socketPath)))
5109
5147
  return;
5110
5148
  this.stop();
5111
5149
  await this.startDaemon();
@@ -5551,7 +5589,8 @@ function createShellCapability(config) {
5551
5589
  cwd,
5552
5590
  shell: false,
5553
5591
  timeout,
5554
- env: safeEnv
5592
+ env: safeEnv,
5593
+ windowsHide: true
5555
5594
  });
5556
5595
  let stdout = "";
5557
5596
  let stderr = "";
@@ -5607,6 +5646,14 @@ function createShellCapability(config) {
5607
5646
  }
5608
5647
 
5609
5648
  // src/agent/capabilities/enabled-registry.ts
5649
+ var DRIVER_NOT_FOUND = "cua-driver was not found. The Beeuni desktop app bundles it on macOS and Windows; a CLI install needs it in CUA_DRIVER_BIN, ~/.local/bin, /usr/local/bin or /opt/homebrew/bin.";
5650
+ function desktopUnavailableReason(findDesktopDriver = findCuaDriverBinary) {
5651
+ try {
5652
+ return findDesktopDriver() ? null : DRIVER_NOT_FOUND;
5653
+ } catch (error) {
5654
+ return error instanceof Error ? error.message : String(error);
5655
+ }
5656
+ }
5610
5657
  function createEnabledCapabilityRegistry(config, findDesktopDriver = findCuaDriverBinary, desktop = {}) {
5611
5658
  const registry = new CapabilityRegistry;
5612
5659
  const enabled = new Set(config.enabledCapabilities ?? ["filesystem", "shell", "desktop"]);
@@ -5614,8 +5661,12 @@ function createEnabledCapabilityRegistry(config, findDesktopDriver = findCuaDriv
5614
5661
  registry.register(createFilesystemCapability(config));
5615
5662
  if (enabled.has("shell"))
5616
5663
  registry.register(createShellCapability(config));
5617
- if (enabled.has("desktop") && findDesktopDriver()) {
5618
- registry.register(createDesktopCapability(desktop));
5664
+ if (enabled.has("desktop")) {
5665
+ const reason = desktopUnavailableReason(findDesktopDriver);
5666
+ if (reason)
5667
+ registry.unavailable.set("desktop", reason);
5668
+ else
5669
+ registry.register(createDesktopCapability(desktop));
5619
5670
  }
5620
5671
  return registry;
5621
5672
  }
@@ -5764,7 +5815,7 @@ import { join as join10 } from "path";
5764
5815
  // src/agent/service-drivers.ts
5765
5816
  import { spawnSync } from "child_process";
5766
5817
  import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync8, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
5767
- import { homedir as homedir4, platform as platform6, userInfo } from "os";
5818
+ import { homedir as homedir4, platform as platform6, userInfo as userInfo2 } from "os";
5768
5819
  import { dirname as dirname6, join as join9 } from "path";
5769
5820
 
5770
5821
  // src/agent/service-quoting.ts
@@ -5787,7 +5838,7 @@ function posixShellCommand(runner) {
5787
5838
  }
5788
5839
  var supervisor = {
5789
5840
  run(command, args) {
5790
- const result = spawnSync(command, args, { encoding: "utf8" });
5841
+ const result = spawnSync(command, args, { encoding: "utf8", windowsHide: true });
5791
5842
  return {
5792
5843
  ok: result.status === 0,
5793
5844
  detail: [result.stdout, result.stderr].filter(Boolean).join(`
@@ -5805,7 +5856,7 @@ function joinDetails(...results) {
5805
5856
  `);
5806
5857
  }
5807
5858
  function launchdTarget() {
5808
- const uid = typeof process.getuid === "function" ? process.getuid() : userInfo().uid;
5859
+ const uid = typeof process.getuid === "function" ? process.getuid() : userInfo2().uid;
5809
5860
  return `gui/${uid}`;
5810
5861
  }
5811
5862
  function logPaths(paths) {
@@ -5878,7 +5929,8 @@ function renderWindowsPowerShellScript(runner) {
5878
5929
  const args = runner.args.map(powershellQuote).join(" ");
5879
5930
  const env = Object.entries(runner.env ?? {}).map(([key, value]) => `$env:${key} = ${powershellQuote(value)}
5880
5931
  `).join("");
5881
- return `$ErrorActionPreference = 'Continue'
5932
+ return `# Run by the Beeuni task under conhost --headless: no console window.
5933
+ $ErrorActionPreference = 'Continue'
5882
5934
  ${env}while ($true) {
5883
5935
  # The pipe makes PowerShell wait for a GUI-subsystem binary (the desktop
5884
5936
  # app's Beeuni.exe) and set $LASTEXITCODE; a bare call returns at once.
@@ -5938,7 +5990,7 @@ var launchd = {
5938
5990
  },
5939
5991
  status(paths, installed) {
5940
5992
  const disabled = run("launchctl", ["print-disabled", launchdTarget()]);
5941
- const enabled = !new RegExp(`"${paths.label.replace(/\./g, "\\.")}" => (disabled|true)`).test(disabled.detail);
5993
+ const enabled = ![`"${paths.label}" => disabled`, `"${paths.label}" => true`].some((line) => disabled.detail.includes(line));
5942
5994
  const status = run("launchctl", ["print", `${launchdTarget()}/${paths.label}`]);
5943
5995
  return {
5944
5996
  active: status.ok,
@@ -5993,7 +6045,7 @@ var systemd = {
5993
6045
  }
5994
6046
  };
5995
6047
  function ensureLinger() {
5996
- const user = userInfo().username;
6048
+ const user = userInfo2().username;
5997
6049
  const linger = run("loginctl", ["show-user", user, "-p", "Linger"]);
5998
6050
  if (/Linger=yes/.test(linger.detail))
5999
6051
  return { ok: true, detail: "" };
@@ -6018,11 +6070,11 @@ function renderWindowsTaskXml(paths, user) {
6018
6070
  <StartWhenAvailable>true</StartWhenAvailable>
6019
6071
  <Enabled>true</Enabled>
6020
6072
  </Settings>
6021
- <Actions Context="Author"><Exec><Command>powershell.exe</Command><Arguments>${xmlEscape(`-NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File "${paths.windowsScript}"`)}</Arguments></Exec></Actions>
6073
+ <Actions Context="Author"><Exec><Command>conhost.exe</Command><Arguments>${xmlEscape(`--headless powershell.exe -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File "${paths.windowsScript}"`)}</Arguments></Exec></Actions>
6022
6074
  </Task>
6023
6075
  `;
6024
6076
  }
6025
- var windowsUser = () => process.env.USERDOMAIN ? `${process.env.USERDOMAIN}\\${userInfo().username}` : userInfo().username;
6077
+ var windowsUser = () => process.env.USERDOMAIN ? `${process.env.USERDOMAIN}\\${userInfo2().username}` : userInfo2().username;
6026
6078
  var windowsTaskXmlPath = (paths) => paths.windowsScript.replace(/\.ps1$/, ".xml");
6027
6079
  var scheduledTask = {
6028
6080
  unitPath: (paths) => paths.windowsScript,
@@ -6386,9 +6438,9 @@ function startAgent(config, options = {}) {
6386
6438
  const registry = createEnabledCapabilityRegistry(config, undefined, {
6387
6439
  onPermissionMissing: restart?.permissionMissing
6388
6440
  });
6389
- if (config.enabledCapabilities?.includes("desktop") && !registry.has("desktop")) {
6390
- console.error("[agent-tunnel] Computer Use is approved but unavailable: install the trusted cua-driver locally, then restart Agent Tunnel.");
6391
- }
6441
+ const desktopReason = registry.unavailable.get("desktop");
6442
+ if (desktopReason)
6443
+ console.error(`[agent-tunnel] Computer Use is approved but unavailable: ${desktopReason}`);
6392
6444
  if (options.service) {
6393
6445
  process.stdout.write(`[agent-tunnel] service starting: ${config.tunnelId} -> ${config.apiUrl}
6394
6446
  `);
@@ -6405,7 +6457,6 @@ function startAgent(config, options = {}) {
6405
6457
  persistent: options.service === true,
6406
6458
  reloadConfig: () => loadConfig({ apiUrl: config.apiUrl })
6407
6459
  });
6408
- agent.connect();
6409
6460
  const shutdown = () => {
6410
6461
  if (!options.service)
6411
6462
  console.log(`
@@ -6415,6 +6466,7 @@ ${c.dim} Shutting down…${c.reset}`);
6415
6466
  };
6416
6467
  process.on("SIGTERM", shutdown);
6417
6468
  process.on("SIGINT", shutdown);
6469
+ agent.connect();
6418
6470
  }
6419
6471
  function chooseBackgroundMode(flags) {
6420
6472
  if (anyFlag(flags, BACKGROUND_FLAGS))
@@ -6525,12 +6577,7 @@ async function pairThisMachine(apiUrl, flags, lease) {
6525
6577
  process.exit(1);
6526
6578
  }
6527
6579
  saveCredentials(outcome.tunnelId, outcome.token, apiUrl, outcome.capabilities);
6528
- writeAccess({
6529
- mode: existsSync7(desktopAppPath()) ? "ask" : "always",
6530
- grantedUntil: null,
6531
- deniedUntil: null,
6532
- keepAwake: readAccess().keepAwake
6533
- });
6580
+ writeAccess(accessAfterPairing(agentTunnelHome(), existsSync7(desktopAppPath())));
6534
6581
  if (jsonMode) {
6535
6582
  emit("approved", { tunnelId: outcome.tunnelId, capabilities: outcome.capabilities });
6536
6583
  } else {
@@ -6637,12 +6684,14 @@ function commandStatus(flags) {
6637
6684
  const service = getServiceStatus();
6638
6685
  const paired = Boolean(config.token && config.tunnelId);
6639
6686
  const approved = new Set(config.enabledCapabilities ?? []);
6687
+ const desktopReason = approved.has("desktop") ? desktopUnavailableReason() : null;
6640
6688
  if (isTruthyFlag(flags.json)) {
6641
6689
  console.log(JSON.stringify({
6642
6690
  paired,
6643
6691
  tunnelId: paired ? config.tunnelId : null,
6644
6692
  apiUrl: config.apiUrl,
6645
6693
  capabilities: [...approved],
6694
+ unavailableCapabilities: desktopReason ? { desktop: desktopReason } : {},
6646
6695
  version: agentTunnelVersion(),
6647
6696
  home: agentTunnelHome(),
6648
6697
  serviceLabel: getServicePaths().label,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beeuni/agent-tunnel",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Tunnel relay between cloud AI agents and local machines — server relay, local agent, client SDK, JSON-RPC, HMAC signing",
@@ -12,6 +12,12 @@
12
12
  "dist/",
13
13
  "README.md"
14
14
  ],
15
+ "scripts": {
16
+ "build": "bun build src/agent/cli.ts --target=node --outfile=dist/agent-cli.js --banner='#!/usr/bin/env node' && bun build src/client/cli.ts --target=node --outfile=dist/client-cli.js --banner='#!/usr/bin/env node' && node -e \"const fs=require('node:fs'); fs.chmodSync('dist/agent-cli.js', 0o755); fs.chmodSync('dist/client-cli.js', 0o755);\"",
17
+ "prepack": "bun run build",
18
+ "typecheck": "tsc --noEmit",
19
+ "test": "bun test --timeout ${BEEUNI_TEST_TIMEOUT_MS:-15000}"
20
+ },
15
21
  "dependencies": {
16
22
  "hono": "^4.13.7",
17
23
  "ws": "^8.18.0"