@termhub/agent 0.1.2 → 0.1.4

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 (3) hide show
  1. package/README.md +9 -1
  2. package/dist/cli.js +242 -49
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,7 +8,7 @@ sleeping.
8
8
  ## Install
9
9
 
10
10
  ```bash
11
- npm i -g @termhub/agent
11
+ npm i -g @termhub/agent && termhub-agent --version
12
12
  ```
13
13
 
14
14
  Requires Node 20+ and `tmux` on the machine you're pairing.
@@ -55,6 +55,14 @@ Full Disk Access: **Ajustes → Privacidade e Segurança → Acesso Total ao Dis
55
55
 
56
56
  ## Troubleshooting
57
57
 
58
+ - **`termhub-agent: command not found` right after `npm i -g`**:
59
+ - **asdf**: asdf only creates shims for new global binaries after `asdf reshim nodejs` — run it once
60
+ and the command appears (same after every `npm i -g` of a new package).
61
+ - Otherwise npm's global bin directory is not on your `PATH` (common with Node from `n`, Volta, a
62
+ custom `npm config set prefix`, or distro packages): `export PATH="$(npm prefix -g)/bin:$PATH"`
63
+ and add it to `~/.zshrc`/`~/.bashrc`.
64
+ - Or skip the PATH entirely: `npx -y @termhub/agent connect --url … --token …`.
65
+
58
66
  - **Terminals fail to open with `posix_spawnp failed.`** (visible in `~/.termhub/agent.log`): node-pty's
59
67
  prebuilt `spawn-helper` lost its exec bit — npm 11 skips the postinstall that sets it unless the
60
68
  package is approved. The agent repairs this itself on startup and in `termhub-agent doctor`; if the
package/dist/cli.js CHANGED
@@ -40,7 +40,8 @@ var machinePath = z.string().min(1).max(4096).refine((p) => (p === "~" || p.star
40
40
  var pasteName = z.string().min(1).max(255).regex(/^[A-Za-z0-9._-]+$/);
41
41
  var aiProvider = z.enum(["claude", "chatgpt", "gemini", "antigravity"]);
42
42
  var rpcErrorSchema = z.object({
43
- code: z.enum(["eperm", "notfound", "no_tmux", "timeout", "invalid", "internal"]),
43
+ /** `failed`: the operation ran on the machine and `message` says why it failed, in words meant for the user. */
44
+ code: z.enum(["eperm", "notfound", "no_tmux", "timeout", "invalid", "internal", "failed"]),
44
45
  message: z.string().max(2e3),
45
46
  path: z.string().max(4096).optional()
46
47
  });
@@ -60,7 +61,10 @@ var RPC = {
60
61
  recursive: z.boolean().optional()
61
62
  }), z.object({ stdout: z.string() })),
62
63
  "ai.credential": def(z.object({ provider: aiProvider, config_dir: machinePath.nullable() }), z.object({ stdout: z.string() }), 1e4),
