@termhub/agent 0.1.3 → 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.
- package/dist/cli.js +242 -49
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|
|
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,
|
|
627
|
+
constructor(code, message, path10) {
|
|
533
628
|
super(message);
|
|
534
629
|
this.code = code;
|
|
535
|
-
this.path =
|
|
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
|
|
856
|
-
if (!
|
|
857
|
-
return { path:
|
|
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:
|
|
895
|
-
return { os:
|
|
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
|
|
914
|
-
import
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -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(
|
|
1140
|
+
<string>${escape(os4.homedir())}</string>
|
|
948
1141
|
</dict>
|
|
949
1142
|
</plist>
|
|
950
1143
|
`;
|
|
951
1144
|
}
|
|
952
1145
|
function plistPath() {
|
|
953
|
-
return
|
|
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
|
|
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(
|
|
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
|
|
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.
|
|
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:
|
|
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:
|
|
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
|
|
1188
|
-
import
|
|
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 =
|
|
1192
|
-
const paths = [home,
|
|
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(
|
|
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
|
|
1481
|
+
import os8 from "os";
|
|
1289
1482
|
|
|
1290
1483
|
// src/service/index.ts
|
|
1291
|
-
import
|
|
1484
|
+
import path9 from "path";
|
|
1292
1485
|
|
|
1293
1486
|
// src/paths.ts
|
|
1294
1487
|
import { realpathSync } from "fs";
|
|
1295
|
-
import
|
|
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
|
|
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
|
|
1313
|
-
import
|
|
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
|
|
1528
|
+
return path8.join(os7.homedir(), ".config", "systemd", "user", `${UNIT_NAME}.service`);
|
|
1336
1529
|
}
|
|
1337
|
-
async function
|
|
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(
|
|
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
|
|
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:
|
|
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
|
|
1569
|
+
async function install4() {
|
|
1377
1570
|
const platform = process.platform;
|
|
1378
1571
|
assertSupported(platform);
|
|
1379
1572
|
const opts = serviceFileOptions();
|
|
1380
|
-
if (platform === "darwin") await
|
|
1381
|
-
else await
|
|
1573
|
+
if (platform === "darwin") await install2(opts);
|
|
1574
|
+
else await install3(opts);
|
|
1382
1575
|
}
|
|
1383
|
-
async function
|
|
1576
|
+
async function uninstall4() {
|
|
1384
1577
|
const platform = process.platform;
|
|
1385
1578
|
assertSupported(platform);
|
|
1386
|
-
if (platform === "darwin") await
|
|
1387
|
-
else await
|
|
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(
|
|
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
|
|
1597
|
+
await install4();
|
|
1405
1598
|
console.log("Servi\xE7o instalado.");
|
|
1406
1599
|
return;
|
|
1407
1600
|
}
|
|
1408
1601
|
case "uninstall":
|
|
1409
|
-
await
|
|
1602
|
+
await uninstall4();
|
|
1410
1603
|
console.log("Servi\xE7o removido.");
|
|
1411
1604
|
return;
|
|
1412
1605
|
case "status": {
|
package/package.json
CHANGED