refactor(cli): drop tmux helpers and update help copy

This commit is contained in:
Peter Steinberger
2025-12-08 12:43:13 +01:00
parent bce84376d3
commit 17fa2f4053
9 changed files with 23 additions and 229 deletions

View File

@@ -6,7 +6,6 @@ const loginWeb = vi.fn();
const monitorWebProvider = vi.fn();
const logWebSelfId = vi.fn();
const waitForever = vi.fn();
const spawnRelayTmux = vi.fn().mockResolvedValue("clawdis-relay");
const monitorTelegramProvider = vi.fn();
const runtime = {
@@ -31,7 +30,6 @@ vi.mock("./deps.js", () => ({
createDefaultDeps: () => ({ waitForever }),
logWebSelfId,
}));
vi.mock("./relay_tmux.js", () => ({ spawnRelayTmux }));
const { buildProgram } = await import("./program.js");
@@ -80,16 +78,6 @@ describe("cli program", () => {
runtime.exit = originalExit;
});
it("runs relay heartbeat tmux helper", async () => {
const program = buildProgram();
await program.parseAsync(["relay:heartbeat:tmux"], { from: "user" });
const shouldAttach = Boolean(process.stdout.isTTY);
expect(spawnRelayTmux).toHaveBeenCalledWith(
"pnpm clawdis relay --verbose --heartbeat-now",
shouldAttach,
);
});
it("runs telegram relay when token set", async () => {
const program = buildProgram();
const prev = process.env.TELEGRAM_BOT_TOKEN;

View File

@@ -25,17 +25,16 @@ import {
resolveReconnectPolicy,
} from "../web/reconnect.js";
import { createDefaultDeps, logWebSelfId } from "./deps.js";
import { spawnRelayTmux } from "./relay_tmux.js";
export function buildProgram() {
const program = new Command();
const PROGRAM_VERSION = VERSION;
const TAGLINE =
"Send, receive, and auto-reply on WhatsApp—Baileys (web) only.";
"Send, receive, and auto-reply on WhatsApp (web) and Telegram (bot).";
program
.name("clawdis")
.description("WhatsApp relay CLI (WhatsApp Web session only)")
.description("Messaging relay CLI for WhatsApp Web and Telegram Bot API")
.version(PROGRAM_VERSION);
const formatIntroLine = (version: string, rich = true) => {
@@ -91,7 +90,11 @@ export function buildProgram() {
],
[
'clawdis agent --to +15555550123 --message "Run summary" --deliver',
"Talk directly to the agent using the same session handling; optionally send the reply.",
"Talk directly to the agent using the same session handling; optionally send the WhatsApp reply.",
],
[
'clawdis send --provider telegram --to @mychat --message "Hi"',
"Send via your Telegram bot.",
],
] as const;
@@ -176,7 +179,7 @@ Examples:
program
.command("agent")
.description(
"Talk directly to the configured agent (no WhatsApp send, reuses sessions)",
"Talk directly to the configured agent (no chat send; optional WhatsApp delivery)",
)
.requiredOption("-m, --message <text>", "Message body for the agent")
.option(
@@ -312,7 +315,7 @@ Examples:
program
.command("heartbeat")
.description("Trigger a heartbeat or manual send once (web only, no tmux)")
.description("Trigger a heartbeat or manual send once (web provider only)")
.option("--to <number>", "Override target E.164; defaults to allowFrom[0]")
.option(
"--session-id <id>",
@@ -396,7 +399,7 @@ Examples:
program
.command("relay")
.description("Auto-reply to inbound messages (web only)")
.description("Auto-reply to inbound WhatsApp messages (web provider)")
.option(
"--web-heartbeat <seconds>",
"Heartbeat interval for web relay health logs (seconds)",
@@ -528,9 +531,7 @@ Examples:
program
.command("relay:heartbeat")
.description(
"Run relay with an immediate heartbeat (no tmux); requires web provider",
)
.description("Run relay with an immediate heartbeat; requires web provider")
.option("--verbose", "Verbose logging", false)
.action(async (opts) => {
setVerbose(Boolean(opts.verbose));
@@ -737,88 +738,6 @@ Shows token usage per session when the agent reports it; set inbound.reply.agent
);
});
program
.command("relay:tmux")
.description(
"Run relay --verbose inside tmux (session clawdis-relay), restarting if already running, then attach",
)
.action(async () => {
try {
const shouldAttach = Boolean(process.stdout.isTTY);
const session = await spawnRelayTmux(
"pnpm clawdis relay --verbose",
shouldAttach,
);
defaultRuntime.log(
info(
shouldAttach
? `tmux session started and attached: ${session} (pane running "pnpm clawdis relay --verbose")`
: `tmux session started: ${session} (pane running "pnpm clawdis relay --verbose"); attach manually with "tmux attach -t ${session}"`,
),
);
} catch (err) {
defaultRuntime.error(
danger(`Failed to start relay tmux session: ${String(err)}`),
);
defaultRuntime.exit(1);
}
});
program
.command("relay:tmux:attach")
.description(
"Attach to the existing clawdis-relay tmux session (no restart)",
)
.action(async () => {
try {
if (!process.stdout.isTTY) {
defaultRuntime.error(
danger(
"Cannot attach: stdout is not a TTY. Run this in a terminal or use 'tmux attach -t clawdis-relay' manually.",
),
);
defaultRuntime.exit(1);
return;
}
await spawnRelayTmux("pnpm clawdis relay --verbose", true, false);
defaultRuntime.log(info("Attached to clawdis-relay session."));
} catch (err) {
defaultRuntime.error(
danger(`Failed to attach to clawdis-relay: ${String(err)}`),
);
defaultRuntime.exit(1);
}
});
program
.command("relay:heartbeat:tmux")
.description(
"Run relay --verbose with an immediate heartbeat inside tmux (session clawdis-relay), then attach",
)
.action(async () => {
try {
const shouldAttach = Boolean(process.stdout.isTTY);
const session = await spawnRelayTmux(
"pnpm clawdis relay --verbose --heartbeat-now",
shouldAttach,
);
defaultRuntime.log(
info(
shouldAttach
? `tmux session started and attached: ${session} (pane running "pnpm clawdis relay --verbose --heartbeat-now")`
: `tmux session started: ${session} (pane running "pnpm clawdis relay --verbose --heartbeat-now"); attach manually with "tmux attach -t ${session}"`,
),
);
} catch (err) {
defaultRuntime.error(
danger(
`Failed to start relay tmux session with heartbeat: ${String(err)}`,
),
);
defaultRuntime.exit(1);
}
});
program
.command("webchat")
.description("Start or query the loopback-only web chat server")

View File

@@ -1,47 +0,0 @@
import { EventEmitter } from "node:events";
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("node:child_process", () => {
const spawn = vi.fn((_cmd: string, _args: string[]) => {
const proc = new EventEmitter() as EventEmitter & {
kill: ReturnType<typeof vi.fn>;
};
queueMicrotask(() => {
proc.emit("exit", 0);
});
proc.kill = vi.fn();
return proc;
});
return { spawn };
});
const { spawnRelayTmux } = await import("./relay_tmux.js");
const { spawn } = await import("node:child_process");
describe("spawnRelayTmux", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("kills old session, starts new one, and attaches", async () => {
const session = await spawnRelayTmux("echo hi", true, true);
expect(session).toBe("clawdis-relay");
const spawnMock = spawn as unknown as vi.Mock;
expect(spawnMock.mock.calls.length).toBe(3);
const calls = spawnMock.mock.calls as Array<[string, string[], unknown]>;
expect(calls[0][0]).toBe("tmux"); // kill-session
expect(calls[1][2]?.cmd ?? "").not.toBeUndefined(); // new session
expect(calls[2][1][0]).toBe("attach-session");
});
it("can skip attach", async () => {
await spawnRelayTmux("echo hi", false, true);
const spawnMock = spawn as unknown as vi.Mock;
const hasAttach = spawnMock.mock.calls.some(
(c) =>
Array.isArray(c[1]) && (c[1] as string[]).includes("attach-session"),
);
expect(hasAttach).toBe(false);
});
});

View File

@@ -1,50 +0,0 @@
import { spawn } from "node:child_process";
const SESSION = "clawdis-relay";
export async function spawnRelayTmux(
cmd = "pnpm clawdis relay --verbose",
attach = true,
restart = true,
) {
if (restart) {
await killSession(SESSION);
await new Promise<void>((resolve, reject) => {
const child = spawn("tmux", ["new", "-d", "-s", SESSION, cmd], {
stdio: "inherit",
shell: false,
});
child.on("error", reject);
child.on("exit", (code) => {
if (code === 0) resolve();
else reject(new Error(`tmux exited with code ${code}`));
});
});
}
if (attach) {
await new Promise<void>((resolve, reject) => {
const child = spawn("tmux", ["attach-session", "-t", SESSION], {
stdio: "inherit",
shell: false,
});
child.on("error", reject);
child.on("exit", (code) => {
if (code === 0) resolve();
else reject(new Error(`tmux attach exited with code ${code}`));
});
});
}
return SESSION;
}
async function killSession(name: string) {
await new Promise<void>((resolve) => {
const child = spawn("tmux", ["kill-session", "-t", name], {
stdio: "ignore",
});
child.on("exit", () => resolve());
child.on("error", () => resolve());
});
}