@termhub/agent 0.1.6 → 0.2.0
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 +64 -4
- 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() })),
|
|
@@ -490,14 +498,26 @@ function buildPasteScript(name) {
|
|
|
490
498
|
}
|
|
491
499
|
|
|
492
500
|
// ../../packages/machine-ops/dist/ai-credentials.js
|
|
501
|
+
var CREDENTIAL_SEPARATOR = "---termhub-credential---";
|
|
493
502
|
function credentialScript(provider) {
|
|
494
503
|
switch (provider) {
|
|
495
504
|
case "claude":
|
|
496
505
|
return [
|
|
497
|
-
`
|
|
498
|
-
|
|
506
|
+
`SEP=${shellQuote(CREDENTIAL_SEPARATOR)}`,
|
|
507
|
+
// 1. the on-disk copy (Linux, and macOS when the keychain entry is missing/disabled)
|
|
508
|
+
`if [ -f "$D/.credentials.json" ]; then cat "$D/.credentials.json" 2>/dev/null || true; fi`,
|
|
509
|
+
`printf '\\n%s\\n' "$SEP"`,
|
|
510
|
+
`if [ "$(uname -s 2>/dev/null)" = Darwin ]; then`,
|
|
511
|
+
` D=\${D%/}`,
|
|
512
|
+
// 2a. the per-config-dir keychain item (CLAUDE_CONFIG_DIR accounts)
|
|
513
|
+
` H=$(printf %s "$D" | shasum -a 256 2>/dev/null | cut -c1-8) || true`,
|
|
514
|
+
` if [ -n "$H" ]; then security find-generic-password -s "Claude Code-credentials-$H" -w 2>/dev/null || true; fi`,
|
|
515
|
+
` printf '\\n%s\\n' "$SEP"`,
|
|
516
|
+
// 2b. the unsuffixed item, only for the default dir (it belongs to a different account otherwise)
|
|
517
|
+
` if [ "$D" = "$HOME/.claude" ]; then security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null || true; fi`,
|
|
518
|
+
` printf '\\n%s\\n' "$SEP"`,
|
|
499
519
|
`fi`
|
|
500
|
-
].join("
|
|
520
|
+
].join("\n");
|
|
501
521
|
case "chatgpt":
|
|
502
522
|
return `if [ -f "$D/auth.json" ]; then cat "$D/auth.json"; fi`;
|
|
503
523
|
case "gemini":
|
|
@@ -1105,6 +1125,7 @@ async function pasteFile(params) {
|
|
|
1105
1125
|
}
|
|
1106
1126
|
|
|
1107
1127
|
// src/rpc/tmux.ts
|
|
1128
|
+
var ENTER_PAUSE_MS = 300;
|
|
1108
1129
|
function processFailure2(r) {
|
|
1109
1130
|
if (r.timedOut) return new RpcFailure("timeout", "tmux timed out");
|
|
1110
1131
|
if (r.error === "enoent") return new RpcFailure("no_tmux", "tmux not found");
|
|
@@ -1132,6 +1153,42 @@ async function capture(params) {
|
|
|
1132
1153
|
if (r.code !== 0) throw new RpcFailure("notfound", "session not found");
|
|
1133
1154
|
return { text: r.stdout };
|
|
1134
1155
|
}
|
|
1156
|
+
var pane = (session) => `=${session}:`;
|
|
1157
|
+
var why = (stderr, fallback) => stderr.trim().split("\n")[0] || fallback;
|
|
1158
|
+
async function ensure(params) {
|
|
1159
|
+
const has = await run(tmuxPath(), ["has-session", "-t", `=${params.session}`]);
|
|
1160
|
+
const hasFailure = processFailure2(has);
|
|
1161
|
+
if (hasFailure) throw hasFailure;
|
|
1162
|
+
if (has.code === 0) return { created: false };
|
|
1163
|
+
const made = await run(tmuxPath(), ["new-session", "-d", "-s", params.session, "-c", params.cwd]);
|
|
1164
|
+
const madeFailure = processFailure2(made);
|
|
1165
|
+
if (madeFailure) throw madeFailure;
|
|
1166
|
+
if (made.code !== 0) throw new RpcFailure("failed", why(made.stderr, "tmux new-session falhou"), params.cwd);
|
|
1167
|
+
return { created: true };
|
|
1168
|
+
}
|
|
1169
|
+
async function sendText(params) {
|
|
1170
|
+
if (params.text) {
|
|
1171
|
+
const typed = await run(tmuxPath(), ["send-keys", "-t", pane(params.session), "-l", "--", params.text]);
|
|
1172
|
+
const failure = processFailure2(typed);
|
|
1173
|
+
if (failure) throw failure;
|
|
1174
|
+
if (typed.code !== 0) throw new RpcFailure("notfound", why(typed.stderr, "session not found"));
|
|
1175
|
+
if (params.enter) await new Promise((r) => setTimeout(r, ENTER_PAUSE_MS));
|
|
1176
|
+
}
|
|
1177
|
+
if (params.enter) {
|
|
1178
|
+
const entered = await run(tmuxPath(), ["send-keys", "-t", pane(params.session), "Enter"]);
|
|
1179
|
+
const failure = processFailure2(entered);
|
|
1180
|
+
if (failure) throw failure;
|
|
1181
|
+
if (entered.code !== 0) throw new RpcFailure("notfound", why(entered.stderr, "session not found"));
|
|
1182
|
+
}
|
|
1183
|
+
return { sent: true };
|
|
1184
|
+
}
|
|
1185
|
+
async function sendKey(params) {
|
|
1186
|
+
const r = await run(tmuxPath(), ["send-keys", "-t", pane(params.session), params.key]);
|
|
1187
|
+
const failure = processFailure2(r);
|
|
1188
|
+
if (failure) throw failure;
|
|
1189
|
+
if (r.code !== 0) throw new RpcFailure("notfound", why(r.stderr, "session not found"));
|
|
1190
|
+
return { sent: true };
|
|
1191
|
+
}
|
|
1135
1192
|
|
|
1136
1193
|
// src/rpc/tools.ts
|
|
1137
1194
|
async function detect(_params) {
|
|
@@ -1147,6 +1204,9 @@ var handlers = {
|
|
|
1147
1204
|
"tmux.list": list2,
|
|
1148
1205
|
"tmux.kill": kill,
|
|
1149
1206
|
"tmux.capture": capture,
|
|
1207
|
+
"tmux.ensure": ensure,
|
|
1208
|
+
"tmux.sendText": sendText,
|
|
1209
|
+
"tmux.sendKey": sendKey,
|
|
1150
1210
|
"tools.detect": detect,
|
|
1151
1211
|
"hw.probe": probe,
|
|
1152
1212
|
"fs.list": list,
|
|
@@ -1242,7 +1302,7 @@ async function status(deps = {}) {
|
|
|
1242
1302
|
}
|
|
1243
1303
|
|
|
1244
1304
|
// src/version.ts
|
|
1245
|
-
var AGENT_VERSION = "0.
|
|
1305
|
+
var AGENT_VERSION = "0.2.0";
|
|
1246
1306
|
|
|
1247
1307
|
// src/run.ts
|
|
1248
1308
|
function detectOs(platform = process.platform) {
|
package/package.json
CHANGED