Skip to content

Commit a89d731

Browse files
committed
refactor: modularize cli helpers
1 parent 5c5a103 commit a89d731

14 files changed

Lines changed: 591 additions & 777 deletions

File tree

src/cli/deps.ts

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,93 @@
1-
import { createDefaultDeps } from "../index.js";
2-
import { logWebSelfId, logTwilioFrom, monitorTwilio } from "../index.js";
1+
import { ensureBinary } from "../infra/binaries.js";
2+
import { ensurePortAvailable, handlePortError } from "../infra/ports.js";
3+
import { ensureFunnel, getTailnetHostname } from "../infra/tailscale.js";
4+
import { waitForever } from "./wait.js";
5+
import { readEnv } from "../env.js";
6+
import { monitorTwilio as monitorTwilioImpl } from "../twilio/monitor.js";
7+
import { sendMessage, waitForFinalStatus } from "../twilio/send.js";
8+
import { sendMessageWeb, monitorWebProvider, logWebSelfId } from "../provider-web.js";
9+
import { assertProvider, sleep } from "../utils.js";
10+
import { createClient } from "../twilio/client.js";
11+
import { listRecentMessages } from "../twilio/messages.js";
12+
import { updateWebhook } from "../twilio/update-webhook.js";
13+
import { findWhatsappSenderSid } from "../twilio/senders.js";
14+
import { startWebhook } from "../twilio/webhook.js";
15+
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
16+
import { info } from "../globals.js";
17+
import { autoReplyIfConfigured } from "../auto-reply/reply.js";
318

4-
export { createDefaultDeps, logWebSelfId, logTwilioFrom, monitorTwilio };
19+
export type CliDeps = {
20+
sendMessage: typeof sendMessage;
21+
sendMessageWeb: typeof sendMessageWeb;
22+
waitForFinalStatus: typeof waitForFinalStatus;
23+
assertProvider: typeof assertProvider;
24+
createClient?: typeof createClient;
25+
monitorTwilio: typeof monitorTwilio;
26+
listRecentMessages: typeof listRecentMessages;
27+
ensurePortAvailable: typeof ensurePortAvailable;
28+
startWebhook: typeof import("../twilio/webhook.js").startWebhook;
29+
waitForever: typeof waitForever;
30+
ensureBinary: typeof ensureBinary;
31+
ensureFunnel: typeof ensureFunnel;
32+
getTailnetHostname: typeof getTailnetHostname;
33+
readEnv: typeof readEnv;
34+
findWhatsappSenderSid: typeof findWhatsappSenderSid;
35+
updateWebhook: typeof updateWebhook;
36+
handlePortError: typeof handlePortError;
37+
monitorWebProvider: typeof monitorWebProvider;
38+
};
39+
40+
export async function monitorTwilio(
41+
intervalSeconds: number,
42+
lookbackMinutes: number,
43+
clientOverride?: ReturnType<typeof createClient>,
44+
maxIterations = Infinity,
45+
) {
46+
// Adapter that wires default deps/runtime for the Twilio monitor loop.
47+
return monitorTwilioImpl(intervalSeconds, lookbackMinutes, {
48+
client: clientOverride,
49+
maxIterations,
50+
deps: {
51+
autoReplyIfConfigured,
52+
listRecentMessages,
53+
readEnv,
54+
createClient,
55+
sleep,
56+
},
57+
runtime: defaultRuntime,
58+
});
59+
}
60+
61+
export function createDefaultDeps(): CliDeps {
62+
// Default dependency bundle used by CLI commands and tests.
63+
return {
64+
sendMessage,
65+
sendMessageWeb,
66+
waitForFinalStatus,
67+
assertProvider,
68+
createClient,
69+
monitorTwilio,
70+
listRecentMessages,
71+
ensurePortAvailable,
72+
startWebhook,
73+
waitForever,
74+
ensureBinary,
75+
ensureFunnel,
76+
getTailnetHostname,
77+
readEnv,
78+
findWhatsappSenderSid,
79+
updateWebhook,
80+
handlePortError,
81+
monitorWebProvider,
82+
};
83+
}
84+
85+
export function logTwilioFrom(runtime: RuntimeEnv = defaultRuntime) {
86+
// Log the configured Twilio sender for clarity in CLI output.
87+
const env = readEnv(runtime);
88+
runtime.log(
89+
info(`Provider: twilio (polling inbound) | from ${env.whatsappFrom}`),
90+
);
91+
}
92+
93+
export { logWebSelfId };

src/cli/program.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Command } from "commander";
22

3-
import { defaultRuntime, setVerbose, setYes, danger, info, warn } from "../globals.js";
3+
import { setVerbose, setYes, danger, info, warn } from "../globals.js";
4+
import { defaultRuntime } from "../runtime.js";
45
import { sendCommand } from "../commands/send.js";
56
import { statusCommand } from "../commands/status.js";
67
import { upCommand } from "../commands/up.js";

src/cli/prompt.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import readline from "node:readline/promises";
2+
import { stdin as input, stdout as output } from "node:process";
3+
4+
import { isVerbose, isYes } from "../globals.js";
5+
6+
export async function promptYesNo(
7+
question: string,
8+
defaultYes = false,
9+
): Promise<boolean> {
10+
// Simple Y/N prompt honoring global --yes and verbosity flags.
11+
if (isVerbose() && isYes()) return true; // redundant guard when both flags set
12+
if (isYes()) return true;
13+
const rl = readline.createInterface({ input, output });
14+
const suffix = defaultYes ? " [Y/n] " : " [y/N] ";
15+
const answer = (await rl.question(`${question}${suffix}`))
16+
.trim()
17+
.toLowerCase();
18+
rl.close();
19+
if (!answer) return defaultYes;
20+
return answer.startsWith("y");
21+
}

src/cli/wait.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function waitForever() {
2+
// Keep event loop alive via an unref'ed interval plus a pending promise.
3+
const interval = setInterval(() => {}, 1_000_000);
4+
interval.unref();
5+
return new Promise<void>(() => {
6+
/* never resolve */
7+
});
8+
}

src/commands/send.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { info } from "../globals.js";
2-
import type { CliDeps, Provider, RuntimeEnv } from "../index.js";
2+
import type { CliDeps } from "../cli/deps.js";
3+
import type { Provider } from "../utils.js";
4+
import type { RuntimeEnv } from "../runtime.js";
35

46
export async function sendCommand(
57
opts: {

src/commands/status.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import type { CliDeps, RuntimeEnv } from "../index.js";
2-
import { formatMessageLine } from "../index.js";
1+
import type { CliDeps } from "../cli/deps.js";
2+
import type { RuntimeEnv } from "../runtime.js";
3+
import { formatMessageLine } from "../twilio/messages.js";
34

45
export async function statusCommand(
56
opts: { limit: string; lookback: string; json?: boolean },

src/commands/up.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import type { CliDeps, RuntimeEnv } from "../index.js";
2-
import { waitForever as defaultWaitForever } from "../index.js";
1+
import type { CliDeps } from "../cli/deps.js";
2+
import type { RuntimeEnv } from "../runtime.js";
3+
import { waitForever as defaultWaitForever } from "../cli/wait.js";
34

45
export async function upCommand(
56
opts: { port: string; path: string; verbose?: boolean; yes?: boolean },

src/commands/webhook.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { CliDeps, RuntimeEnv } from "../index.js";
1+
import type { CliDeps } from "../cli/deps.js";
2+
import type { RuntimeEnv } from "../runtime.js";
23

34
export async function webhookCommand(
45
opts: {

0 commit comments

Comments
 (0)