@termhub/agent 0.2.0 → 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.
- package/README.md +7 -0
- package/dist/cli.js +247 -149
- 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
|
@@ -77,7 +77,9 @@ var RPC = {
|
|
|
77
77
|
token: z.string().min(1).max(256).regex(/^[A-Za-z0-9_-]+$/),
|
|
78
78
|
claude_dirs: z.array(machinePath).max(16).optional()
|
|
79
79
|
}), z.object({ home: z.string(), claude: z.enum(["installed", "skipped"]), codex: z.enum(["installed", "skipped"]), claude_dirs: z.array(z.string()).optional() }), 15e3),
|
|
80
|
-
"hooks.uninstall": def(z.object({ claude_dirs: z.array(machinePath).max(16).optional() }), z.object({ removed: z.boolean() }), 15e3)
|
|
80
|
+
"hooks.uninstall": def(z.object({ claude_dirs: z.array(machinePath).max(16).optional() }), z.object({ removed: z.boolean() }), 15e3),
|
|
81
|
+
/** Installs `version` of @termhub/agent with npm; when the agent runs as a service it then exits so the service relaunches the new code (since agent 0.2.1). */
|
|
82
|
+
"agent.update": def(z.object({ version: z.string().regex(/^\d+\.\d+\.\d+$/) }), z.object({ installed_version: z.string(), restart: z.enum(["service", "manual"]) }), 18e4)
|
|
81
83
|
};
|
|
82
84
|
var RPC_METHODS = Object.keys(RPC);
|
|
83
85
|
var rpcMethod = z.enum(RPC_METHODS);
|
|
@@ -372,7 +374,7 @@ function deleteConfig() {
|
|
|
372
374
|
}
|
|
373
375
|
|
|
374
376
|
// src/run.ts
|
|
375
|
-
import
|
|
377
|
+
import os6 from "os";
|
|
376
378
|
|
|
377
379
|
// src/exec.ts
|
|
378
380
|
import { execFile } from "child_process";
|
|
@@ -666,10 +668,10 @@ function stripCodexConfig(current) {
|
|
|
666
668
|
// src/exec.ts
|
|
667
669
|
var DEFAULT_TIMEOUT_MS = 8e3;
|
|
668
670
|
var RpcFailure = class extends Error {
|
|
669
|
-
constructor(code, message,
|
|
671
|
+
constructor(code, message, path11) {
|
|
670
672
|
super(message);
|
|
671
673
|
this.code = code;
|
|
672
|
-
this.path =
|
|
674
|
+
this.path = path11;
|
|
673
675
|
}
|
|
674
676
|
code;
|
|
675
677
|
path;
|
|
@@ -1119,9 +1121,9 @@ async function pasteFile(params) {
|
|
|
1119
1121
|
if (r.timedOut) throw new RpcFailure("timeout", "file.paste timed out");
|
|
1120
1122
|
if (r.code !== 0) throw new RpcFailure("internal", `file.paste exited with code ${r.code}`);
|
|
1121
1123
|
const lines = r.stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1122
|
-
const
|
|
1123
|
-
if (!
|
|
1124
|
-
return { path:
|
|
1124
|
+
const path11 = lines[lines.length - 1] ?? "";
|
|
1125
|
+
if (!path11.startsWith("/")) throw new RpcFailure("internal", "unexpected paste output");
|
|
1126
|
+
return { path: path11 };
|
|
1125
1127
|
}
|
|
1126
1128
|
|
|
1127
1129
|
// src/rpc/tmux.ts
|
|
@@ -1199,28 +1201,35 @@ async function detect(_params) {
|
|
|
1199
1201
|
return { os: os9, tools: capabilities };
|
|
1200
1202
|
}
|
|
1201
1203
|
|
|
1202
|
-
// src/rpc/
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
"
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
}
|
|
1204
|
+
// src/rpc/update.ts
|
|
1205
|
+
import { existsSync, realpathSync as realpathSync2 } from "fs";
|
|
1206
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1207
|
+
import path9 from "path";
|
|
1208
|
+
|
|
1209
|
+
// src/paths.ts
|
|
1210
|
+
import { realpathSync } from "fs";
|
|
1211
|
+
import path5 from "path";
|
|
1212
|
+
import { pathToFileURL } from "url";
|
|
1213
|
+
function resolveScriptPath(argv1) {
|
|
1214
|
+
if (!argv1) return "";
|
|
1215
|
+
try {
|
|
1216
|
+
return realpathSync(argv1);
|
|
1217
|
+
} catch {
|
|
1218
|
+
return path5.resolve(argv1);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
function isMainModule(importMetaUrl, argv1) {
|
|
1222
|
+
if (!argv1) return false;
|
|
1223
|
+
return importMetaUrl === pathToFileURL(resolveScriptPath(argv1)).href;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// src/service/index.ts
|
|
1227
|
+
import path8 from "path";
|
|
1219
1228
|
|
|
1220
1229
|
// src/service/launchd.ts
|
|
1221
1230
|
import fs4 from "fs";
|
|
1222
1231
|
import os4 from "os";
|
|
1223
|
-
import
|
|
1232
|
+
import path6 from "path";
|
|
1224
1233
|
var LABEL = "dev.termhub.agent";
|
|
1225
1234
|
function renderPlist({ label, node, script, logPath }) {
|
|
1226
1235
|
const escape = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -1258,17 +1267,17 @@ function renderPlist({ label, node, script, logPath }) {
|
|
|
1258
1267
|
</plist>
|
|
1259
1268
|
`;
|
|
1260
1269
|
}
|
|
1261
|
-
function plistPath() {
|
|
1262
|
-
return
|
|
1270
|
+
function plistPath(home = os4.homedir()) {
|
|
1271
|
+
return path6.join(home, "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
1263
1272
|
}
|
|
1264
1273
|
function gui() {
|
|
1265
1274
|
return `gui/${process.getuid ? process.getuid() : 0}`;
|
|
1266
1275
|
}
|
|
1267
1276
|
async function install2(opts, deps = {}) {
|
|
1268
1277
|
const runFn = deps.run ?? run;
|
|
1269
|
-
const file = plistPath();
|
|
1278
|
+
const file = plistPath(deps.home);
|
|
1270
1279
|
const plist = renderPlist({ label: LABEL, node: opts.node, script: opts.script, logPath: opts.logPath });
|
|
1271
|
-
fs4.mkdirSync(
|
|
1280
|
+
fs4.mkdirSync(path6.dirname(file), { recursive: true });
|
|
1272
1281
|
fs4.writeFileSync(file, plist, "utf8");
|
|
1273
1282
|
await runFn("launchctl", ["bootout", gui(), file]);
|
|
1274
1283
|
const result = await runFn("launchctl", ["bootstrap", gui(), file]);
|
|
@@ -1278,7 +1287,7 @@ async function install2(opts, deps = {}) {
|
|
|
1278
1287
|
}
|
|
1279
1288
|
async function uninstall2(deps = {}) {
|
|
1280
1289
|
const runFn = deps.run ?? run;
|
|
1281
|
-
const file = plistPath();
|
|
1290
|
+
const file = plistPath(deps.home);
|
|
1282
1291
|
await runFn("launchctl", ["bootout", gui(), file]);
|
|
1283
1292
|
try {
|
|
1284
1293
|
fs4.unlinkSync(file);
|
|
@@ -1301,8 +1310,200 @@ async function status(deps = {}) {
|
|
|
1301
1310
|
return result.code === 0;
|
|
1302
1311
|
}
|
|
1303
1312
|
|
|
1313
|
+
// src/service/systemd.ts
|
|
1314
|
+
import fs5 from "fs";
|
|
1315
|
+
import os5 from "os";
|
|
1316
|
+
import path7 from "path";
|
|
1317
|
+
var UNIT_NAME = "termhub-agent";
|
|
1318
|
+
function renderUnit({ node, script, logPath, pathEnv }) {
|
|
1319
|
+
const lines = [
|
|
1320
|
+
"[Unit]",
|
|
1321
|
+
"Description=termhub agent",
|
|
1322
|
+
"After=network-online.target",
|
|
1323
|
+
"",
|
|
1324
|
+
"[Service]",
|
|
1325
|
+
`ExecStart=${node} ${script} run`,
|
|
1326
|
+
"KillMode=process",
|
|
1327
|
+
"Restart=on-failure",
|
|
1328
|
+
"RestartSec=2",
|
|
1329
|
+
"RestartPreventExitStatus=78",
|
|
1330
|
+
`Environment=PATH=${pathEnv ?? agentEnv().PATH ?? ""}`
|
|
1331
|
+
];
|
|
1332
|
+
if (logPath) {
|
|
1333
|
+
lines.push(`StandardOutput=append:${logPath}`, `StandardError=append:${logPath}`);
|
|
1334
|
+
}
|
|
1335
|
+
lines.push("", "[Install]", "WantedBy=default.target", "");
|
|
1336
|
+
return lines.join("\n");
|
|
1337
|
+
}
|
|
1338
|
+
function unitPath(home = os5.homedir()) {
|
|
1339
|
+
return path7.join(home, ".config", "systemd", "user", `${UNIT_NAME}.service`);
|
|
1340
|
+
}
|
|
1341
|
+
async function install3(opts, deps = {}) {
|
|
1342
|
+
const runFn = deps.run ?? run;
|
|
1343
|
+
const file = unitPath(deps.home);
|
|
1344
|
+
const unit = renderUnit({ node: opts.node, script: opts.script, logPath: opts.logPath });
|
|
1345
|
+
fs5.mkdirSync(path7.dirname(file), { recursive: true });
|
|
1346
|
+
fs5.writeFileSync(file, unit, "utf8");
|
|
1347
|
+
const reload = await runFn("systemctl", ["--user", "daemon-reload"]);
|
|
1348
|
+
if (reload.code !== 0) throw new Error(`systemctl daemon-reload failed (code ${reload.code}): ${reload.stderr.trim()}`);
|
|
1349
|
+
const enable = await runFn("systemctl", ["--user", "enable", "--now", UNIT_NAME]);
|
|
1350
|
+
if (enable.code !== 0) throw new Error(`systemctl enable --now failed (code ${enable.code}): ${enable.stderr.trim()}`);
|
|
1351
|
+
console.log("Para o agente continuar ap\xF3s o logout: loginctl enable-linger $USER");
|
|
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
|
+
}
|
|
1373
|
+
async function uninstall3(deps = {}) {
|
|
1374
|
+
const runFn = deps.run ?? run;
|
|
1375
|
+
await runFn("systemctl", ["--user", "disable", "--now", UNIT_NAME]);
|
|
1376
|
+
try {
|
|
1377
|
+
fs5.unlinkSync(unitPath(deps.home));
|
|
1378
|
+
} catch (err) {
|
|
1379
|
+
if (err.code !== "ENOENT") throw err;
|
|
1380
|
+
}
|
|
1381
|
+
await runFn("systemctl", ["--user", "daemon-reload"]);
|
|
1382
|
+
}
|
|
1383
|
+
async function status2(deps = {}) {
|
|
1384
|
+
const runFn = deps.run ?? run;
|
|
1385
|
+
const result = await runFn("systemctl", ["--user", "is-active", UNIT_NAME]);
|
|
1386
|
+
return result.code === 0;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// src/service/index.ts
|
|
1390
|
+
function serviceFileOptions() {
|
|
1391
|
+
return {
|
|
1392
|
+
node: process.execPath,
|
|
1393
|
+
script: resolveScriptPath(process.argv[1]),
|
|
1394
|
+
logPath: path8.join(agentHome(), "agent.log")
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
function assertSupported(platform) {
|
|
1398
|
+
if (platform !== "darwin" && platform !== "linux") throw new Error("Sistema n\xE3o suportado");
|
|
1399
|
+
}
|
|
1400
|
+
async function install4() {
|
|
1401
|
+
const platform = process.platform;
|
|
1402
|
+
assertSupported(platform);
|
|
1403
|
+
const opts = serviceFileOptions();
|
|
1404
|
+
if (platform === "darwin") await install2(opts);
|
|
1405
|
+
else await install3(opts);
|
|
1406
|
+
}
|
|
1407
|
+
async function refresh() {
|
|
1408
|
+
if (process.platform !== "linux") return false;
|
|
1409
|
+
return refreshUnit(serviceFileOptions());
|
|
1410
|
+
}
|
|
1411
|
+
async function uninstall4() {
|
|
1412
|
+
const platform = process.platform;
|
|
1413
|
+
assertSupported(platform);
|
|
1414
|
+
if (platform === "darwin") await uninstall2();
|
|
1415
|
+
else await uninstall3();
|
|
1416
|
+
}
|
|
1417
|
+
async function status3() {
|
|
1418
|
+
const platform = process.platform;
|
|
1419
|
+
assertSupported(platform);
|
|
1420
|
+
if (platform === "darwin") return status();
|
|
1421
|
+
return status2();
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1304
1424
|
// src/version.ts
|
|
1305
|
-
var AGENT_VERSION = "0.2.
|
|
1425
|
+
var AGENT_VERSION = "0.2.2";
|
|
1426
|
+
|
|
1427
|
+
// src/rpc/update.ts
|
|
1428
|
+
var PACKAGE = "@termhub/agent";
|
|
1429
|
+
var NPM_TIMEOUT_MS = 15e4;
|
|
1430
|
+
var EXIT_DELAY_MS = 750;
|
|
1431
|
+
function npmCliBesideNode(execPath = process.execPath) {
|
|
1432
|
+
const dir = path9.dirname(execPath);
|
|
1433
|
+
const symlink = path9.join(dir, "npm");
|
|
1434
|
+
if (existsSync(symlink)) return realpathSync2(symlink);
|
|
1435
|
+
const fallback = path9.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js");
|
|
1436
|
+
return existsSync(fallback) ? fallback : null;
|
|
1437
|
+
}
|
|
1438
|
+
async function installedAgentVersion(argv1 = process.argv[1]) {
|
|
1439
|
+
const script = resolveScriptPath(argv1);
|
|
1440
|
+
if (!script) return null;
|
|
1441
|
+
try {
|
|
1442
|
+
const pkg = JSON.parse(await readFile2(path9.join(path9.dirname(script), "..", "package.json"), "utf8"));
|
|
1443
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
1444
|
+
} catch {
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
var defaultDeps = {
|
|
1449
|
+
run,
|
|
1450
|
+
execPath: process.execPath,
|
|
1451
|
+
npmCli: npmCliBesideNode,
|
|
1452
|
+
installedVersion: installedAgentVersion,
|
|
1453
|
+
serviceInstalled: () => status3(),
|
|
1454
|
+
exit: (code) => process.exit(code),
|
|
1455
|
+
log: (msg, meta) => console.error(meta ? `[termhub-agent] ${msg} ${JSON.stringify(meta)}` : `[termhub-agent] ${msg}`)
|
|
1456
|
+
};
|
|
1457
|
+
var inFlight = null;
|
|
1458
|
+
async function updateAgent(params, deps = defaultDeps) {
|
|
1459
|
+
if (inFlight) throw new RpcFailure("failed", "update already running");
|
|
1460
|
+
inFlight = doUpdate(params.version, deps);
|
|
1461
|
+
try {
|
|
1462
|
+
return await inFlight;
|
|
1463
|
+
} finally {
|
|
1464
|
+
inFlight = null;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
async function doUpdate(version, deps) {
|
|
1468
|
+
deps.log("update starting", { from: AGENT_VERSION, to: version });
|
|
1469
|
+
const npmCli = deps.npmCli();
|
|
1470
|
+
if (!npmCli) throw new RpcFailure("notfound", "npm not found beside node");
|
|
1471
|
+
const r = await deps.run(deps.execPath, [npmCli, "install", "-g", "--no-fund", "--no-audit", `${PACKAGE}@${version}`], { timeoutMs: NPM_TIMEOUT_MS });
|
|
1472
|
+
if (r.error === "enoent") throw new RpcFailure("notfound", "npm not found on this machine");
|
|
1473
|
+
if (r.timedOut) throw new RpcFailure("timeout", "npm install timed out");
|
|
1474
|
+
if (r.code !== 0) {
|
|
1475
|
+
deps.log("update failed", { to: version, code: r.code });
|
|
1476
|
+
throw new RpcFailure("failed", `npm exited with code ${r.code ?? "unknown"}`);
|
|
1477
|
+
}
|
|
1478
|
+
const installed = await deps.installedVersion();
|
|
1479
|
+
if (installed !== version) throw new RpcFailure("failed", `installed version mismatch (${installed ?? "unknown"})`);
|
|
1480
|
+
const restart = await deps.serviceInstalled() ? "service" : "manual";
|
|
1481
|
+
deps.log("update installed", { from: AGENT_VERSION, to: version, restart });
|
|
1482
|
+
if (restart === "service") {
|
|
1483
|
+
setTimeout(() => deps.exit(1), EXIT_DELAY_MS);
|
|
1484
|
+
}
|
|
1485
|
+
return { installed_version: installed, restart };
|
|
1486
|
+
}
|
|
1487
|
+
var update = (params) => updateAgent(params);
|
|
1488
|
+
|
|
1489
|
+
// src/rpc/index.ts
|
|
1490
|
+
var handlers = {
|
|
1491
|
+
"tmux.list": list2,
|
|
1492
|
+
"tmux.kill": kill,
|
|
1493
|
+
"tmux.capture": capture,
|
|
1494
|
+
"tmux.ensure": ensure,
|
|
1495
|
+
"tmux.sendText": sendText,
|
|
1496
|
+
"tmux.sendKey": sendKey,
|
|
1497
|
+
"tools.detect": detect,
|
|
1498
|
+
"hw.probe": probe,
|
|
1499
|
+
"fs.list": list,
|
|
1500
|
+
"fs.mkdir": mkdir,
|
|
1501
|
+
"ai.credential": credential,
|
|
1502
|
+
"file.paste": pasteFile,
|
|
1503
|
+
"hooks.install": install,
|
|
1504
|
+
"hooks.uninstall": uninstall,
|
|
1505
|
+
"agent.update": update
|
|
1506
|
+
};
|
|
1306
1507
|
|
|
1307
1508
|
// src/run.ts
|
|
1308
1509
|
function detectOs(platform = process.platform) {
|
|
@@ -1322,7 +1523,7 @@ async function buildHello(osName) {
|
|
|
1322
1523
|
agent_version: AGENT_VERSION,
|
|
1323
1524
|
os: osName,
|
|
1324
1525
|
arch: process.arch,
|
|
1325
|
-
hostname:
|
|
1526
|
+
hostname: os6.hostname(),
|
|
1326
1527
|
tmux: tools.includes("tmux"),
|
|
1327
1528
|
tools
|
|
1328
1529
|
};
|
|
@@ -1342,7 +1543,7 @@ async function checkServerConnection(config, timeoutMs = 5e3) {
|
|
|
1342
1543
|
{
|
|
1343
1544
|
url: config.url,
|
|
1344
1545
|
token: config.token,
|
|
1345
|
-
hello: { agent_version: AGENT_VERSION, os: osName, arch: process.arch, hostname:
|
|
1546
|
+
hello: { agent_version: AGENT_VERSION, os: osName, arch: process.arch, hostname: os6.hostname(), tmux: false, tools: [], probe: true },
|
|
1346
1547
|
onServerMessage: () => {
|
|
1347
1548
|
},
|
|
1348
1549
|
onStream: () => {
|
|
@@ -1492,17 +1693,17 @@ function disconnectCommand() {
|
|
|
1492
1693
|
}
|
|
1493
1694
|
|
|
1494
1695
|
// src/doctor.ts
|
|
1495
|
-
import
|
|
1496
|
-
import
|
|
1497
|
-
import
|
|
1696
|
+
import fs6 from "fs";
|
|
1697
|
+
import os7 from "os";
|
|
1698
|
+
import path10 from "path";
|
|
1498
1699
|
var SERVER_CHECK_TIMEOUT_MS = 5e3;
|
|
1499
1700
|
function defaultDoctorPaths() {
|
|
1500
|
-
const home =
|
|
1501
|
-
const paths = [home,
|
|
1701
|
+
const home = os7.homedir();
|
|
1702
|
+
const paths = [home, path10.join(home, "Documents"), path10.join(home, "Desktop")];
|
|
1502
1703
|
if (process.platform === "darwin") {
|
|
1503
1704
|
try {
|
|
1504
|
-
for (const entry of
|
|
1505
|
-
if (entry.isDirectory()) paths.push(
|
|
1705
|
+
for (const entry of fs6.readdirSync("/Volumes", { withFileTypes: true })) {
|
|
1706
|
+
if (entry.isDirectory()) paths.push(path10.join("/Volumes", entry.name));
|
|
1506
1707
|
}
|
|
1507
1708
|
} catch {
|
|
1508
1709
|
}
|
|
@@ -1515,7 +1716,7 @@ function pathErrorCode(err) {
|
|
|
1515
1716
|
if (e?.code) return e.code;
|
|
1516
1717
|
return err instanceof Error ? err.message : String(err);
|
|
1517
1718
|
}
|
|
1518
|
-
function checkPathAccess(p, fsImpl =
|
|
1719
|
+
function checkPathAccess(p, fsImpl = fs6) {
|
|
1519
1720
|
try {
|
|
1520
1721
|
fsImpl.readdirSync(p);
|
|
1521
1722
|
return { path: p, ok: true };
|
|
@@ -1527,7 +1728,7 @@ function fullDiskAccessNote(execPath = process.execPath) {
|
|
|
1527
1728
|
return `Conceda Acesso Total ao Disco a ${execPath} em Ajustes \u2192 Privacidade e Seguran\xE7a \u2192 Acesso Total ao Disco`;
|
|
1528
1729
|
}
|
|
1529
1730
|
async function runDoctor(paths, deps = {}) {
|
|
1530
|
-
const fsImpl = deps.fs ??
|
|
1731
|
+
const fsImpl = deps.fs ?? fs6;
|
|
1531
1732
|
const connect = deps.connect ?? checkServerConnection;
|
|
1532
1733
|
const config = readConfig();
|
|
1533
1734
|
const configReport = { ok: config !== null, path: configPath() };
|
|
@@ -1590,119 +1791,16 @@ async function runCommand(log2) {
|
|
|
1590
1791
|
if (!config) {
|
|
1591
1792
|
return exitWithoutRestart("Nenhuma configura\xE7\xE3o. Rode: termhub-agent connect --url <url>");
|
|
1592
1793
|
}
|
|
1593
|
-
await runForegroundUntilSignal(config, log2);
|
|
1594
|
-
}
|
|
1595
|
-
|
|
1596
|
-
// src/commands/service.ts
|
|
1597
|
-
import os8 from "os";
|
|
1598
|
-
|
|
1599
|
-
// src/service/index.ts
|
|
1600
|
-
import path9 from "path";
|
|
1601
|
-
|
|
1602
|
-
// src/paths.ts
|
|
1603
|
-
import { realpathSync } from "fs";
|
|
1604
|
-
import path7 from "path";
|
|
1605
|
-
import { pathToFileURL } from "url";
|
|
1606
|
-
function resolveScriptPath(argv1) {
|
|
1607
|
-
if (!argv1) return "";
|
|
1608
1794
|
try {
|
|
1609
|
-
|
|
1610
|
-
} catch {
|
|
1611
|
-
return path7.resolve(argv1);
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
function isMainModule(importMetaUrl, argv1) {
|
|
1615
|
-
if (!argv1) return false;
|
|
1616
|
-
return importMetaUrl === pathToFileURL(resolveScriptPath(argv1)).href;
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
|
-
// src/service/systemd.ts
|
|
1620
|
-
import fs6 from "fs";
|
|
1621
|
-
import os7 from "os";
|
|
1622
|
-
import path8 from "path";
|
|
1623
|
-
var UNIT_NAME = "termhub-agent";
|
|
1624
|
-
function renderUnit({ node, script, logPath }) {
|
|
1625
|
-
const lines = [
|
|
1626
|
-
"[Unit]",
|
|
1627
|
-
"Description=termhub agent",
|
|
1628
|
-
"After=network-online.target",
|
|
1629
|
-
"",
|
|
1630
|
-
"[Service]",
|
|
1631
|
-
`ExecStart=${node} ${script} run`,
|
|
1632
|
-
"Restart=on-failure",
|
|
1633
|
-
"RestartSec=2",
|
|
1634
|
-
"RestartPreventExitStatus=78",
|
|
1635
|
-
`Environment=PATH=${agentEnv().PATH ?? ""}`
|
|
1636
|
-
];
|
|
1637
|
-
if (logPath) {
|
|
1638
|
-
lines.push(`StandardOutput=append:${logPath}`, `StandardError=append:${logPath}`);
|
|
1639
|
-
}
|
|
1640
|
-
lines.push("", "[Install]", "WantedBy=default.target", "");
|
|
1641
|
-
return lines.join("\n");
|
|
1642
|
-
}
|
|
1643
|
-
function unitPath() {
|
|
1644
|
-
return path8.join(os7.homedir(), ".config", "systemd", "user", `${UNIT_NAME}.service`);
|
|
1645
|
-
}
|
|
1646
|
-
async function install3(opts, deps = {}) {
|
|
1647
|
-
const runFn = deps.run ?? run;
|
|
1648
|
-
const file = unitPath();
|
|
1649
|
-
const unit = renderUnit({ node: opts.node, script: opts.script, logPath: opts.logPath });
|
|
1650
|
-
fs6.mkdirSync(path8.dirname(file), { recursive: true });
|
|
1651
|
-
fs6.writeFileSync(file, unit, "utf8");
|
|
1652
|
-
const reload = await runFn("systemctl", ["--user", "daemon-reload"]);
|
|
1653
|
-
if (reload.code !== 0) throw new Error(`systemctl daemon-reload failed (code ${reload.code}): ${reload.stderr.trim()}`);
|
|
1654
|
-
const enable = await runFn("systemctl", ["--user", "enable", "--now", UNIT_NAME]);
|
|
1655
|
-
if (enable.code !== 0) throw new Error(`systemctl enable --now failed (code ${enable.code}): ${enable.stderr.trim()}`);
|
|
1656
|
-
console.log("Para o agente continuar ap\xF3s o logout: loginctl enable-linger $USER");
|
|
1657
|
-
}
|
|
1658
|
-
async function uninstall3(deps = {}) {
|
|
1659
|
-
const runFn = deps.run ?? run;
|
|
1660
|
-
await runFn("systemctl", ["--user", "disable", "--now", UNIT_NAME]);
|
|
1661
|
-
try {
|
|
1662
|
-
fs6.unlinkSync(unitPath());
|
|
1795
|
+
if (await refresh()) log2("service definition refreshed");
|
|
1663
1796
|
} catch (err) {
|
|
1664
|
-
|
|
1797
|
+
log2("service definition could not be refreshed", { error: err instanceof Error ? err.message : String(err) });
|
|
1665
1798
|
}
|
|
1666
|
-
await
|
|
1667
|
-
}
|
|
1668
|
-
async function status2(deps = {}) {
|
|
1669
|
-
const runFn = deps.run ?? run;
|
|
1670
|
-
const result = await runFn("systemctl", ["--user", "is-active", UNIT_NAME]);
|
|
1671
|
-
return result.code === 0;
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
// src/service/index.ts
|
|
1675
|
-
function serviceFileOptions() {
|
|
1676
|
-
return {
|
|
1677
|
-
node: process.execPath,
|
|
1678
|
-
script: resolveScriptPath(process.argv[1]),
|
|
1679
|
-
logPath: path9.join(agentHome(), "agent.log")
|
|
1680
|
-
};
|
|
1681
|
-
}
|
|
1682
|
-
function assertSupported(platform) {
|
|
1683
|
-
if (platform !== "darwin" && platform !== "linux") throw new Error("Sistema n\xE3o suportado");
|
|
1684
|
-
}
|
|
1685
|
-
async function install4() {
|
|
1686
|
-
const platform = process.platform;
|
|
1687
|
-
assertSupported(platform);
|
|
1688
|
-
const opts = serviceFileOptions();
|
|
1689
|
-
if (platform === "darwin") await install2(opts);
|
|
1690
|
-
else await install3(opts);
|
|
1691
|
-
}
|
|
1692
|
-
async function uninstall4() {
|
|
1693
|
-
const platform = process.platform;
|
|
1694
|
-
assertSupported(platform);
|
|
1695
|
-
if (platform === "darwin") await uninstall2();
|
|
1696
|
-
else await uninstall3();
|
|
1697
|
-
}
|
|
1698
|
-
async function status3() {
|
|
1699
|
-
const platform = process.platform;
|
|
1700
|
-
assertSupported(platform);
|
|
1701
|
-
if (platform === "darwin") return status();
|
|
1702
|
-
return status2();
|
|
1799
|
+
await runForegroundUntilSignal(config, log2);
|
|
1703
1800
|
}
|
|
1704
1801
|
|
|
1705
1802
|
// src/commands/service.ts
|
|
1803
|
+
import os8 from "os";
|
|
1706
1804
|
async function serviceCommand(sub) {
|
|
1707
1805
|
switch (sub) {
|
|
1708
1806
|
case "install": {
|
package/package.json
CHANGED