refactor: modularize cli helpers

This commit is contained in:
Peter Steinberger
2025-11-25 03:42:12 +01:00
parent 5c5a103abb
commit a89d7319a9
14 changed files with 591 additions and 777 deletions

View File

@@ -1,4 +1,93 @@
import { createDefaultDeps } from "../index.js";
import { logWebSelfId, logTwilioFrom, monitorTwilio } from "../index.js";
import { ensureBinary } from "../infra/binaries.js";
import { ensurePortAvailable, handlePortError } from "../infra/ports.js";
import { ensureFunnel, getTailnetHostname } from "../infra/tailscale.js";
import { waitForever } from "./wait.js";
import { readEnv } from "../env.js";
import { monitorTwilio as monitorTwilioImpl } from "../twilio/monitor.js";
import { sendMessage, waitForFinalStatus } from "../twilio/send.js";
import { sendMessageWeb, monitorWebProvider, logWebSelfId } from "../provider-web.js";
import { assertProvider, sleep } from "../utils.js";
import { createClient } from "../twilio/client.js";
import { listRecentMessages } from "../twilio/messages.js";
import { updateWebhook } from "../twilio/update-webhook.js";
import { findWhatsappSenderSid } from "../twilio/senders.js";
import { startWebhook } from "../twilio/webhook.js";
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
import { info } from "../globals.js";
import { autoReplyIfConfigured } from "../auto-reply/reply.js";
export { createDefaultDeps, logWebSelfId, logTwilioFrom, monitorTwilio };
export type CliDeps = {
sendMessage: typeof sendMessage;
sendMessageWeb: typeof sendMessageWeb;
waitForFinalStatus: typeof waitForFinalStatus;
assertProvider: typeof assertProvider;
createClient?: typeof createClient;
monitorTwilio: typeof monitorTwilio;
listRecentMessages: typeof listRecentMessages;
ensurePortAvailable: typeof ensurePortAvailable;
startWebhook: typeof import("../twilio/webhook.js").startWebhook;
waitForever: typeof waitForever;
ensureBinary: typeof ensureBinary;
ensureFunnel: typeof ensureFunnel;
getTailnetHostname: typeof getTailnetHostname;
readEnv: typeof readEnv;
findWhatsappSenderSid: typeof findWhatsappSenderSid;
updateWebhook: typeof updateWebhook;
handlePortError: typeof handlePortError;
monitorWebProvider: typeof monitorWebProvider;
};
export async function monitorTwilio(
intervalSeconds: number,
lookbackMinutes: number,
clientOverride?: ReturnType<typeof createClient>,
maxIterations = Infinity,
) {
// Adapter that wires default deps/runtime for the Twilio monitor loop.
return monitorTwilioImpl(intervalSeconds, lookbackMinutes, {
client: clientOverride,
maxIterations,
deps: {
autoReplyIfConfigured,
listRecentMessages,
readEnv,
createClient,
sleep,
},
runtime: defaultRuntime,
});
}
export function createDefaultDeps(): CliDeps {
// Default dependency bundle used by CLI commands and tests.
return {
sendMessage,
sendMessageWeb,
waitForFinalStatus,
assertProvider,
createClient,
monitorTwilio,
listRecentMessages,
ensurePortAvailable,
startWebhook,
waitForever,
ensureBinary,
ensureFunnel,
getTailnetHostname,
readEnv,
findWhatsappSenderSid,
updateWebhook,
handlePortError,
monitorWebProvider,
};
}
export function logTwilioFrom(runtime: RuntimeEnv = defaultRuntime) {
// Log the configured Twilio sender for clarity in CLI output.
const env = readEnv(runtime);
runtime.log(
info(`Provider: twilio (polling inbound) | from ${env.whatsappFrom}`),
);
}
export { logWebSelfId };

View File

@@ -1,6 +1,7 @@
import { Command } from "commander";
import { defaultRuntime, setVerbose, setYes, danger, info, warn } from "../globals.js";
import { setVerbose, setYes, danger, info, warn } from "../globals.js";
import { defaultRuntime } from "../runtime.js";
import { sendCommand } from "../commands/send.js";
import { statusCommand } from "../commands/status.js";
import { upCommand } from "../commands/up.js";

21
src/cli/prompt.ts Normal file
View File

@@ -0,0 +1,21 @@
import readline from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";
import { isVerbose, isYes } from "../globals.js";
export async function promptYesNo(
question: string,
defaultYes = false,
): Promise<boolean> {
// Simple Y/N prompt honoring global --yes and verbosity flags.
if (isVerbose() && isYes()) return true; // redundant guard when both flags set
if (isYes()) return true;
const rl = readline.createInterface({ input, output });
const suffix = defaultYes ? " [Y/n] " : " [y/N] ";
const answer = (await rl.question(`${question}${suffix}`))
.trim()
.toLowerCase();
rl.close();
if (!answer) return defaultYes;
return answer.startsWith("y");
}

8
src/cli/wait.ts Normal file
View File

@@ -0,0 +1,8 @@
export function waitForever() {
// Keep event loop alive via an unref'ed interval plus a pending promise.
const interval = setInterval(() => {}, 1_000_000);
interval.unref();
return new Promise<void>(() => {
/* never resolve */
});
}