@termhub/agent 0.2.1 → 0.2.2

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 +7 -0
  2. package/dist/cli.js +41 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -35,6 +35,13 @@ starts the agent at login/boot and restarts it on failure. `termhub-agent servic
35
35
  `termhub-agent service uninstall` manage it afterwards. On Linux, run
36
36
  `loginctl enable-linger $USER` once so the service keeps running after you log out.
37
37
 
38
+ The service is deliberately kept apart from the tmux server that holds your terminals: on Linux
39
+ the unit sets `KillMode=process`, so restarting or updating the agent signals only the agent
40
+ itself and leaves tmux — and whatever is running inside it — alone, and the agent reattaches to
41
+ the existing sessions when it comes back (macOS needs nothing: launchd does not follow tmux once
42
+ it daemonises). A unit installed by an older agent is rewritten to the current template the first
43
+ time the new agent starts.
44
+
38
45
  ## Other commands
39
46
 
40
47
  - `termhub-agent status` — shows the paired server, machine name and whether the agent can reach
package/dist/cli.js CHANGED
@@ -1267,15 +1267,15 @@ function renderPlist({ label, node, script, logPath }) {
1267
1267
  </plist>
1268
1268
  `;
1269
1269
  }
1270
- function plistPath() {
1271
- return path6.join(os4.homedir(), "Library", "LaunchAgents", `${LABEL}.plist`);
1270
+ function plistPath(home = os4.homedir()) {
1271
+ return path6.join(home, "Library", "LaunchAgents", `${LABEL}.plist`);
1272
1272
  }
1273
1273
  function gui() {
1274
1274
  return `gui/${process.getuid ? process.getuid() : 0}`;
1275
1275
  }
1276
1276
  async function install2(opts, deps = {}) {
1277
1277
  const runFn = deps.run ?? run;
1278
- const file = plistPath();
1278
+ const file = plistPath(deps.home);
1279
1279
  const plist = renderPlist({ label: LABEL, node: opts.node, script: opts.script, logPath: opts.logPath });
1280
1280
  fs4.mkdirSync(path6.dirname(file), { recursive: true });
1281
1281
  fs4.writeFileSync(file, plist, "utf8");
@@ -1287,7 +1287,7 @@ async function install2(opts, deps = {}) {
1287
1287
  }
1288
1288
  async function uninstall2(deps = {}) {
1289
1289
  const runFn = deps.run ?? run;
1290
- const file = plistPath();
1290
+ const file = plistPath(deps.home);
1291
1291
  await runFn("launchctl", ["bootout", gui(), file]);
1292
1292
  try {
1293
1293
  fs4.unlinkSync(file);
@@ -1315,7 +1315,7 @@ import fs5 from "fs";
1315
1315
  import os5 from "os";
1316
1316
  import path7 from "path";
1317
1317
  var UNIT_NAME = "termhub-agent";
1318
- function renderUnit({ node, script, logPath }) {
1318
+ function renderUnit({ node, script, logPath, pathEnv }) {
1319
1319
  const lines = [
1320
1320
  "[Unit]",
1321
1321
  "Description=termhub agent",
@@ -1323,10 +1323,11 @@ function renderUnit({ node, script, logPath }) {
1323
1323
  "",
1324
1324
  "[Service]",
1325
1325
  `ExecStart=${node} ${script} run`,
1326
+ "KillMode=process",
1326
1327
  "Restart=on-failure",
1327
1328
  "RestartSec=2",
1328
1329
  "RestartPreventExitStatus=78",
1329
- `Environment=PATH=${agentEnv().PATH ?? ""}`
1330
+ `Environment=PATH=${pathEnv ?? agentEnv().PATH ?? ""}`
1330
1331
  ];
1331
1332
  if (logPath) {
1332
1333
  lines.push(`StandardOutput=append:${logPath}`, `StandardError=append:${logPath}`);
@@ -1334,12 +1335,12 @@ function renderUnit({ node, script, logPath }) {
1334
1335
  lines.push("", "[Install]", "WantedBy=default.target", "");
1335
1336
  return lines.join("\n");
1336
1337
  }
1337
- function unitPath() {
1338
- return path7.join(os5.homedir(), ".config", "systemd", "user", `${UNIT_NAME}.service`);
1338
+ function unitPath(home = os5.homedir()) {
1339
+ return path7.join(home, ".config", "systemd", "user", `${UNIT_NAME}.service`);
1339
1340
  }
1340
1341
  async function install3(opts, deps = {}) {
1341
1342
  const runFn = deps.run ?? run;
1342
- const file = unitPath();
1343
+ const file = unitPath(deps.home);
1343
1344
  const unit = renderUnit({ node: opts.node, script: opts.script, logPath: opts.logPath });
1344
1345
  fs5.mkdirSync(path7.dirname(file), { recursive: true });
1345
1346
  fs5.writeFileSync(file, unit, "utf8");
@@ -1349,11 +1350,31 @@ async function install3(opts, deps = {}) {
1349
1350
  if (enable.code !== 0) throw new Error(`systemctl enable --now failed (code ${enable.code}): ${enable.stderr.trim()}`);
1350
1351
  console.log("Para o agente continuar ap\xF3s o logout: loginctl enable-linger $USER");
1351
1352
  }
1353
+ function unitPathEnv(unit) {
1354
+ return /^Environment=PATH=(.*)$/m.exec(unit)?.[1];
1355
+ }
1356
+ async function refreshUnit(opts, deps = {}) {
1357
+ const file = unitPath(deps.home);
1358
+ let current;
1359
+ try {
1360
+ current = fs5.readFileSync(file, "utf8");
1361
+ } catch (err) {
1362
+ if (err.code === "ENOENT") return false;
1363
+ throw err;
1364
+ }
1365
+ const unit = renderUnit({ node: opts.node, script: opts.script, logPath: opts.logPath, pathEnv: unitPathEnv(current) });
1366
+ if (unit === current) return false;
1367
+ fs5.writeFileSync(file, unit, "utf8");
1368
+ const runFn = deps.run ?? run;
1369
+ const reload = await runFn("systemctl", ["--user", "daemon-reload"]);
1370
+ if (reload.code !== 0) throw new Error(`systemctl daemon-reload failed (code ${reload.code}): ${reload.stderr.trim()}`);
1371
+ return true;
1372
+ }
1352
1373
  async function uninstall3(deps = {}) {
1353
1374
  const runFn = deps.run ?? run;
1354
1375
  await runFn("systemctl", ["--user", "disable", "--now", UNIT_NAME]);
1355
1376
  try {
1356
- fs5.unlinkSync(unitPath());
1377
+ fs5.unlinkSync(unitPath(deps.home));
1357
1378
  } catch (err) {
1358
1379
  if (err.code !== "ENOENT") throw err;
1359
1380
  }
@@ -1383,6 +1404,10 @@ async function install4() {
1383
1404
  if (platform === "darwin") await install2(opts);
1384
1405
  else await install3(opts);
1385
1406
  }
1407
+ async function refresh() {
1408
+ if (process.platform !== "linux") return false;
1409
+ return refreshUnit(serviceFileOptions());
1410
+ }
1386
1411
  async function uninstall4() {
1387
1412
  const platform = process.platform;
1388
1413
  assertSupported(platform);
@@ -1397,7 +1422,7 @@ async function status3() {
1397
1422
  }
1398
1423
 
1399
1424
  // src/version.ts
1400
- var AGENT_VERSION = "0.2.1";
1425
+ var AGENT_VERSION = "0.2.2";
1401
1426
 
1402
1427
  // src/rpc/update.ts
1403
1428
  var PACKAGE = "@termhub/agent";
@@ -1766,6 +1791,11 @@ async function runCommand(log2) {
1766
1791
  if (!config) {
1767
1792
  return exitWithoutRestart("Nenhuma configura\xE7\xE3o. Rode: termhub-agent connect --url <url>");
1768
1793
  }
1794
+ try {
1795
+ if (await refresh()) log2("service definition refreshed");
1796
+ } catch (err) {
1797
+ log2("service definition could not be refreshed", { error: err instanceof Error ? err.message : String(err) });
1798
+ }
1769
1799
  await runForegroundUntilSignal(config, log2);
1770
1800
  }
1771
1801
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@termhub/agent",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
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,