@termhub/agent 0.1.4 → 0.1.5
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 +79 -38
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -63,8 +63,13 @@ var RPC = {
|
|
|
63
63
|
"ai.credential": def(z.object({ provider: aiProvider, config_dir: machinePath.nullable() }), z.object({ stdout: z.string() }), 1e4),
|
|
64
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
65
|
/** Monitor hooks (see @termhub/machine-ops hooks.ts): the agent writes the script, env and config entries under its own $HOME. */
|
|
66
|
-
|
|
67
|
-
"hooks.
|
|
66
|
+
/** `claude_dirs`: Claude config dirs besides ~/.claude (accounts with CLAUDE_CONFIG_DIR), hooked when they exist; since agent 0.1.5. */
|
|
67
|
+
"hooks.install": def(z.object({
|
|
68
|
+
hooks_url: z.string().min(1).max(2048).regex(/^https?:\/\/[^\s'"]+$/),
|
|
69
|
+
token: z.string().min(1).max(256).regex(/^[A-Za-z0-9_-]+$/),
|
|
70
|
+
claude_dirs: z.array(machinePath).max(16).optional()
|
|
71
|
+
}), 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)
|
|
68
73
|
};
|
|
69
74
|
var RPC_METHODS = Object.keys(RPC);
|
|
70
75
|
var rpcMethod = z.enum(RPC_METHODS);
|
|
@@ -534,6 +539,23 @@ function ptyEnv(base, shell) {
|
|
|
534
539
|
var HOOK_SCRIPT_REL = ".termhub/bin/termhub-hook";
|
|
535
540
|
var HOOK_ENV_REL = ".termhub/hook.env";
|
|
536
541
|
var HOOK_MARK = "termhub-hook";
|
|
542
|
+
var CLAUDE_DEFAULT_DIR = "~/.claude";
|
|
543
|
+
function claudeConfigDirs(accountDirs) {
|
|
544
|
+
const out = [CLAUDE_DEFAULT_DIR];
|
|
545
|
+
for (const raw of accountDirs) {
|
|
546
|
+
let d = (raw ?? "").trim().replace(/\/+$/, "");
|
|
547
|
+
if (!d || d === "~" || /[\0\n\r]/.test(d))
|
|
548
|
+
continue;
|
|
549
|
+
if (!d.startsWith("~/") && !d.startsWith("/"))
|
|
550
|
+
d = `~/${d}`;
|
|
551
|
+
if (!out.includes(d))
|
|
552
|
+
out.push(d);
|
|
553
|
+
}
|
|
554
|
+
return out;
|
|
555
|
+
}
|
|
556
|
+
function expandHome(dir, home) {
|
|
557
|
+
return dir.startsWith("~/") ? `${home}/${dir.slice(2)}` : dir;
|
|
558
|
+
}
|
|
537
559
|
var CLAUDE_HOOK_EVENTS = ["SessionStart", "UserPromptSubmit", "Notification", "Stop", "SessionEnd"];
|
|
538
560
|
var HOOK_SCRIPT = `#!/bin/sh
|
|
539
561
|
# termhub monitor hook \u2014 installed by termhub; forwards Claude Code / Codex hook events to
|
|
@@ -730,7 +752,7 @@ function createDispatcher(deps) {
|
|
|
730
752
|
// src/pty.ts
|
|
731
753
|
import fs2 from "fs";
|
|
732
754
|
import path2 from "path";
|
|
733
|
-
function
|
|
755
|
+
function expandHome2(rawCwd, home) {
|
|
734
756
|
if (rawCwd === "~") return home;
|
|
735
757
|
if (rawCwd.startsWith("~/")) return path2.join(home, rawCwd.slice(2));
|
|
736
758
|
return rawCwd;
|
|
@@ -744,7 +766,7 @@ function existsDir(p) {
|
|
|
744
766
|
}
|
|
745
767
|
function resolveCwd(rawCwd) {
|
|
746
768
|
const home = process.env.HOME || "/";
|
|
747
|
-
const expanded =
|
|
769
|
+
const expanded = expandHome2(rawCwd, home);
|
|
748
770
|
return existsDir(expanded) ? expanded : home;
|
|
749
771
|
}
|
|
750
772
|
function isEnoent(err) {
|
|
@@ -935,7 +957,6 @@ async function mkdir(params) {
|
|
|
935
957
|
import { chmod, mkdir as mkdir2, readFile, rename, rm, stat, writeFile } from "fs/promises";
|
|
936
958
|
import os3 from "os";
|
|
937
959
|
import path4 from "path";
|
|
938
|
-
var CLAUDE_SETTINGS_REL = ".claude/settings.json";
|
|
939
960
|
var CODEX_DIR_REL = ".codex";
|
|
940
961
|
var CODEX_CONFIG_REL = ".codex/config.toml";
|
|
941
962
|
var isEnoent2 = (err) => err?.code === "ENOENT";
|
|
@@ -960,65 +981,85 @@ async function writeAtomic(file, body, mode) {
|
|
|
960
981
|
await chmod(tmp, mode);
|
|
961
982
|
await rename(tmp, file);
|
|
962
983
|
}
|
|
963
|
-
|
|
984
|
+
var relPath = (shown) => shown.startsWith("~/") ? shown.slice(2) : shown;
|
|
985
|
+
function fsFailure(err, shown) {
|
|
964
986
|
const code = err?.code;
|
|
965
|
-
if (code === "EACCES" || code === "EPERM") return new RpcFailure("eperm", `sem permiss\xE3o em
|
|
966
|
-
return new RpcFailure("failed", `n\xE3o foi poss\xEDvel escrever
|
|
987
|
+
if (code === "EACCES" || code === "EPERM") return new RpcFailure("eperm", `sem permiss\xE3o em ${shown}`, relPath(shown));
|
|
988
|
+
return new RpcFailure("failed", `n\xE3o foi poss\xEDvel escrever ${shown}: ${err instanceof Error ? err.message : String(err)}`, relPath(shown));
|
|
989
|
+
}
|
|
990
|
+
async function claudeTargets(dirs, home) {
|
|
991
|
+
const out = [];
|
|
992
|
+
for (const d of claudeConfigDirs(dirs ?? [])) {
|
|
993
|
+
const dir = expandHome(d, home);
|
|
994
|
+
if (d !== CLAUDE_DEFAULT_DIR && !await isDir(dir)) continue;
|
|
995
|
+
out.push({ dir, file: path4.join(dir, "settings.json"), shown: `${d}/settings.json` });
|
|
996
|
+
}
|
|
997
|
+
return out;
|
|
967
998
|
}
|
|
968
999
|
async function install(params, home = os3.homedir()) {
|
|
969
1000
|
const scriptPath = path4.join(home, HOOK_SCRIPT_REL);
|
|
970
|
-
const claudeFile = path4.join(home, CLAUDE_SETTINGS_REL);
|
|
971
1001
|
const codexFile = path4.join(home, CODEX_CONFIG_REL);
|
|
972
|
-
const
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1002
|
+
const targets = await claudeTargets(params.claude_dirs, home);
|
|
1003
|
+
const merged = [];
|
|
1004
|
+
for (const target of targets) {
|
|
1005
|
+
try {
|
|
1006
|
+
merged.push({ target, body: mergeClaudeSettings(await readOrEmpty(target.file), scriptPath) });
|
|
1007
|
+
} catch (err) {
|
|
1008
|
+
const message = err instanceof SyntaxError || err instanceof Error && err.message.includes("n\xE3o \xE9 um objeto JSON") ? `${target.shown} n\xE3o \xE9 JSON v\xE1lido` : err instanceof Error ? err.message : String(err);
|
|
1009
|
+
throw new RpcFailure("failed", message, relPath(target.shown));
|
|
1010
|
+
}
|
|
978
1011
|
}
|
|
979
1012
|
const hasCodex = await isDir(path4.join(home, CODEX_DIR_REL));
|
|
980
1013
|
const mergedCodex = hasCodex ? mergeCodexConfig(await readOrEmpty(codexFile), scriptPath) : null;
|
|
981
|
-
let current = HOOK_SCRIPT_REL
|
|
1014
|
+
let current = `~/${HOOK_SCRIPT_REL}`;
|
|
982
1015
|
try {
|
|
983
1016
|
await mkdir2(path4.dirname(scriptPath), { recursive: true });
|
|
984
|
-
|
|
985
|
-
current = HOOK_ENV_REL;
|
|
1017
|
+
current = `~/${HOOK_ENV_REL}`;
|
|
986
1018
|
await writeAtomic(path4.join(home, HOOK_ENV_REL), hookEnvFile(params.hooks_url, params.token), 384);
|
|
987
|
-
current = HOOK_SCRIPT_REL
|
|
1019
|
+
current = `~/${HOOK_SCRIPT_REL}`;
|
|
988
1020
|
await writeAtomic(scriptPath, HOOK_SCRIPT, 493);
|
|
989
|
-
|
|
990
|
-
|
|
1021
|
+
for (const { target, body } of merged) {
|
|
1022
|
+
current = target.shown;
|
|
1023
|
+
await mkdir2(target.dir, { recursive: true });
|
|
1024
|
+
await writeAtomic(target.file, body, 420);
|
|
1025
|
+
}
|
|
991
1026
|
if (mergedCodex !== null) {
|
|
992
|
-
current = CODEX_CONFIG_REL
|
|
1027
|
+
current = `~/${CODEX_CONFIG_REL}`;
|
|
993
1028
|
await writeAtomic(codexFile, mergedCodex, 420);
|
|
994
1029
|
}
|
|
995
1030
|
} catch (err) {
|
|
996
1031
|
throw fsFailure(err, current);
|
|
997
1032
|
}
|
|
998
|
-
return {
|
|
1033
|
+
return {
|
|
1034
|
+
home,
|
|
1035
|
+
claude: "installed",
|
|
1036
|
+
codex: mergedCodex !== null ? "installed" : "skipped",
|
|
1037
|
+
claude_dirs: merged.map(({ target }) => target.shown.replace(/\/settings\.json$/, ""))
|
|
1038
|
+
};
|
|
999
1039
|
}
|
|
1000
|
-
async function uninstall(
|
|
1001
|
-
const claudeFile = path4.join(home, CLAUDE_SETTINGS_REL);
|
|
1040
|
+
async function uninstall(params, home = os3.homedir()) {
|
|
1002
1041
|
const codexFile = path4.join(home, CODEX_CONFIG_REL);
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1042
|
+
const stripped = [];
|
|
1043
|
+
for (const target of await claudeTargets(params.claude_dirs, home)) {
|
|
1044
|
+
const current2 = await readOrEmpty(target.file);
|
|
1045
|
+
try {
|
|
1046
|
+
const body = current2.trim() ? stripClaudeSettings(current2) : current2;
|
|
1047
|
+
if (body !== current2) stripped.push({ target, body });
|
|
1048
|
+
} catch {
|
|
1049
|
+
}
|
|
1009
1050
|
}
|
|
1010
1051
|
const codexConfig = await isDir(path4.join(home, CODEX_DIR_REL)) ? await readOrEmpty(codexFile) : "";
|
|
1011
|
-
let current = HOOK_SCRIPT_REL
|
|
1052
|
+
let current = `~/${HOOK_SCRIPT_REL}`;
|
|
1012
1053
|
try {
|
|
1013
1054
|
await rm(path4.join(home, HOOK_SCRIPT_REL), { force: true });
|
|
1014
|
-
current = HOOK_ENV_REL
|
|
1055
|
+
current = `~/${HOOK_ENV_REL}`;
|
|
1015
1056
|
await rm(path4.join(home, HOOK_ENV_REL), { force: true });
|
|
1016
|
-
|
|
1017
|
-
current =
|
|
1018
|
-
await writeAtomic(
|
|
1057
|
+
for (const { target, body } of stripped) {
|
|
1058
|
+
current = target.shown;
|
|
1059
|
+
await writeAtomic(target.file, body, 420);
|
|
1019
1060
|
}
|
|
1020
1061
|
if (codexConfig.includes(HOOK_MARK)) {
|
|
1021
|
-
current = CODEX_CONFIG_REL
|
|
1062
|
+
current = `~/${CODEX_CONFIG_REL}`;
|
|
1022
1063
|
await writeAtomic(codexFile, stripCodexConfig(codexConfig), 420);
|
|
1023
1064
|
}
|
|
1024
1065
|
} catch (err) {
|
|
@@ -1186,7 +1227,7 @@ async function status(deps = {}) {
|
|
|
1186
1227
|
}
|
|
1187
1228
|
|
|
1188
1229
|
// src/version.ts
|
|
1189
|
-
var AGENT_VERSION = "0.1.
|
|
1230
|
+
var AGENT_VERSION = "0.1.5";
|
|
1190
1231
|
|
|
1191
1232
|
// src/run.ts
|
|
1192
1233
|
function detectOs(platform = process.platform) {
|
package/package.json
CHANGED