@deckasoft/waify 0.3.5 → 0.3.7

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.
Files changed (2) hide show
  1. package/dist/cli/index.js +60 -67
  2. package/package.json +3 -1
package/dist/cli/index.js CHANGED
@@ -212,7 +212,7 @@ var init_secrets = __esm({
212
212
  init_paths();
213
213
  SecretsSchema = z4.object({
214
214
  GEMINI_API_KEY: z4.string().min(1),
215
- OPENWA_API_KEY: z4.string().min(1)
215
+ OPENWA_API_KEY: z4.string().min(1).default("dev-admin-key")
216
216
  });
217
217
  parseEnvFile = (content) => content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).reduce((acc, line) => {
218
218
  const eq = line.indexOf("=");
@@ -963,6 +963,7 @@ import { existsSync as existsSync6, mkdirSync as mkdirSync4, writeFileSync as wr
963
963
  import { homedir as homedir2 } from "os";
964
964
  import { join as join2 } from "path";
965
965
  import { createInterface } from "readline";
966
+ import qrcode from "qrcode-terminal";
966
967
  import { z as z5 } from "zod";
967
968
  var SessionResponseSchema = z5.object({
968
969
  id: z5.string().optional(),
@@ -999,27 +1000,54 @@ var composeTemplate = () => `services:
999
1000
  - openwa-data:/app/data
1000
1001
  restart: unless-stopped
1001
1002
 
1002
- openwa-dashboard:
1003
- image: ghcr.io/deckasoft/openwa-dashboard:latest
1004
- ports:
1005
- - '2886:80'
1006
- restart: unless-stopped
1007
-
1008
1003
  volumes:
1009
1004
  openwa-data:
1010
1005
  `;
1011
1006
  var promptLine = (rl, question) => new Promise((resolve2) => rl.question(question, resolve2));
1007
+ var renderQr = (qrString) => new Promise((resolve2) => qrcode.generate(qrString, { small: true }, () => resolve2()));
1012
1008
  var registerSetup = (program2) => {
1013
1009
  program2.command("setup").description("Guided first-run wizard: installs OpenWA, authenticates WhatsApp, and configures waify").action(async () => {
1010
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1014
1011
  try {
1015
1012
  console.warn("Checking Docker...");
1016
1013
  const dockerCheck = spawnSync("docker", ["info"], { stdio: "pipe" });
1017
1014
  if (dockerCheck.status !== 0) {
1018
1015
  console.error("Docker is not running or not installed. Please install Docker and start it before running setup.");
1019
- process.exit(1);
1016
+ process.exitCode = 1;
1017
+ return;
1020
1018
  }
1021
1019
  console.warn("Creating config directory...");
1022
1020
  mkdirSync4(join2(homedir2(), ".config", "waify"), { recursive: true });
1021
+ const baseUrl = loadConfig().openwaBaseUrl;
1022
+ const rawApiKey = await promptLine(
1023
+ rl,
1024
+ 'OpenWA API key (press Enter to use default "dev-admin-key"):\n> '
1025
+ );
1026
+ const openwaApiKey = rawApiKey.trim() || "dev-admin-key";
1027
+ let geminiKey = "";
1028
+ while (!geminiKey.trim()) {
1029
+ geminiKey = await promptLine(
1030
+ rl,
1031
+ "Enter your Gemini API key (get one free at https://aistudio.google.com/apikey):\n> "
1032
+ );
1033
+ if (!geminiKey.trim()) {
1034
+ console.warn("Gemini API key cannot be empty. Please try again.");
1035
+ }
1036
+ }
1037
+ let recipientNumber = "";
1038
+ const phoneRegex = /^\d{8,15}$/;
1039
+ while (!phoneRegex.test(recipientNumber.trim())) {
1040
+ recipientNumber = await promptLine(
1041
+ rl,
1042
+ "Enter your recipient's WhatsApp number (e.g. 5511999998888 \u2014 digits only, no + or spaces):\n> "
1043
+ );
1044
+ if (!phoneRegex.test(recipientNumber.trim())) {
1045
+ console.warn("Invalid number format. Use digits only, 8\u201315 characters. Please try again.");
1046
+ }
1047
+ }
1048
+ const chatId = `${recipientNumber.trim()}@c.us`;
1049
+ saveSecrets({ GEMINI_API_KEY: geminiKey.trim(), OPENWA_API_KEY: openwaApiKey });
1050
+ saveConfig({ ...loadConfig(), openwaApiKey, recipients: [{ chatId }] });
1023
1051
  console.warn("Writing docker-compose.yml...");
1024
1052
  writeFileSync5(composePath(), composeTemplate(), "utf-8");
1025
1053
  console.warn("Starting OpenWA containers (this may take a minute on first run)...");
@@ -1028,13 +1056,14 @@ var registerSetup = (program2) => {
1028
1056
  });
1029
1057
  if (upResult.status !== 0) {
1030
1058
  console.error("Failed to start OpenWA containers. Check docker compose logs for details.");
1031
- process.exit(1);
1059
+ process.exitCode = 1;
1060
+ return;
1032
1061
  }
1033
1062
  console.warn("Waiting for OpenWA API to start...");
1034
1063
  let apiReady = false;
1035
1064
  for (let attempt = 0; attempt < 30; attempt++) {
1036
1065
  try {
1037
- const res = await fetchWithTimeout("http://localhost:2785/api/health");
1066
+ const res = await fetchWithTimeout(`${baseUrl}/api/health`);
1038
1067
  if (res.status >= 200 && res.status < 300) {
1039
1068
  apiReady = true;
1040
1069
  break;
@@ -1047,24 +1076,11 @@ var registerSetup = (program2) => {
1047
1076
  console.error(
1048
1077
  "OpenWA API did not become ready in time. Check logs with: docker compose -f " + composePath() + " logs openwa-api"
1049
1078
  );
1050
- process.exit(1);
1051
- }
1052
- console.warn("Reading API credentials...");
1053
- const keyResult = spawnSync(
1054
- "docker",
1055
- ["compose", "-f", composePath(), "exec", "-T", "openwa-api", "cat", "/app/data/.api-key"],
1056
- { encoding: "utf-8", stdio: "pipe" }
1057
- );
1058
- const openwaApiKey = keyResult.stdout?.trim();
1059
- if (keyResult.status !== 0 || !openwaApiKey) {
1060
- console.error(
1061
- "Could not read API key from container. Check logs: docker compose -f " + composePath() + " logs openwa-api"
1062
- );
1063
- if (keyResult.stderr) console.error(keyResult.stderr);
1064
- process.exit(1);
1079
+ process.exitCode = 1;
1080
+ return;
1065
1081
  }
1066
1082
  console.warn("Creating WhatsApp session...");
1067
- const sessionRes = await fetchWithTimeout("http://localhost:2785/api/sessions", {
1083
+ const sessionRes = await fetchWithTimeout(`${baseUrl}/api/sessions`, {
1068
1084
  method: "POST",
1069
1085
  headers: {
1070
1086
  "X-API-Key": openwaApiKey,
@@ -1077,9 +1093,8 @@ var registerSetup = (program2) => {
1077
1093
  }
1078
1094
  const sessionData = SessionResponseSchema.parse(await sessionRes.json());
1079
1095
  const sessionId = sessionData.id ?? sessionData.name ?? "waify";
1080
- saveConfig({ ...loadConfig(), openwaApiKey, openwaSessionId: sessionId });
1081
1096
  console.warn("Starting WhatsApp engine...");
1082
- const startRes = await fetchWithTimeout(`http://localhost:2785/api/sessions/${sessionId}/start`, {
1097
+ const startRes = await fetchWithTimeout(`${baseUrl}/api/sessions/${sessionId}/start`, {
1083
1098
  method: "POST",
1084
1099
  headers: { "X-API-Key": openwaApiKey }
1085
1100
  }, 1e4);
@@ -1087,16 +1102,16 @@ var registerSetup = (program2) => {
1087
1102
  throw new Error(`Failed to start session: ${startRes.status} ${startRes.statusText}`);
1088
1103
  }
1089
1104
  console.warn("Waiting for QR code...");
1090
- let qrReady = false;
1105
+ let qrCode;
1091
1106
  for (let attempt = 0; attempt < 30; attempt++) {
1092
1107
  try {
1093
- const qrRes = await fetchWithTimeout(`http://localhost:2785/api/sessions/${sessionId}/qr`, {
1108
+ const qrRes = await fetchWithTimeout(`${baseUrl}/api/sessions/${sessionId}/qr`, {
1094
1109
  headers: { "X-API-Key": openwaApiKey }
1095
1110
  });
1096
1111
  if (qrRes.ok) {
1097
1112
  const qrData = QrResponseSchema.parse(await qrRes.json());
1098
1113
  if (qrData.qrCode) {
1099
- qrReady = true;
1114
+ qrCode = qrData.qrCode;
1100
1115
  break;
1101
1116
  }
1102
1117
  }
@@ -1105,16 +1120,19 @@ var registerSetup = (program2) => {
1105
1120
  await wait(2e3);
1106
1121
  }
1107
1122
  console.warn("\n\u{1F4F1} Scan the QR code with WhatsApp to link your device:");
1108
- console.warn(" Settings \u2192 Linked Devices \u2192 Link a Device");
1109
- console.warn(" View QR at: http://localhost:2785/api/docs (Sessions \u2192 GET /sessions/{id}/qr)");
1110
- if (!qrReady) {
1111
- console.warn(" (QR not yet ready \u2014 Chromium may still be initializing, check the Swagger link above)");
1123
+ console.warn(" Settings \u2192 Linked Devices \u2192 Link a Device\n");
1124
+ if (qrCode) {
1125
+ await renderQr(qrCode);
1126
+ console.warn("\n (QR expires in ~20s \u2014 re-run setup if it expires before you scan)");
1127
+ } else {
1128
+ console.warn(` QR not yet ready. Check: ${baseUrl}/api/sessions/${sessionId}/qr`);
1129
+ console.warn(` (Add header: X-API-Key: ${openwaApiKey})`);
1112
1130
  }
1113
1131
  console.warn(" Waiting up to 2 minutes for you to scan...\n");
1114
1132
  let connected = false;
1115
1133
  for (let attempt = 0; attempt < 60; attempt++) {
1116
1134
  try {
1117
- const statusRes = await fetchWithTimeout(`http://localhost:2785/api/sessions/${sessionId}`, {
1135
+ const statusRes = await fetchWithTimeout(`${baseUrl}/api/sessions/${sessionId}`, {
1118
1136
  headers: { "X-API-Key": openwaApiKey }
1119
1137
  });
1120
1138
  const parsed = StatusResponseSchema.safeParse(await statusRes.json());
@@ -1129,38 +1147,11 @@ var registerSetup = (program2) => {
1129
1147
  }
1130
1148
  if (!connected) {
1131
1149
  console.error("WhatsApp did not connect within 2 minutes. Please re-run `waify setup` to try again.");
1132
- process.exit(1);
1150
+ process.exitCode = 1;
1151
+ return;
1133
1152
  }
1134
1153
  console.warn("\u2713 WhatsApp connected!");
1135
- const rl = createInterface({ input: process.stdin, output: process.stdout });
1136
- try {
1137
- let geminiKey = "";
1138
- while (!geminiKey.trim()) {
1139
- geminiKey = await promptLine(
1140
- rl,
1141
- "Enter your Gemini API key (get one free at https://aistudio.google.com/apikey):\n> "
1142
- );
1143
- if (!geminiKey.trim()) {
1144
- console.warn("Gemini API key cannot be empty. Please try again.");
1145
- }
1146
- }
1147
- saveSecrets({ GEMINI_API_KEY: geminiKey.trim(), OPENWA_API_KEY: openwaApiKey });
1148
- let recipientNumber = "";
1149
- const phoneRegex = /^\d{8,15}$/;
1150
- while (!phoneRegex.test(recipientNumber.trim())) {
1151
- recipientNumber = await promptLine(
1152
- rl,
1153
- "Enter your recipient's WhatsApp number (e.g. 5511999998888 \u2014 digits only, no + or spaces):\n> "
1154
- );
1155
- if (!phoneRegex.test(recipientNumber.trim())) {
1156
- console.warn("Invalid number format. Use digits only, 8\u201315 characters. Please try again.");
1157
- }
1158
- }
1159
- const chatId = `${recipientNumber.trim()}@c.us`;
1160
- saveConfig({ ...loadConfig(), recipients: [{ chatId }] });
1161
- } finally {
1162
- rl.close();
1163
- }
1154
+ saveConfig({ ...loadConfig(), openwaSessionId: sessionId });
1164
1155
  if (!existsSync6(promptPath())) {
1165
1156
  savePrompt(defaultPrompt);
1166
1157
  }
@@ -1170,7 +1161,9 @@ var registerSetup = (program2) => {
1170
1161
  console.warn("\n\u2713 All done! Run `waify send` to send your first message.");
1171
1162
  } catch (err) {
1172
1163
  console.error(err instanceof Error ? err.message : String(err));
1173
- process.exit(1);
1164
+ process.exitCode = 1;
1165
+ } finally {
1166
+ rl.close();
1174
1167
  }
1175
1168
  });
1176
1169
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deckasoft/waify",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "AI-powered daily message sender for WhatsApp, powered by OpenWA",
5
5
  "keywords": [
6
6
  "whatsapp",
@@ -41,6 +41,7 @@
41
41
  "ink-select-input": "^6.2.0",
42
42
  "ink-spinner": "^5.0.0",
43
43
  "ink-text-input": "^6.0.0",
44
+ "qrcode-terminal": "^0.12.0",
44
45
  "react": "^19.2.6",
45
46
  "zod": "^3.24.0"
46
47
  },
@@ -48,6 +49,7 @@
48
49
  "@semantic-release/changelog": "^6.0.3",
49
50
  "@semantic-release/git": "^10.0.1",
50
51
  "@types/node": "^22.0.0",
52
+ "@types/qrcode-terminal": "^0.12.2",
51
53
  "@types/react": "^19.2.15",
52
54
  "semantic-release": "^25.0.3",
53
55
  "tsup": "^8.5.1",