@termhub/agent 0.1.1 → 0.1.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 +71 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,3 +52,10 @@ macOS's TCC (Transparency, Consent and Control) can block the agent from listing
|
|
|
52
52
|
special privileges. If `doctor` or `service install` reports this, grant the agent's Node binary
|
|
53
53
|
Full Disk Access: **Ajustes → Privacidade e Segurança → Acesso Total ao Disco**, then add the
|
|
54
54
|
`node` binary path `doctor` printed.
|
|
55
|
+
|
|
56
|
+
## Troubleshooting
|
|
57
|
+
|
|
58
|
+
- **Terminals fail to open with `posix_spawnp failed.`** (visible in `~/.termhub/agent.log`): node-pty's
|
|
59
|
+
prebuilt `spawn-helper` lost its exec bit — npm 11 skips the postinstall that sets it unless the
|
|
60
|
+
package is approved. The agent repairs this itself on startup and in `termhub-agent doctor`; if the
|
|
61
|
+
file is not writable by your user, `doctor` prints the exact `chmod +x` to run.
|
package/dist/cli.js
CHANGED
|
@@ -529,10 +529,10 @@ function ptyEnv(base, shell) {
|
|
|
529
529
|
// src/exec.ts
|
|
530
530
|
var DEFAULT_TIMEOUT_MS = 8e3;
|
|
531
531
|
var RpcFailure = class extends Error {
|
|
532
|
-
constructor(code, message,
|
|
532
|
+
constructor(code, message, path9) {
|
|
533
533
|
super(message);
|
|
534
534
|
this.code = code;
|
|
535
|
-
this.path =
|
|
535
|
+
this.path = path9;
|
|
536
536
|
}
|
|
537
537
|
code;
|
|
538
538
|
path;
|
|
@@ -771,6 +771,37 @@ function createPtyManager(deps) {
|
|
|
771
771
|
};
|
|
772
772
|
}
|
|
773
773
|
|
|
774
|
+
// src/pty-health.ts
|
|
775
|
+
import fs3 from "fs";
|
|
776
|
+
import path3 from "path";
|
|
777
|
+
import { createRequire } from "module";
|
|
778
|
+
function findSpawnHelper(resolveFrom = import.meta.url) {
|
|
779
|
+
let pkgJson;
|
|
780
|
+
try {
|
|
781
|
+
pkgJson = createRequire(resolveFrom).resolve("node-pty/package.json");
|
|
782
|
+
} catch {
|
|
783
|
+
return null;
|
|
784
|
+
}
|
|
785
|
+
const root = path3.dirname(pkgJson);
|
|
786
|
+
const candidates = [
|
|
787
|
+
path3.join(root, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper"),
|
|
788
|
+
path3.join(root, "build", "Release", "spawn-helper")
|
|
789
|
+
];
|
|
790
|
+
return candidates.find((p) => fs3.existsSync(p)) ?? null;
|
|
791
|
+
}
|
|
792
|
+
var EXEC_BITS = 73;
|
|
793
|
+
function ensureSpawnHelperExecutable(helper = findSpawnHelper()) {
|
|
794
|
+
if (!helper) return { path: null, executable: true, repaired: false };
|
|
795
|
+
try {
|
|
796
|
+
const mode = fs3.statSync(helper).mode;
|
|
797
|
+
if (mode & EXEC_BITS) return { path: helper, executable: true, repaired: false };
|
|
798
|
+
fs3.chmodSync(helper, (mode | 493) & 4095);
|
|
799
|
+
return { path: helper, executable: true, repaired: true };
|
|
800
|
+
} catch (err) {
|
|
801
|
+
return { path: helper, executable: false, repaired: false, error: err instanceof Error ? err.message : String(err) };
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
774
805
|
// src/rpc/ai.ts
|
|
775
806
|
async function credential(params) {
|
|
776
807
|
let prefix;
|
|
@@ -821,9 +852,9 @@ async function pasteFile(params) {
|
|
|
821
852
|
if (r.timedOut) throw new RpcFailure("timeout", "file.paste timed out");
|
|
822
853
|
if (r.code !== 0) throw new RpcFailure("internal", `file.paste exited with code ${r.code}`);
|
|
823
854
|
const lines = r.stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
824
|
-
const
|
|
825
|
-
if (!
|
|
826
|
-
return { path:
|
|
855
|
+
const path9 = lines[lines.length - 1] ?? "";
|
|
856
|
+
if (!path9.startsWith("/")) throw new RpcFailure("internal", "unexpected paste output");
|
|
857
|
+
return { path: path9 };
|
|
827
858
|
}
|
|
828
859
|
|
|
829
860
|
// src/rpc/tmux.ts
|
|
@@ -878,9 +909,9 @@ var handlers = {
|
|
|
878
909
|
};
|
|
879
910
|
|
|
880
911
|
// src/service/launchd.ts
|
|
881
|
-
import
|
|
912
|
+
import fs4 from "fs";
|
|
882
913
|
import os3 from "os";
|
|
883
|
-
import
|
|
914
|
+
import path4 from "path";
|
|
884
915
|
var LABEL = "dev.termhub.agent";
|
|
885
916
|
function renderPlist({ label, node, script, logPath }) {
|
|
886
917
|
const escape = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -919,7 +950,7 @@ function renderPlist({ label, node, script, logPath }) {
|
|
|
919
950
|
`;
|
|
920
951
|
}
|
|
921
952
|
function plistPath() {
|
|
922
|
-
return
|
|
953
|
+
return path4.join(os3.homedir(), "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
923
954
|
}
|
|
924
955
|
function gui() {
|
|
925
956
|
return `gui/${process.getuid ? process.getuid() : 0}`;
|
|
@@ -928,8 +959,8 @@ async function install(opts, deps = {}) {
|
|
|
928
959
|
const runFn = deps.run ?? run;
|
|
929
960
|
const file = plistPath();
|
|
930
961
|
const plist = renderPlist({ label: LABEL, node: opts.node, script: opts.script, logPath: opts.logPath });
|
|
931
|
-
|
|
932
|
-
|
|
962
|
+
fs4.mkdirSync(path4.dirname(file), { recursive: true });
|
|
963
|
+
fs4.writeFileSync(file, plist, "utf8");
|
|
933
964
|
await runFn("launchctl", ["bootout", gui(), file]);
|
|
934
965
|
const result = await runFn("launchctl", ["bootstrap", gui(), file]);
|
|
935
966
|
if (result.code !== 0) {
|
|
@@ -941,7 +972,7 @@ async function uninstall(deps = {}) {
|
|
|
941
972
|
const file = plistPath();
|
|
942
973
|
await runFn("launchctl", ["bootout", gui(), file]);
|
|
943
974
|
try {
|
|
944
|
-
|
|
975
|
+
fs4.unlinkSync(file);
|
|
945
976
|
} catch (err) {
|
|
946
977
|
if (err.code !== "ENOENT") throw err;
|
|
947
978
|
}
|
|
@@ -962,7 +993,7 @@ async function status(deps = {}) {
|
|
|
962
993
|
}
|
|
963
994
|
|
|
964
995
|
// src/version.ts
|
|
965
|
-
var AGENT_VERSION = "0.1.
|
|
996
|
+
var AGENT_VERSION = "0.1.2";
|
|
966
997
|
|
|
967
998
|
// src/run.ts
|
|
968
999
|
function detectOs(platform = process.platform) {
|
|
@@ -1040,6 +1071,9 @@ async function runAgent(config, opts) {
|
|
|
1040
1071
|
process.exit(1);
|
|
1041
1072
|
}
|
|
1042
1073
|
const hello = await buildHello(osName);
|
|
1074
|
+
const helper = ensureSpawnHelperExecutable();
|
|
1075
|
+
if (helper.repaired) opts.log("spawn-helper exec bit repaired", { path: helper.path });
|
|
1076
|
+
else if (!helper.executable) opts.log("spawn-helper is not executable and could not be fixed", { path: helper.path, error: helper.error });
|
|
1043
1077
|
const pty = createPtyManager({ log: opts.log });
|
|
1044
1078
|
const dispatch = createDispatcher({ handlers, pty, log: opts.log });
|
|
1045
1079
|
try {
|
|
@@ -1149,17 +1183,17 @@ function disconnectCommand() {
|
|
|
1149
1183
|
}
|
|
1150
1184
|
|
|
1151
1185
|
// src/doctor.ts
|
|
1152
|
-
import
|
|
1186
|
+
import fs5 from "fs";
|
|
1153
1187
|
import os5 from "os";
|
|
1154
|
-
import
|
|
1188
|
+
import path5 from "path";
|
|
1155
1189
|
var SERVER_CHECK_TIMEOUT_MS = 5e3;
|
|
1156
1190
|
function defaultDoctorPaths() {
|
|
1157
1191
|
const home = os5.homedir();
|
|
1158
|
-
const paths = [home,
|
|
1192
|
+
const paths = [home, path5.join(home, "Documents"), path5.join(home, "Desktop")];
|
|
1159
1193
|
if (process.platform === "darwin") {
|
|
1160
1194
|
try {
|
|
1161
|
-
for (const entry of
|
|
1162
|
-
if (entry.isDirectory()) paths.push(
|
|
1195
|
+
for (const entry of fs5.readdirSync("/Volumes", { withFileTypes: true })) {
|
|
1196
|
+
if (entry.isDirectory()) paths.push(path5.join("/Volumes", entry.name));
|
|
1163
1197
|
}
|
|
1164
1198
|
} catch {
|
|
1165
1199
|
}
|
|
@@ -1172,7 +1206,7 @@ function pathErrorCode(err) {
|
|
|
1172
1206
|
if (e?.code) return e.code;
|
|
1173
1207
|
return err instanceof Error ? err.message : String(err);
|
|
1174
1208
|
}
|
|
1175
|
-
function checkPathAccess(p, fsImpl =
|
|
1209
|
+
function checkPathAccess(p, fsImpl = fs5) {
|
|
1176
1210
|
try {
|
|
1177
1211
|
fsImpl.readdirSync(p);
|
|
1178
1212
|
return { path: p, ok: true };
|
|
@@ -1184,7 +1218,7 @@ function fullDiskAccessNote(execPath = process.execPath) {
|
|
|
1184
1218
|
return `Conceda Acesso Total ao Disco a ${execPath} em Ajustes \u2192 Privacidade e Seguran\xE7a \u2192 Acesso Total ao Disco`;
|
|
1185
1219
|
}
|
|
1186
1220
|
async function runDoctor(paths, deps = {}) {
|
|
1187
|
-
const fsImpl = deps.fs ??
|
|
1221
|
+
const fsImpl = deps.fs ?? fs5;
|
|
1188
1222
|
const connect = deps.connect ?? checkServerConnection;
|
|
1189
1223
|
const config = readConfig();
|
|
1190
1224
|
const configReport = { ok: config !== null, path: configPath() };
|
|
@@ -1199,7 +1233,9 @@ async function runDoctor(paths, deps = {}) {
|
|
|
1199
1233
|
nodePty = { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
1200
1234
|
}
|
|
1201
1235
|
const pathsReport = paths.map((p) => checkPathAccess(p, fsImpl));
|
|
1202
|
-
|
|
1236
|
+
const helper = ensureSpawnHelperExecutable();
|
|
1237
|
+
const spawnHelper = { ok: helper.executable, path: helper.path, repaired: helper.repaired, ...helper.error ? { error: helper.error } : {} };
|
|
1238
|
+
return { config: configReport, server, tmux, nodePty, spawnHelper, paths: pathsReport };
|
|
1203
1239
|
}
|
|
1204
1240
|
function mark(ok) {
|
|
1205
1241
|
return ok ? "\u2713" : "\u2717";
|
|
@@ -1212,6 +1248,11 @@ function formatDoctor(report, deps = {}) {
|
|
|
1212
1248
|
lines.push(`${mark(report.server.ok)} Servidor${report.server.ok ? "" : report.server.error ? `: ${report.server.error}` : ""}`);
|
|
1213
1249
|
lines.push(`${mark(report.tmux.ok)} tmux${report.tmux.path ? ` (${report.tmux.path})` : ""}`);
|
|
1214
1250
|
lines.push(`${mark(report.nodePty.ok)} node-pty${report.nodePty.ok ? "" : report.nodePty.error ? `: ${report.nodePty.error}` : ""}`);
|
|
1251
|
+
const sh2 = report.spawnHelper;
|
|
1252
|
+
if (sh2.path) {
|
|
1253
|
+
lines.push(`${mark(sh2.ok)} spawn-helper do node-pty ${sh2.ok ? sh2.repaired ? "(permiss\xE3o de execu\xE7\xE3o corrigida agora)" : "execut\xE1vel" : `sem permiss\xE3o de execu\xE7\xE3o${sh2.error ? `: ${sh2.error}` : ""}`}`);
|
|
1254
|
+
if (!sh2.ok) lines.push(` \u2192 rode: chmod +x "${sh2.path}"`);
|
|
1255
|
+
}
|
|
1215
1256
|
for (const p of report.paths) {
|
|
1216
1257
|
lines.push(`${mark(p.ok)} ${p.path}${p.ok ? "" : p.error ? `: ${p.error}` : ""}`);
|
|
1217
1258
|
if (!p.ok && p.error === "eperm" && platform === "darwin") {
|
|
@@ -1247,18 +1288,18 @@ async function runCommand(log2) {
|
|
|
1247
1288
|
import os7 from "os";
|
|
1248
1289
|
|
|
1249
1290
|
// src/service/index.ts
|
|
1250
|
-
import
|
|
1291
|
+
import path8 from "path";
|
|
1251
1292
|
|
|
1252
1293
|
// src/paths.ts
|
|
1253
1294
|
import { realpathSync } from "fs";
|
|
1254
|
-
import
|
|
1295
|
+
import path6 from "path";
|
|
1255
1296
|
import { pathToFileURL } from "url";
|
|
1256
1297
|
function resolveScriptPath(argv1) {
|
|
1257
1298
|
if (!argv1) return "";
|
|
1258
1299
|
try {
|
|
1259
1300
|
return realpathSync(argv1);
|
|
1260
1301
|
} catch {
|
|
1261
|
-
return
|
|
1302
|
+
return path6.resolve(argv1);
|
|
1262
1303
|
}
|
|
1263
1304
|
}
|
|
1264
1305
|
function isMainModule(importMetaUrl, argv1) {
|
|
@@ -1267,9 +1308,9 @@ function isMainModule(importMetaUrl, argv1) {
|
|
|
1267
1308
|
}
|
|
1268
1309
|
|
|
1269
1310
|
// src/service/systemd.ts
|
|
1270
|
-
import
|
|
1311
|
+
import fs6 from "fs";
|
|
1271
1312
|
import os6 from "os";
|
|
1272
|
-
import
|
|
1313
|
+
import path7 from "path";
|
|
1273
1314
|
var UNIT_NAME = "termhub-agent";
|
|
1274
1315
|
function renderUnit({ node, script, logPath }) {
|
|
1275
1316
|
const lines = [
|
|
@@ -1291,14 +1332,14 @@ function renderUnit({ node, script, logPath }) {
|
|
|
1291
1332
|
return lines.join("\n");
|
|
1292
1333
|
}
|
|
1293
1334
|
function unitPath() {
|
|
1294
|
-
return
|
|
1335
|
+
return path7.join(os6.homedir(), ".config", "systemd", "user", `${UNIT_NAME}.service`);
|
|
1295
1336
|
}
|
|
1296
1337
|
async function install2(opts, deps = {}) {
|
|
1297
1338
|
const runFn = deps.run ?? run;
|
|
1298
1339
|
const file = unitPath();
|
|
1299
1340
|
const unit = renderUnit({ node: opts.node, script: opts.script, logPath: opts.logPath });
|
|
1300
|
-
|
|
1301
|
-
|
|
1341
|
+
fs6.mkdirSync(path7.dirname(file), { recursive: true });
|
|
1342
|
+
fs6.writeFileSync(file, unit, "utf8");
|
|
1302
1343
|
const reload = await runFn("systemctl", ["--user", "daemon-reload"]);
|
|
1303
1344
|
if (reload.code !== 0) throw new Error(`systemctl daemon-reload failed (code ${reload.code}): ${reload.stderr.trim()}`);
|
|
1304
1345
|
const enable = await runFn("systemctl", ["--user", "enable", "--now", UNIT_NAME]);
|
|
@@ -1309,7 +1350,7 @@ async function uninstall2(deps = {}) {
|
|
|
1309
1350
|
const runFn = deps.run ?? run;
|
|
1310
1351
|
await runFn("systemctl", ["--user", "disable", "--now", UNIT_NAME]);
|
|
1311
1352
|
try {
|
|
1312
|
-
|
|
1353
|
+
fs6.unlinkSync(unitPath());
|
|
1313
1354
|
} catch (err) {
|
|
1314
1355
|
if (err.code !== "ENOENT") throw err;
|
|
1315
1356
|
}
|
|
@@ -1326,7 +1367,7 @@ function serviceFileOptions() {
|
|
|
1326
1367
|
return {
|
|
1327
1368
|
node: process.execPath,
|
|
1328
1369
|
script: resolveScriptPath(process.argv[1]),
|
|
1329
|
-
logPath:
|
|
1370
|
+
logPath: path8.join(agentHome(), "agent.log")
|
|
1330
1371
|
};
|
|
1331
1372
|
}
|
|
1332
1373
|
function assertSupported(platform) {
|
package/package.json
CHANGED