63
- "file.paste": def(z.object({ name: pasteName, data_b64: z.string().min(1).max(28 * 1024 * 1024) }), z.object({ path: z.string() }), 6e4)
64
+ "file.paste": def(z.object({ name: pasteName, data_b64: z.string().min(1).max(28 * 1024 * 1024) }), z.object({ path: z.string() }), 6e4),
65
+ /** Monitor hooks (see @termhub/machine-ops hooks.ts): the agent writes the script, env and config entries under its own $HOME. */
66
+ "hooks.install": def(z.object({ hooks_url: z.string().min(1).max(2048).regex(/^https?:\/\/[^\s'"]+$/), token: z.string().min(1).max(256).regex(/^[A-Za-z0-9_-]+$/) }), z.object({ home: z.string(), claude: z.enum(["installed", "skipped"]), codex: z.enum(["installed", "skipped"]) }), 15e3),
67
+ "hooks.uninstall": def(z.object({}), z.object({ removed: z.boolean() }), 15e3)
64
68
  };
65
69
  var RPC_METHODS = Object.keys(RPC);
66
70
  var rpcMethod = z.enum(RPC_METHODS);
@@ -355,7 +359,7 @@ function deleteConfig() {
355
359
  }
356
360
 
357
361
  // src/run.ts
358
- import os4 from "os";
362
+ import os5 from "os";
359
363
 
360
364
  // src/exec.ts
361
365
  import { execFile } from "child_process";
@@ -372,16 +376,16 @@ var DETECT_TOOLS = ["tmux", "claude", "gh", "git", "node", "pnpm", "xcodebuild",
372
376
  var WDA_RUNNER_APP = "$HOME/.termhub/WebDriverAgent/DerivedData/Build/Products/Debug-iphonesimulator/WebDriverAgentRunner-Runner.app";
373
377
  var DETECT_SCRIPT = `echo OS:$(uname -s); for t in ${DETECT_TOOLS.join(" ")}; do command -v $t >/dev/null 2>&1 && echo CAP:$t; done; [ -d "${WDA_RUNNER_APP}" ] && echo CAP:wda; exit 0`;
374
378
  function parseDetect(stdout) {
375
- let os8 = null;
379
+ let os9 = null;
376
380
  const caps = [];
377
381
  for (const line of stdout.split("\n")) {
378
382
  if (line.startsWith("OS:")) {
379
383
  const raw = line.slice(3).trim().toLowerCase();
380
- os8 = raw === "darwin" ? "macos" : raw || null;
384
+ os9 = raw === "darwin" ? "macos" : raw || null;
381
385
  } else if (line.startsWith("CAP:"))
382
386
  caps.push(line.slice(4).trim());
383
387
  }
384
- return { os: os8, capabilities: caps };
388
+ return { os: os9, capabilities: caps };
385
389
  }
386
390
 
387
391
  // ../../packages/machine-ops/dist/hardware-script.js
@@ -526,13 +530,104 @@ function ptyEnv(base, shell) {
526
530
  };
527
531
  }
528
532
 
533
+ // ../../packages/machine-ops/dist/hooks.js
534
+ var HOOK_SCRIPT_REL = ".termhub/bin/termhub-hook";
535
+ var HOOK_ENV_REL = ".termhub/hook.env";
536
+ var HOOK_MARK = "termhub-hook";
537
+ var CLAUDE_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "Notification", "Stop", "SessionEnd"];
538
+ var HOOK_SCRIPT = `#!/bin/sh
539
+ # termhub monitor hook \u2014 installed by termhub; forwards Claude Code / Codex hook events to
540
+ # termhub tagged with the tmux session, so the app knows which tab is waiting for you.
541
+ # Safe to delete (also remove the entries in ~/.claude/settings.json and ~/.codex/config.toml).
542
+ TOOL="\${1:-claude}"
543
+ [ -f "$HOME/${HOOK_ENV_REL}" ] || exit 0
544
+ . "$HOME/${HOOK_ENV_REL}"
545
+ [ -n "$TERMHUB_HOOK_URL" ] && [ -n "$TERMHUB_HOOK_TOKEN" ] || exit 0
546
+ [ -n "$TMUX_PANE" ] || exit 0
547
+ PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
548
+ SESSION=$(tmux display-message -p -t "$TMUX_PANE" '#{session_name}' 2>/dev/null) || exit 0
549
+ [ -n "$SESSION" ] || exit 0
550
+ if [ "$TOOL" = codex ]; then EVENT="$2"; else EVENT=$(cat 2>/dev/null); fi
551
+ [ -n "$EVENT" ] || EVENT='{}'
552
+ { printf '{"tool":"%s","session":"%s","event":' "$TOOL" "$SESSION"; printf '%s' "$EVENT"; printf '}'; } |
553
+ curl -s -m 5 -o /dev/null -X POST "$TERMHUB_HOOK_URL" \\
554
+ -H "authorization: Bearer $TERMHUB_HOOK_TOKEN" -H 'content-type: application/json' --data-binary @- >/dev/null 2>&1 &
555
+ exit 0
556
+ `;
557
+ function hookEnvFile(hooksUrl, token) {
558
+ return `TERMHUB_HOOK_URL=${shellQuote(hooksUrl)}
559
+ TERMHUB_HOOK_TOKEN=${shellQuote(token)}
560
+ `;
561
+ }
562
+ var isOurs = (e) => !!e && typeof e === "object" && Array.isArray(e.hooks) && e.hooks.some((h) => typeof h?.command === "string" && h.command.includes(HOOK_MARK));
563
+ function mergeClaudeSettings(current, scriptPath) {
564
+ let settings = {};
565
+ if (current.trim()) {
566
+ const parsed = JSON.parse(current);
567
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
568
+ throw new Error("~/.claude/settings.json n\xE3o \xE9 um objeto JSON");
569
+ settings = parsed;
570
+ }
571
+ const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
572
+ for (const event of CLAUDE_HOOK_EVENTS) {
573
+ const list3 = Array.isArray(hooks[event]) ? hooks[event] : [];
574
+ const others = list3.filter((e) => !isOurs(e));
575
+ others.push({ hooks: [{ type: "command", command: `${scriptPath} claude`, timeout: 10 }] });
576
+ hooks[event] = others;
577
+ }
578
+ settings.hooks = hooks;
579
+ return `${JSON.stringify(settings, null, 2)}
580
+ `;
581
+ }
582
+ function stripClaudeSettings(current) {
583
+ if (!current.trim())
584
+ return current;
585
+ const parsed = JSON.parse(current);
586
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
587
+ return current;
588
+ const settings = parsed;
589
+ const hooks = settings.hooks;
590
+ if (hooks && typeof hooks === "object" && !Array.isArray(hooks)) {
591
+ const h = hooks;
592
+ for (const key of Object.keys(h)) {
593
+ if (!Array.isArray(h[key]))
594
+ continue;
595
+ const kept = h[key].filter((e) => !isOurs(e));
596
+ if (kept.length)
597
+ h[key] = kept;
598
+ else
599
+ delete h[key];
600
+ }
601
+ if (Object.keys(h).length === 0)
602
+ delete settings.hooks;
603
+ }
604
+ return `${JSON.stringify(settings, null, 2)}
605
+ `;
606
+ }
607
+ function mergeCodexConfig(current, scriptPath) {
608
+ const line = `notify = [${JSON.stringify(scriptPath)}, "codex"]`;
609
+ const lines = current.split("\n");
610
+ const idx = lines.findIndex((l) => /^\s*notify\s*=/.test(l));
611
+ if (idx !== -1)
612
+ lines[idx] = line;
613
+ else
614
+ lines.unshift(line);
615
+ const out = lines.join("\n");
616
+ return out.endsWith("\n") ? out : `${out}
617
+ `;
618
+ }
619
+ function stripCodexConfig(current) {
620
+ const lines = current.split("\n").filter((l) => !(/^\s*notify\s*=/.test(l) && l.includes(HOOK_MARK)));
621
+ return lines.join("\n");
622
+ }
623
+
529
624
  // src/exec.ts
530
625
  var DEFAULT_TIMEOUT_MS = 8e3;
531
626
  var RpcFailure = class extends Error {
532
- constructor(code, message, path9) {
627
+ constructor(code, message, path10) {
533
628
  super(message);
534
629
  this.code = code;
535
- this.path = path9;
630
+ this.path = path10;
536
631
  }
537
632
  code;
538
633
  path;
@@ -836,6 +931,102 @@ async function mkdir(params) {
836
931
  return { stdout: r.stdout };
837
932
  }
838
933
 
934
+ // src/rpc/hooks.ts
935
+ import { chmod, mkdir as mkdir2, readFile, rename, rm, stat, writeFile } from "fs/promises";
936
+ import os3 from "os";
937
+ import path4 from "path";
938
+ var CLAUDE_SETTINGS_REL = ".claude/settings.json";
939
+ var CODEX_DIR_REL = ".codex";
940
+ var CODEX_CONFIG_REL = ".codex/config.toml";
941
+ var isEnoent2 = (err) => err?.code === "ENOENT";
942
+ async function readOrEmpty(file) {
943
+ try {
944
+ return await readFile(file, "utf8");
945
+ } catch (err) {
946
+ if (isEnoent2(err)) return "";
947
+ throw err;
948
+ }
949
+ }
950
+ async function isDir(dir) {
951
+ try {
952
+ return (await stat(dir)).isDirectory();
953
+ } catch {
954
+ return false;
955
+ }
956
+ }
957
+ async function writeAtomic(file, body, mode) {
958
+ const tmp = `${file}.termhub-new`;
959
+ await writeFile(tmp, body, { encoding: "utf8", mode });
960
+ await chmod(tmp, mode);
961
+ await rename(tmp, file);
962
+ }
963
+ function fsFailure(err, rel) {
964
+ const code = err?.code;
965
+ if (code === "EACCES" || code === "EPERM") return new RpcFailure("eperm", `sem permiss\xE3o em ~/${rel}`, rel);
966
+ return new RpcFailure("failed", `n\xE3o foi poss\xEDvel escrever ~/${rel}: ${err instanceof Error ? err.message : String(err)}`, rel);
967
+ }
968
+ async function install(params, home = os3.homedir()) {
969
+ const scriptPath = path4.join(home, HOOK_SCRIPT_REL);
970
+ const claudeFile = path4.join(home, CLAUDE_SETTINGS_REL);
971
+ const codexFile = path4.join(home, CODEX_CONFIG_REL);
972
+ const claudeSettings = await readOrEmpty(claudeFile);
973
+ let mergedClaude;
974
+ try {
975
+ mergedClaude = mergeClaudeSettings(claudeSettings, scriptPath);
976
+ } catch (err) {
977
+ throw new RpcFailure("failed", err instanceof SyntaxError ? "~/.claude/settings.json n\xE3o \xE9 JSON v\xE1lido" : err instanceof Error ? err.message : String(err), CLAUDE_SETTINGS_REL);
978
+ }
979
+ const hasCodex = await isDir(path4.join(home, CODEX_DIR_REL));
980
+ const mergedCodex = hasCodex ? mergeCodexConfig(await readOrEmpty(codexFile), scriptPath) : null;
981
+ let current = HOOK_SCRIPT_REL;
982
+ try {
983
+ await mkdir2(path4.dirname(scriptPath), { recursive: true });
984
+ await mkdir2(path4.dirname(claudeFile), { recursive: true });
985
+ current = HOOK_ENV_REL;
986
+ await writeAtomic(path4.join(home, HOOK_ENV_REL), hookEnvFile(params.hooks_url, params.token), 384);
987
+ current = HOOK_SCRIPT_REL;
988
+ await writeAtomic(scriptPath, HOOK_SCRIPT, 493);
989
+ current = CLAUDE_SETTINGS_REL;
990
+ await writeAtomic(claudeFile, mergedClaude, 420);
991
+ if (mergedCodex !== null) {
992
+ current = CODEX_CONFIG_REL;
993
+ await writeAtomic(codexFile, mergedCodex, 420);
994
+ }
995
+ } catch (err) {
996
+ throw fsFailure(err, current);
997
+ }
998
+ return { home, claude: "installed", codex: mergedCodex !== null ? "installed" : "skipped" };
999
+ }
1000
+ async function uninstall(_params, home = os3.homedir()) {
1001
+ const claudeFile = path4.join(home, CLAUDE_SETTINGS_REL);
1002
+ const codexFile = path4.join(home, CODEX_CONFIG_REL);
1003
+ const claudeSettings = await readOrEmpty(claudeFile);
1004
+ let stripped = null;
1005
+ try {
1006
+ stripped = claudeSettings.trim() ? stripClaudeSettings(claudeSettings) : null;
1007
+ } catch {
1008
+ stripped = null;
1009
+ }
1010
+ const codexConfig = await isDir(path4.join(home, CODEX_DIR_REL)) ? await readOrEmpty(codexFile) : "";
1011
+ let current = HOOK_SCRIPT_REL;
1012
+ try {
1013
+ await rm(path4.join(home, HOOK_SCRIPT_REL), { force: true });
1014
+ current = HOOK_ENV_REL;
1015
+ await rm(path4.join(home, HOOK_ENV_REL), { force: true });
1016
+ if (stripped !== null && stripped !== claudeSettings) {
1017
+ current = CLAUDE_SETTINGS_REL;
1018
+ await writeAtomic(claudeFile, stripped, 420);
1019
+ }
1020
+ if (codexConfig.includes(HOOK_MARK)) {
1021
+ current = CODEX_CONFIG_REL;
1022
+ await writeAtomic(codexFile, stripCodexConfig(codexConfig), 420);
1023
+ }
1024
+ } catch (err) {
1025
+ throw fsFailure(err, current);
1026
+ }
1027
+ return { removed: true };
1028
+ }
1029
+
839
1030
  // src/rpc/hw.ts
840
1031
  async function probe(_params) {
841
1032
  const r = await sh(HARDWARE_SCRIPT, { timeoutMs: 14e3 });
@@ -852,9 +1043,9 @@ async function pasteFile(params) {
852
1043
  if (r.timedOut) throw new RpcFailure("timeout", "file.paste timed out");
853
1044
  if (r.code !== 0) throw new RpcFailure("internal", `file.paste exited with code ${r.code}`);
854
1045
  const lines = r.stdout.split("\n").map((l) => l.trim()).filter(Boolean);
855
- const path9 = lines[lines.length - 1] ?? "";
856
- if (!path9.startsWith("/")) throw new RpcFailure("internal", "unexpected paste output");
857
- return { path: path9 };
1046
+ const path10 = lines[lines.length - 1] ?? "";
1047
+ if (!path10.startsWith("/")) throw new RpcFailure("internal", "unexpected paste output");
1048
+ return { path: path10 };
858
1049
  }
859
1050
 
860
1051
  // src/rpc/tmux.ts
@@ -891,8 +1082,8 @@ async function detect(_params) {
891
1082
  const r = await sh(DETECT_SCRIPT);
892
1083
  if (r.timedOut) throw new RpcFailure("timeout", "tools.detect timed out");
893
1084
  if (r.code !== 0) throw new RpcFailure("internal", `tools.detect exited with code ${r.code}`);
894
- const { os: os8, capabilities } = parseDetect(r.stdout);
895
- return { os: os8, tools: capabilities };
1085
+ const { os: os9, capabilities } = parseDetect(r.stdout);
1086
+ return { os: os9, tools: capabilities };
896
1087
  }
897
1088
 
898
1089
  // src/rpc/index.ts
@@ -905,13 +1096,15 @@ var handlers = {
905
1096
  "fs.list": list,
906
1097
  "fs.mkdir": mkdir,
907
1098
  "ai.credential": credential,
908
- "file.paste": pasteFile
1099
+ "file.paste": pasteFile,
1100
+ "hooks.install": install,
1101
+ "hooks.uninstall": uninstall
909
1102
  };
910
1103
 
911
1104
  // src/service/launchd.ts
912
1105
  import fs4 from "fs";
913
- import os3 from "os";
914
- import path4 from "path";
1106
+ import os4 from "os";
1107
+ import path5 from "path";
915
1108
  var LABEL = "dev.termhub.agent";
916
1109
  function renderPlist({ label, node, script, logPath }) {
917
1110
  const escape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -944,22 +1137,22 @@ function renderPlist({ label, node, script, logPath }) {
944
1137
  <string>${escape(agentEnv().PATH ?? "")}</string>
945
1138
  </dict>
946
1139
  <key>WorkingDirectory</key>
947
- <string>${escape(os3.homedir())}</string>
1140
+ <string>${escape(os4.homedir())}</string>
948
1141
  </dict>
949
1142
  </plist>
950
1143
  `;
951
1144
  }
952
1145
  function plistPath() {
953
- return path4.join(os3.homedir(), "Library", "LaunchAgents", `${LABEL}.plist`);
1146
+ return path5.join(os4.homedir(), "Library", "LaunchAgents", `${LABEL}.plist`);
954
1147
  }
955
1148
  function gui() {
956
1149
  return `gui/${process.getuid ? process.getuid() : 0}`;
957
1150
  }
958
- async function install(opts, deps = {}) {
1151
+ async function install2(opts, deps = {}) {
959
1152
  const runFn = deps.run ?? run;
960
1153
  const file = plistPath();
961
1154
  const plist = renderPlist({ label: LABEL, node: opts.node, script: opts.script, logPath: opts.logPath });
962
- fs4.mkdirSync(path4.dirname(file), { recursive: true });
1155
+ fs4.mkdirSync(path5.dirname(file), { recursive: true });
963
1156
  fs4.writeFileSync(file, plist, "utf8");
964
1157
  await runFn("launchctl", ["bootout", gui(), file]);
965
1158
  const result = await runFn("launchctl", ["bootstrap", gui(), file]);
@@ -967,7 +1160,7 @@ async function install(opts, deps = {}) {
967
1160
  throw new Error(`launchctl bootstrap failed (code ${result.code}): ${result.stderr.trim() || result.stdout.trim()}`);
968
1161
  }
969
1162
  }
970
- async function uninstall(deps = {}) {
1163
+ async function uninstall2(deps = {}) {
971
1164
  const runFn = deps.run ?? run;
972
1165
  const file = plistPath();
973
1166
  await runFn("launchctl", ["bootout", gui(), file]);
@@ -993,7 +1186,7 @@ async function status(deps = {}) {
993
1186
  }
994
1187
 
995
1188
  // src/version.ts
996
- var AGENT_VERSION = "0.1.2";
1189
+ var AGENT_VERSION = "0.1.4";
997
1190
 
998
1191
  // src/run.ts
999
1192
  function detectOs(platform = process.platform) {
@@ -1013,7 +1206,7 @@ async function buildHello(osName) {
1013
1206
  agent_version: AGENT_VERSION,
1014
1207
  os: osName,
1015
1208
  arch: process.arch,
1016
- hostname: os4.hostname(),
1209
+ hostname: os5.hostname(),
1017
1210
  tmux: tools.includes("tmux"),
1018
1211
  tools
1019
1212
  };
@@ -1033,7 +1226,7 @@ async function checkServerConnection(config, timeoutMs = 5e3) {
1033
1226
  {
1034
1227
  url: config.url,
1035
1228
  token: config.token,
1036
- hello: { agent_version: AGENT_VERSION, os: osName, arch: process.arch, hostname: os4.hostname(), tmux: false, tools: [], probe: true },
1229
+ hello: { agent_version: AGENT_VERSION, os: osName, arch: process.arch, hostname: os5.hostname(), tmux: false, tools: [], probe: true },
1037
1230
  onServerMessage: () => {
1038
1231
  },
1039
1232
  onStream: () => {
@@ -1184,16 +1377,16 @@ function disconnectCommand() {
1184
1377
 
1185
1378
  // src/doctor.ts
1186
1379
  import fs5 from "fs";
1187
- import os5 from "os";
1188
- import path5 from "path";
1380
+ import os6 from "os";
1381
+ import path6 from "path";
1189
1382
  var SERVER_CHECK_TIMEOUT_MS = 5e3;
1190
1383
  function defaultDoctorPaths() {
1191
- const home = os5.homedir();
1192
- const paths = [home, path5.join(home, "Documents"), path5.join(home, "Desktop")];
1384
+ const home = os6.homedir();
1385
+ const paths = [home, path6.join(home, "Documents"), path6.join(home, "Desktop")];
1193
1386
  if (process.platform === "darwin") {
1194
1387
  try {
1195
1388
  for (const entry of fs5.readdirSync("/Volumes", { withFileTypes: true })) {
1196
- if (entry.isDirectory()) paths.push(path5.join("/Volumes", entry.name));
1389
+ if (entry.isDirectory()) paths.push(path6.join("/Volumes", entry.name));
1197
1390
  }
1198
1391
  } catch {
1199
1392
  }
@@ -1285,21 +1478,21 @@ async function runCommand(log2) {
1285
1478
  }
1286
1479
 
1287
1480
  // src/commands/service.ts
1288
- import os7 from "os";
1481
+ import os8 from "os";
1289
1482
 
1290
1483
  // src/service/index.ts
1291
- import path8 from "path";
1484
+ import path9 from "path";
1292
1485
 
1293
1486
  // src/paths.ts
1294
1487
  import { realpathSync } from "fs";
1295
- import path6 from "path";
1488
+ import path7 from "path";
1296
1489
  import { pathToFileURL } from "url";
1297
1490
  function resolveScriptPath(argv1) {
1298
1491
  if (!argv1) return "";
1299
1492
  try {
1300
1493
  return realpathSync(argv1);
1301
1494
  } catch {
1302
- return path6.resolve(argv1);
1495
+ return path7.resolve(argv1);
1303
1496
  }
1304
1497
  }
1305
1498
  function isMainModule(importMetaUrl, argv1) {
@@ -1309,8 +1502,8 @@ function isMainModule(importMetaUrl, argv1) {
1309
1502
 
1310
1503
  // src/service/systemd.ts
1311
1504
  import fs6 from "fs";
1312
- import os6 from "os";
1313
- import path7 from "path";
1505
+ import os7 from "os";
1506
+ import path8 from "path";
1314
1507
  var UNIT_NAME = "termhub-agent";
1315
1508
  function renderUnit({ node, script, logPath }) {
1316
1509
  const lines = [
@@ -1332,13 +1525,13 @@ function renderUnit({ node, script, logPath }) {
1332
1525
  return lines.join("\n");
1333
1526
  }
1334
1527
  function unitPath() {
1335
- return path7.join(os6.homedir(), ".config", "systemd", "user", `${UNIT_NAME}.service`);
1528
+ return path8.join(os7.homedir(), ".config", "systemd", "user", `${UNIT_NAME}.service`);
1336
1529
  }
1337
- async function install2(opts, deps = {}) {
1530
+ async function install3(opts, deps = {}) {
1338
1531
  const runFn = deps.run ?? run;
1339
1532
  const file = unitPath();
1340
1533
  const unit = renderUnit({ node: opts.node, script: opts.script, logPath: opts.logPath });
1341
- fs6.mkdirSync(path7.dirname(file), { recursive: true });
1534
+ fs6.mkdirSync(path8.dirname(file), { recursive: true });
1342
1535
  fs6.writeFileSync(file, unit, "utf8");
1343
1536
  const reload = await runFn("systemctl", ["--user", "daemon-reload"]);
1344
1537
  if (reload.code !== 0) throw new Error(`systemctl daemon-reload failed (code ${reload.code}): ${reload.stderr.trim()}`);
@@ -1346,7 +1539,7 @@ async function install2(opts, deps = {}) {
1346
1539
  if (enable.code !== 0) throw new Error(`systemctl enable --now failed (code ${enable.code}): ${enable.stderr.trim()}`);
1347
1540
  console.log("Para o agente continuar ap\xF3s o logout: loginctl enable-linger $USER");
1348
1541
  }
1349
- async function uninstall2(deps = {}) {
1542
+ async function uninstall3(deps = {}) {
1350
1543
  const runFn = deps.run ?? run;
1351
1544
  await runFn("systemctl", ["--user", "disable", "--now", UNIT_NAME]);
1352
1545
  try {
@@ -1367,24 +1560,24 @@ function serviceFileOptions() {
1367
1560
  return {
1368
1561
  node: process.execPath,
1369
1562
  script: resolveScriptPath(process.argv[1]),
1370
- logPath: path8.join(agentHome(), "agent.log")
1563
+ logPath: path9.join(agentHome(), "agent.log")
1371
1564
  };
1372
1565
  }
1373
1566
  function assertSupported(platform) {
1374
1567
  if (platform !== "darwin" && platform !== "linux") throw new Error("Sistema n\xE3o suportado");
1375
1568
  }
1376
- async function install3() {
1569
+ async function install4() {
1377
1570
  const platform = process.platform;
1378
1571
  assertSupported(platform);
1379
1572
  const opts = serviceFileOptions();
1380
- if (platform === "darwin") await install(opts);
1381
- else await install2(opts);
1573
+ if (platform === "darwin") await install2(opts);
1574
+ else await install3(opts);
1382
1575
  }
1383
- async function uninstall3() {
1576
+ async function uninstall4() {
1384
1577
  const platform = process.platform;
1385
1578
  assertSupported(platform);
1386
- if (platform === "darwin") await uninstall();
1387
- else await uninstall2();
1579
+ if (platform === "darwin") await uninstall2();
1580
+ else await uninstall3();
1388
1581
  }
1389
1582
  async function status3() {
1390
1583
  const platform = process.platform;
@@ -1397,16 +1590,16 @@ async function status3() {
1397
1590
  async function serviceCommand(sub) {
1398
1591
  switch (sub) {
1399
1592
  case "install": {
1400
- const homeCheck = checkPathAccess(os7.homedir());
1593
+ const homeCheck = checkPathAccess(os8.homedir());
1401
1594
  if (!homeCheck.ok && homeCheck.error === "eperm" && process.platform === "darwin") {
1402
1595
  console.error(fullDiskAccessNote());
1403
1596
  }
1404
- await install3();
1597
+ await install4();
1405
1598
  console.log("Servi\xE7o instalado.");
1406
1599
  return;
1407
1600
  }
1408
1601
  case "uninstall":
1409
- await uninstall3();
1602
+ await uninstall4();
1410
1603
  console.log("Servi\xE7o removido.");
1411
1604
  return;
1412
1605
  case "status": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@termhub/agent",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Agente do termhub: conecta esta máquina ao servidor por WebSocket de saída (sem SSH) e expõe os terminais tmux no navegador.",
5
5
  "license": "MIT",
6
6
  "private": false,