@termhub/agent 0.1.7 → 0.2.1
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/dist/cli.js +260 -144
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -39,6 +39,9 @@ var sessionName = z.string().min(1).max(128).regex(SESSION_RE);
|
|
|
39
39
|
var machinePath = z.string().min(1).max(4096).refine((p) => (p === "~" || p.startsWith("~/") || p.startsWith("/")) && !/[\0\n\r]/.test(p), "invalid path");
|
|
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
|
+
var TMUX_KEYS = ["Enter", "Escape", "C-c", "Up", "Down", "Tab", "y", "n", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
|
|
43
|
+
var tmuxKey = z.enum(TMUX_KEYS);
|
|
44
|
+
var TEXT_MAX_CHARS = 4e3;
|
|
42
45
|
var rpcErrorSchema = z.object({
|
|
43
46
|
/** `failed`: the operation ran on the machine and `message` says why it failed, in words meant for the user. */
|
|
44
47
|
code: z.enum(["eperm", "notfound", "no_tmux", "timeout", "invalid", "internal", "failed"]),
|
|
@@ -51,6 +54,11 @@ var RPC = {
|
|
|
51
54
|
"tmux.list": def(z.object({}), z.object({ sessions: z.array(sessionName) })),
|
|
52
55
|
"tmux.kill": def(z.object({ session: sessionName }), z.object({ killed: z.boolean() })),
|
|
53
56
|
"tmux.capture": def(z.object({ session: sessionName, lines: z.number().int().min(1).max(5e3) }), z.object({ text: z.string() })),
|
|
57
|
+
/** Idempotent: creates the detached session in `cwd` when it is missing. `created` says whether it had to. */
|
|
58
|
+
"tmux.ensure": def(z.object({ session: sessionName, cwd: machinePath }), z.object({ created: z.boolean() }), 1e4),
|
|
59
|
+
/** Types `text` literally, then (with `enter`) presses Enter on its own after a short pause. */
|
|
60
|
+
"tmux.sendText": def(z.object({ session: sessionName, text: z.string().max(TEXT_MAX_CHARS), enter: z.boolean() }), z.object({ sent: z.literal(true) }), 1e4),
|
|
61
|
+
"tmux.sendKey": def(z.object({ session: sessionName, key: tmuxKey }), z.object({ sent: z.literal(true) }), 1e4),
|
|
54
62
|
"tools.detect": def(z.object({}), z.object({ os: z.string().nullable(), tools: z.array(z.string().max(32)) })),
|
|
55
63
|
"hw.probe": def(z.object({}), z.object({ stdout: z.string() }), 15e3),
|
|
56
64
|
"fs.list": def(z.object({ path: machinePath }), z.object({ stdout: z.string() })),
|
|
@@ -69,7 +77,9 @@ var RPC = {
|
|
|
69
77
|
token: z.string().min(1).max(256).regex(/^[A-Za-z0-9_-]+$/),
|
|
70
78
|
claude_dirs: z.array(machinePath).max(16).optional()
|
|
71
79
|
}), z.object({ home: z.string(), claude: z.enum(["installed", "skipped"]), codex: z.enum(["installed", "skipped"]), claude_dirs: z.array(z.string()).optional() }), 15e3),
|
|
72
|
-
"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)
|
|
73
83
|
};
|
|
74
84
|
var RPC_METHODS = Object.keys(RPC);
|
|
75
85
|
var rpcMethod = z.enum(RPC_METHODS);
|
|
@@ -364,7 +374,7 @@ function deleteConfig() {
|
|
|
364
374
|
}
|
|
365
375
|
|
|
366
376
|
// src/run.ts
|
|
367
|
-
import
|
|
377
|
+
import os6 from "os";
|
|
368
378
|
|
|
369
379
|
// src/exec.ts
|
|
370
380
|
import { execFile } from "child_process";
|
|
@@ -658,10 +668,10 @@ function stripCodexConfig(current) {
|
|
|
658
668
|
// src/exec.ts
|
|
659
669
|
var DEFAULT_TIMEOUT_MS = 8e3;
|
|
660
670
|
var RpcFailure = class extends Error {
|
|
661
|
-
constructor(code, message,
|
|
671
|
+
constructor(code, message, path11) {
|
|
662
672
|
super(message);
|
|
663
673
|
this.code = code;
|
|
664
|
-
this.path =
|
|
674
|
+
this.path = path11;
|
|
665
675
|
}
|
|
666
676
|
code;
|
|
667
677
|
path;
|
|
@@ -1111,12 +1121,13 @@ async function pasteFile(params) {
|
|
|
1111
1121
|
if (r.timedOut) throw new RpcFailure("timeout", "file.paste timed out");
|
|
1112
1122
|
if (r.code !== 0) throw new RpcFailure("internal", `file.paste exited with code ${r.code}`);
|
|
1113
1123
|
const lines = r.stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1114
|
-
const
|
|
1115
|
-
if (!
|
|
1116
|
-
return { path:
|
|
1124
|
+
const path11 = lines[lines.length - 1] ?? "";
|
|
1125
|
+
if (!path11.startsWith("/")) throw new RpcFailure("internal", "unexpected paste output");
|
|
1126
|
+
return { path: path11 };
|
|
1117
1127
|
}
|
|
1118
1128
|
|
|
1119
1129
|
// src/rpc/tmux.ts
|
|
1130
|
+
var ENTER_PAUSE_MS = 300;
|
|
1120
1131
|
function processFailure2(r) {
|
|
1121
1132
|
if (r.timedOut) return new RpcFailure("timeout", "tmux timed out");
|
|
1122
1133
|
if (r.error === "enoent") return new RpcFailure("no_tmux", "tmux not found");
|
|
@@ -1144,6 +1155,42 @@ async function capture(params) {
|
|
|
1144
1155
|
if (r.code !== 0) throw new RpcFailure("notfound", "session not found");
|
|
1145
1156
|
return { text: r.stdout };
|
|
1146
1157
|
}
|
|
1158
|
+
var pane = (session) => `=${session}:`;
|
|
1159
|
+
var why = (stderr, fallback) => stderr.trim().split("\n")[0] || fallback;
|
|
1160
|
+
async function ensure(params) {
|
|
1161
|
+
const has = await run(tmuxPath(), ["has-session", "-t", `=${params.session}`]);
|
|
1162
|
+
const hasFailure = processFailure2(has);
|
|
1163
|
+
if (hasFailure) throw hasFailure;
|
|
1164
|
+
if (has.code === 0) return { created: false };
|
|
1165
|
+
const made = await run(tmuxPath(), ["new-session", "-d", "-s", params.session, "-c", params.cwd]);
|
|
1166
|
+
const madeFailure = processFailure2(made);
|
|
1167
|
+
if (madeFailure) throw madeFailure;
|
|
1168
|
+
if (made.code !== 0) throw new RpcFailure("failed", why(made.stderr, "tmux new-session falhou"), params.cwd);
|
|
1169
|
+
return { created: true };
|
|
1170
|
+
}
|
|
1171
|
+
async function sendText(params) {
|
|
1172
|
+
if (params.text) {
|
|
1173
|
+
const typed = await run(tmuxPath(), ["send-keys", "-t", pane(params.session), "-l", "--", params.text]);
|
|
1174
|
+
const failure = processFailure2(typed);
|
|
1175
|
+
if (failure) throw failure;
|
|
1176
|
+
if (typed.code !== 0) throw new RpcFailure("notfound", why(typed.stderr, "session not found"));
|
|
1177
|
+
if (params.enter) await new Promise((r) => setTimeout(r, ENTER_PAUSE_MS));
|
|
1178
|
+
}
|
|
1179
|
+
if (params.enter) {
|
|
1180
|
+
const entered = await run(tmuxPath(), ["send-keys", "-t", pane(params.session), "Enter"]);
|
|
1181
|
+
const failure = processFailure2(entered);
|
|
1182
|
+
if (failure) throw failure;
|
|
1183
|
+
if (entered.code !== 0) throw new RpcFailure("notfound", why(entered.stderr, "session not found"));
|
|
1184
|
+
}
|
|
1185
|
+
return { sent: true };
|
|
1186
|
+
}
|
|
1187
|
+
async function sendKey(params) {
|
|
1188
|
+
const r = await run(tmuxPath(), ["send-keys", "-t", pane(params.session), params.key]);
|
|
1189
|
+
const failure = processFailure2(r);
|
|
1190
|
+
if (failure) throw failure;
|
|
1191
|
+
if (r.code !== 0) throw new RpcFailure("notfound", why(r.stderr, "session not found"));
|
|
1192
|
+
return { sent: true };
|
|
1193
|
+
}
|
|
1147
1194
|
|
|
1148
1195
|
// src/rpc/tools.ts
|
|
1149
1196
|
async function detect(_params) {
|
|
@@ -1154,25 +1201,35 @@ async function detect(_params) {
|
|
|
1154
1201
|
return { os: os9, tools: capabilities };
|
|
1155
1202
|
}
|
|
1156
1203
|
|
|
1157
|
-
// src/rpc/
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
"
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
}
|
|
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";
|
|
1171
1228
|
|
|
1172
1229
|
// src/service/launchd.ts
|
|
1173
1230
|
import fs4 from "fs";
|
|
1174
1231
|
import os4 from "os";
|
|
1175
|
-
import
|
|
1232
|
+
import path6 from "path";
|
|
1176
1233
|
var LABEL = "dev.termhub.agent";
|
|
1177
1234
|
function renderPlist({ label, node, script, logPath }) {
|
|
1178
1235
|
const escape = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -1211,7 +1268,7 @@ function renderPlist({ label, node, script, logPath }) {
|
|
|
1211
1268
|
`;
|
|
1212
1269
|
}
|
|
1213
1270
|
function plistPath() {
|
|
1214
|
-
return
|
|
1271
|
+
return path6.join(os4.homedir(), "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
1215
1272
|
}
|
|
1216
1273
|
function gui() {
|
|
1217
1274
|
return `gui/${process.getuid ? process.getuid() : 0}`;
|
|
@@ -1220,7 +1277,7 @@ async function install2(opts, deps = {}) {
|
|
|
1220
1277
|
const runFn = deps.run ?? run;
|
|
1221
1278
|
const file = plistPath();
|
|
1222
1279
|
const plist = renderPlist({ label: LABEL, node: opts.node, script: opts.script, logPath: opts.logPath });
|
|
1223
|
-
fs4.mkdirSync(
|
|
1280
|
+
fs4.mkdirSync(path6.dirname(file), { recursive: true });
|
|
1224
1281
|
fs4.writeFileSync(file, plist, "utf8");
|
|
1225
1282
|
await runFn("launchctl", ["bootout", gui(), file]);
|
|
1226
1283
|
const result = await runFn("launchctl", ["bootstrap", gui(), file]);
|
|
@@ -1253,8 +1310,175 @@ async function status(deps = {}) {
|
|
|
1253
1310
|
return result.code === 0;
|
|
1254
1311
|
}
|
|
1255
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 }) {
|
|
1319
|
+
const lines = [
|
|
1320
|
+
"[Unit]",
|
|
1321
|
+
"Description=termhub agent",
|
|
1322
|
+
"After=network-online.target",
|
|
1323
|
+
"",
|
|
1324
|
+
"[Service]",
|
|
1325
|
+
`ExecStart=${node} ${script} run`,
|
|
1326
|
+
"Restart=on-failure",
|
|
1327
|
+
"RestartSec=2",
|
|
1328
|
+
"RestartPreventExitStatus=78",
|
|
1329
|
+
`Environment=PATH=${agentEnv().PATH ?? ""}`
|
|
1330
|
+
];
|
|
1331
|
+
if (logPath) {
|
|
1332
|
+
lines.push(`StandardOutput=append:${logPath}`, `StandardError=append:${logPath}`);
|
|
1333
|
+
}
|
|
1334
|
+
lines.push("", "[Install]", "WantedBy=default.target", "");
|
|
1335
|
+
return lines.join("\n");
|
|
1336
|
+
}
|
|
1337
|
+
function unitPath() {
|
|
1338
|
+
return path7.join(os5.homedir(), ".config", "systemd", "user", `${UNIT_NAME}.service`);
|
|
1339
|
+
}
|
|
1340
|
+
async function install3(opts, deps = {}) {
|
|
1341
|
+
const runFn = deps.run ?? run;
|
|
1342
|
+
const file = unitPath();
|
|
1343
|
+
const unit = renderUnit({ node: opts.node, script: opts.script, logPath: opts.logPath });
|
|
1344
|
+
fs5.mkdirSync(path7.dirname(file), { recursive: true });
|
|
1345
|
+
fs5.writeFileSync(file, unit, "utf8");
|
|
1346
|
+
const reload = await runFn("systemctl", ["--user", "daemon-reload"]);
|
|
1347
|
+
if (reload.code !== 0) throw new Error(`systemctl daemon-reload failed (code ${reload.code}): ${reload.stderr.trim()}`);
|
|
1348
|
+
const enable = await runFn("systemctl", ["--user", "enable", "--now", UNIT_NAME]);
|
|
1349
|
+
if (enable.code !== 0) throw new Error(`systemctl enable --now failed (code ${enable.code}): ${enable.stderr.trim()}`);
|
|
1350
|
+
console.log("Para o agente continuar ap\xF3s o logout: loginctl enable-linger $USER");
|
|
1351
|
+
}
|
|
1352
|
+
async function uninstall3(deps = {}) {
|
|
1353
|
+
const runFn = deps.run ?? run;
|
|
1354
|
+
await runFn("systemctl", ["--user", "disable", "--now", UNIT_NAME]);
|
|
1355
|
+
try {
|
|
1356
|
+
fs5.unlinkSync(unitPath());
|
|
1357
|
+
} catch (err) {
|
|
1358
|
+
if (err.code !== "ENOENT") throw err;
|
|
1359
|
+
}
|
|
1360
|
+
await runFn("systemctl", ["--user", "daemon-reload"]);
|
|
1361
|
+
}
|
|
1362
|
+
async function status2(deps = {}) {
|
|
1363
|
+
const runFn = deps.run ?? run;
|
|
1364
|
+
const result = await runFn("systemctl", ["--user", "is-active", UNIT_NAME]);
|
|
1365
|
+
return result.code === 0;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// src/service/index.ts
|
|
1369
|
+
function serviceFileOptions() {
|
|
1370
|
+
return {
|
|
1371
|
+
node: process.execPath,
|
|
1372
|
+
script: resolveScriptPath(process.argv[1]),
|
|
1373
|
+
logPath: path8.join(agentHome(), "agent.log")
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
function assertSupported(platform) {
|
|
1377
|
+
if (platform !== "darwin" && platform !== "linux") throw new Error("Sistema n\xE3o suportado");
|
|
1378
|
+
}
|
|
1379
|
+
async function install4() {
|
|
1380
|
+
const platform = process.platform;
|
|
1381
|
+
assertSupported(platform);
|
|
1382
|
+
const opts = serviceFileOptions();
|
|
1383
|
+
if (platform === "darwin") await install2(opts);
|
|
1384
|
+
else await install3(opts);
|
|
1385
|
+
}
|
|
1386
|
+
async function uninstall4() {
|
|
1387
|
+
const platform = process.platform;
|
|
1388
|
+
assertSupported(platform);
|
|
1389
|
+
if (platform === "darwin") await uninstall2();
|
|
1390
|
+
else await uninstall3();
|
|
1391
|
+
}
|
|
1392
|
+
async function status3() {
|
|
1393
|
+
const platform = process.platform;
|
|
1394
|
+
assertSupported(platform);
|
|
1395
|
+
if (platform === "darwin") return status();
|
|
1396
|
+
return status2();
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1256
1399
|
// src/version.ts
|
|
1257
|
-
var AGENT_VERSION = "0.1
|
|
1400
|
+
var AGENT_VERSION = "0.2.1";
|
|
1401
|
+
|
|
1402
|
+
// src/rpc/update.ts
|
|
1403
|
+
var PACKAGE = "@termhub/agent";
|
|
1404
|
+
var NPM_TIMEOUT_MS = 15e4;
|
|
1405
|
+
var EXIT_DELAY_MS = 750;
|
|
1406
|
+
function npmCliBesideNode(execPath = process.execPath) {
|
|
1407
|
+
const dir = path9.dirname(execPath);
|
|
1408
|
+
const symlink = path9.join(dir, "npm");
|
|
1409
|
+
if (existsSync(symlink)) return realpathSync2(symlink);
|
|
1410
|
+
const fallback = path9.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js");
|
|
1411
|
+
return existsSync(fallback) ? fallback : null;
|
|
1412
|
+
}
|
|
1413
|
+
async function installedAgentVersion(argv1 = process.argv[1]) {
|
|
1414
|
+
const script = resolveScriptPath(argv1);
|
|
1415
|
+
if (!script) return null;
|
|
1416
|
+
try {
|
|
1417
|
+
const pkg = JSON.parse(await readFile2(path9.join(path9.dirname(script), "..", "package.json"), "utf8"));
|
|
1418
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
1419
|
+
} catch {
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
var defaultDeps = {
|
|
1424
|
+
run,
|
|
1425
|
+
execPath: process.execPath,
|
|
1426
|
+
npmCli: npmCliBesideNode,
|
|
1427
|
+
installedVersion: installedAgentVersion,
|
|
1428
|
+
serviceInstalled: () => status3(),
|
|
1429
|
+
exit: (code) => process.exit(code),
|
|
1430
|
+
log: (msg, meta) => console.error(meta ? `[termhub-agent] ${msg} ${JSON.stringify(meta)}` : `[termhub-agent] ${msg}`)
|
|
1431
|
+
};
|
|
1432
|
+
var inFlight = null;
|
|
1433
|
+
async function updateAgent(params, deps = defaultDeps) {
|
|
1434
|
+
if (inFlight) throw new RpcFailure("failed", "update already running");
|
|
1435
|
+
inFlight = doUpdate(params.version, deps);
|
|
1436
|
+
try {
|
|
1437
|
+
return await inFlight;
|
|
1438
|
+
} finally {
|
|
1439
|
+
inFlight = null;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
async function doUpdate(version, deps) {
|
|
1443
|
+
deps.log("update starting", { from: AGENT_VERSION, to: version });
|
|
1444
|
+
const npmCli = deps.npmCli();
|
|
1445
|
+
if (!npmCli) throw new RpcFailure("notfound", "npm not found beside node");
|
|
1446
|
+
const r = await deps.run(deps.execPath, [npmCli, "install", "-g", "--no-fund", "--no-audit", `${PACKAGE}@${version}`], { timeoutMs: NPM_TIMEOUT_MS });
|
|
1447
|
+
if (r.error === "enoent") throw new RpcFailure("notfound", "npm not found on this machine");
|
|
1448
|
+
if (r.timedOut) throw new RpcFailure("timeout", "npm install timed out");
|
|
1449
|
+
if (r.code !== 0) {
|
|
1450
|
+
deps.log("update failed", { to: version, code: r.code });
|
|
1451
|
+
throw new RpcFailure("failed", `npm exited with code ${r.code ?? "unknown"}`);
|
|
1452
|
+
}
|
|
1453
|
+
const installed = await deps.installedVersion();
|
|
1454
|
+
if (installed !== version) throw new RpcFailure("failed", `installed version mismatch (${installed ?? "unknown"})`);
|
|
1455
|
+
const restart = await deps.serviceInstalled() ? "service" : "manual";
|
|
1456
|
+
deps.log("update installed", { from: AGENT_VERSION, to: version, restart });
|
|
1457
|
+
if (restart === "service") {
|
|
1458
|
+
setTimeout(() => deps.exit(1), EXIT_DELAY_MS);
|
|
1459
|
+
}
|
|
1460
|
+
return { installed_version: installed, restart };
|
|
1461
|
+
}
|
|
1462
|
+
var update = (params) => updateAgent(params);
|
|
1463
|
+
|
|
1464
|
+
// src/rpc/index.ts
|
|
1465
|
+
var handlers = {
|
|
1466
|
+
"tmux.list": list2,
|
|
1467
|
+
"tmux.kill": kill,
|
|
1468
|
+
"tmux.capture": capture,
|
|
1469
|
+
"tmux.ensure": ensure,
|
|
1470
|
+
"tmux.sendText": sendText,
|
|
1471
|
+
"tmux.sendKey": sendKey,
|
|
1472
|
+
"tools.detect": detect,
|
|
1473
|
+
"hw.probe": probe,
|
|
1474
|
+
"fs.list": list,
|
|
1475
|
+
"fs.mkdir": mkdir,
|
|
1476
|
+
"ai.credential": credential,
|
|
1477
|
+
"file.paste": pasteFile,
|
|
1478
|
+
"hooks.install": install,
|
|
1479
|
+
"hooks.uninstall": uninstall,
|
|
1480
|
+
"agent.update": update
|
|
1481
|
+
};
|
|
1258
1482
|
|
|
1259
1483
|
// src/run.ts
|
|
1260
1484
|
function detectOs(platform = process.platform) {
|
|
@@ -1274,7 +1498,7 @@ async function buildHello(osName) {
|
|
|
1274
1498
|
agent_version: AGENT_VERSION,
|
|
1275
1499
|
os: osName,
|
|
1276
1500
|
arch: process.arch,
|
|
1277
|
-
hostname:
|
|
1501
|
+
hostname: os6.hostname(),
|
|
1278
1502
|
tmux: tools.includes("tmux"),
|
|
1279
1503
|
tools
|
|
1280
1504
|
};
|
|
@@ -1294,7 +1518,7 @@ async function checkServerConnection(config, timeoutMs = 5e3) {
|
|
|
1294
1518
|
{
|
|
1295
1519
|
url: config.url,
|
|
1296
1520
|
token: config.token,
|
|
1297
|
-
hello: { agent_version: AGENT_VERSION, os: osName, arch: process.arch, hostname:
|
|
1521
|
+
hello: { agent_version: AGENT_VERSION, os: osName, arch: process.arch, hostname: os6.hostname(), tmux: false, tools: [], probe: true },
|
|
1298
1522
|
onServerMessage: () => {
|
|
1299
1523
|
},
|
|
1300
1524
|
onStream: () => {
|
|
@@ -1444,17 +1668,17 @@ function disconnectCommand() {
|
|
|
1444
1668
|
}
|
|
1445
1669
|
|
|
1446
1670
|
// src/doctor.ts
|
|
1447
|
-
import
|
|
1448
|
-
import
|
|
1449
|
-
import
|
|
1671
|
+
import fs6 from "fs";
|
|
1672
|
+
import os7 from "os";
|
|
1673
|
+
import path10 from "path";
|
|
1450
1674
|
var SERVER_CHECK_TIMEOUT_MS = 5e3;
|
|
1451
1675
|
function defaultDoctorPaths() {
|
|
1452
|
-
const home =
|
|
1453
|
-
const paths = [home,
|
|
1676
|
+
const home = os7.homedir();
|
|
1677
|
+
const paths = [home, path10.join(home, "Documents"), path10.join(home, "Desktop")];
|
|
1454
1678
|
if (process.platform === "darwin") {
|
|
1455
1679
|
try {
|
|
1456
|
-
for (const entry of
|
|
1457
|
-
if (entry.isDirectory()) paths.push(
|
|
1680
|
+
for (const entry of fs6.readdirSync("/Volumes", { withFileTypes: true })) {
|
|
1681
|
+
if (entry.isDirectory()) paths.push(path10.join("/Volumes", entry.name));
|
|
1458
1682
|
}
|
|
1459
1683
|
} catch {
|
|
1460
1684
|
}
|
|
@@ -1467,7 +1691,7 @@ function pathErrorCode(err) {
|
|
|
1467
1691
|
if (e?.code) return e.code;
|
|
1468
1692
|
return err instanceof Error ? err.message : String(err);
|
|
1469
1693
|
}
|
|
1470
|
-
function checkPathAccess(p, fsImpl =
|
|
1694
|
+
function checkPathAccess(p, fsImpl = fs6) {
|
|
1471
1695
|
try {
|
|
1472
1696
|
fsImpl.readdirSync(p);
|
|
1473
1697
|
return { path: p, ok: true };
|
|
@@ -1479,7 +1703,7 @@ function fullDiskAccessNote(execPath = process.execPath) {
|
|
|
1479
1703
|
return `Conceda Acesso Total ao Disco a ${execPath} em Ajustes \u2192 Privacidade e Seguran\xE7a \u2192 Acesso Total ao Disco`;
|
|
1480
1704
|
}
|
|
1481
1705
|
async function runDoctor(paths, deps = {}) {
|
|
1482
|
-
const fsImpl = deps.fs ??
|
|
1706
|
+
const fsImpl = deps.fs ?? fs6;
|
|
1483
1707
|
const connect = deps.connect ?? checkServerConnection;
|
|
1484
1708
|
const config = readConfig();
|
|
1485
1709
|
const configReport = { ok: config !== null, path: configPath() };
|
|
@@ -1547,114 +1771,6 @@ async function runCommand(log2) {
|
|
|
1547
1771
|
|
|
1548
1772
|
// src/commands/service.ts
|
|
1549
1773
|
import os8 from "os";
|
|
1550
|
-
|
|
1551
|
-
// src/service/index.ts
|
|
1552
|
-
import path9 from "path";
|
|
1553
|
-
|
|
1554
|
-
// src/paths.ts
|
|
1555
|
-
import { realpathSync } from "fs";
|
|
1556
|
-
import path7 from "path";
|
|
1557
|
-
import { pathToFileURL } from "url";
|
|
1558
|
-
function resolveScriptPath(argv1) {
|
|
1559
|
-
if (!argv1) return "";
|
|
1560
|
-
try {
|
|
1561
|
-
return realpathSync(argv1);
|
|
1562
|
-
} catch {
|
|
1563
|
-
return path7.resolve(argv1);
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
function isMainModule(importMetaUrl, argv1) {
|
|
1567
|
-
if (!argv1) return false;
|
|
1568
|
-
return importMetaUrl === pathToFileURL(resolveScriptPath(argv1)).href;
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
// src/service/systemd.ts
|
|
1572
|
-
import fs6 from "fs";
|
|
1573
|
-
import os7 from "os";
|
|
1574
|
-
import path8 from "path";
|
|
1575
|
-
var UNIT_NAME = "termhub-agent";
|
|
1576
|
-
function renderUnit({ node, script, logPath }) {
|
|
1577
|
-
const lines = [
|
|
1578
|
-
"[Unit]",
|
|
1579
|
-
"Description=termhub agent",
|
|
1580
|
-
"After=network-online.target",
|
|
1581
|
-
"",
|
|
1582
|
-
"[Service]",
|
|
1583
|
-
`ExecStart=${node} ${script} run`,
|
|
1584
|
-
"Restart=on-failure",
|
|
1585
|
-
"RestartSec=2",
|
|
1586
|
-
"RestartPreventExitStatus=78",
|
|
1587
|
-
`Environment=PATH=${agentEnv().PATH ?? ""}`
|
|
1588
|
-
];
|
|
1589
|
-
if (logPath) {
|
|
1590
|
-
lines.push(`StandardOutput=append:${logPath}`, `StandardError=append:${logPath}`);
|
|
1591
|
-
}
|
|
1592
|
-
lines.push("", "[Install]", "WantedBy=default.target", "");
|
|
1593
|
-
return lines.join("\n");
|
|
1594
|
-
}
|
|
1595
|
-
function unitPath() {
|
|
1596
|
-
return path8.join(os7.homedir(), ".config", "systemd", "user", `${UNIT_NAME}.service`);
|
|
1597
|
-
}
|
|
1598
|
-
async function install3(opts, deps = {}) {
|
|
1599
|
-
const runFn = deps.run ?? run;
|
|
1600
|
-
const file = unitPath();
|
|
1601
|
-
const unit = renderUnit({ node: opts.node, script: opts.script, logPath: opts.logPath });
|
|
1602
|
-
fs6.mkdirSync(path8.dirname(file), { recursive: true });
|
|
1603
|
-
fs6.writeFileSync(file, unit, "utf8");
|
|
1604
|
-
const reload = await runFn("systemctl", ["--user", "daemon-reload"]);
|
|
1605
|
-
if (reload.code !== 0) throw new Error(`systemctl daemon-reload failed (code ${reload.code}): ${reload.stderr.trim()}`);
|
|
1606
|
-
const enable = await runFn("systemctl", ["--user", "enable", "--now", UNIT_NAME]);
|
|
1607
|
-
if (enable.code !== 0) throw new Error(`systemctl enable --now failed (code ${enable.code}): ${enable.stderr.trim()}`);
|
|
1608
|
-
console.log("Para o agente continuar ap\xF3s o logout: loginctl enable-linger $USER");
|
|
1609
|
-
}
|
|
1610
|
-
async function uninstall3(deps = {}) {
|
|
1611
|
-
const runFn = deps.run ?? run;
|
|
1612
|
-
await runFn("systemctl", ["--user", "disable", "--now", UNIT_NAME]);
|
|
1613
|
-
try {
|
|
1614
|
-
fs6.unlinkSync(unitPath());
|
|
1615
|
-
} catch (err) {
|
|
1616
|
-
if (err.code !== "ENOENT") throw err;
|
|
1617
|
-
}
|
|
1618
|
-
await runFn("systemctl", ["--user", "daemon-reload"]);
|
|
1619
|
-
}
|
|
1620
|
-
async function status2(deps = {}) {
|
|
1621
|
-
const runFn = deps.run ?? run;
|
|
1622
|
-
const result = await runFn("systemctl", ["--user", "is-active", UNIT_NAME]);
|
|
1623
|
-
return result.code === 0;
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
// src/service/index.ts
|
|
1627
|
-
function serviceFileOptions() {
|
|
1628
|
-
return {
|
|
1629
|
-
node: process.execPath,
|
|
1630
|
-
script: resolveScriptPath(process.argv[1]),
|
|
1631
|
-
logPath: path9.join(agentHome(), "agent.log")
|
|
1632
|
-
};
|
|
1633
|
-
}
|
|
1634
|
-
function assertSupported(platform) {
|
|
1635
|
-
if (platform !== "darwin" && platform !== "linux") throw new Error("Sistema n\xE3o suportado");
|
|
1636
|
-
}
|
|
1637
|
-
async function install4() {
|
|
1638
|
-
const platform = process.platform;
|
|
1639
|
-
assertSupported(platform);
|
|
1640
|
-
const opts = serviceFileOptions();
|
|
1641
|
-
if (platform === "darwin") await install2(opts);
|
|
1642
|
-
else await install3(opts);
|
|
1643
|
-
}
|
|
1644
|
-
async function uninstall4() {
|
|
1645
|
-
const platform = process.platform;
|
|
1646
|
-
assertSupported(platform);
|
|
1647
|
-
if (platform === "darwin") await uninstall2();
|
|
1648
|
-
else await uninstall3();
|
|
1649
|
-
}
|
|
1650
|
-
async function status3() {
|
|
1651
|
-
const platform = process.platform;
|
|
1652
|
-
assertSupported(platform);
|
|
1653
|
-
if (platform === "darwin") return status();
|
|
1654
|
-
return status2();
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
// src/commands/service.ts
|
|
1658
1774
|
async function serviceCommand(sub) {
|
|
1659
1775
|
switch (sub) {
|
|
1660
1776
|
case "install": {
|
package/package.json
CHANGED