@deckasoft/waify 0.3.10 → 0.4.1

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 +281 -77
  2. package/package.json +6 -1
package/dist/cli/index.js CHANGED
@@ -12,7 +12,7 @@ var __export = (target, all) => {
12
12
  // src/core/paths.ts
13
13
  import { join } from "path";
14
14
  import { homedir } from "os";
15
- var dataDir, configPath, promptPath, scheduleJsonPath, schedulePath, logPath, envPath, composePath;
15
+ var dataDir, configPath, promptPath, scheduleJsonPath, schedulePath, logPath, envPath, composePath, qrImagePath;
16
16
  var init_paths = __esm({
17
17
  "src/core/paths.ts"() {
18
18
  "use strict";
@@ -24,6 +24,7 @@ var init_paths = __esm({
24
24
  logPath = () => join(dataDir(), "messages.log");
25
25
  envPath = () => process.env["WAIFY_ENV_PATH"] ?? join(dataDir(), ".env");
26
26
  composePath = () => join(dataDir(), "docker-compose.yml");
27
+ qrImagePath = () => join(dataDir(), "qr.png");
27
28
  }
28
29
  });
29
30
 
@@ -203,7 +204,7 @@ var init_schedule = __esm({
203
204
  });
204
205
 
205
206
  // src/core/secrets.ts
206
- import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
207
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
207
208
  import { z as z4 } from "zod";
208
209
  var SecretsSchema, parseEnvFile, loadSecrets, tryLoadSecrets, saveSecrets;
209
210
  var init_secrets = __esm({
@@ -231,7 +232,7 @@ var init_secrets = __esm({
231
232
  const existing = existsSync5(path) ? parseEnvFile(readFileSync4(path, "utf-8")) : {};
232
233
  const merged = { ...existing, ...next };
233
234
  const body = Object.entries(merged).filter(([, v]) => typeof v === "string" && v.length > 0).map(([k, v]) => `${k}=${v}`).join("\n");
234
- writeFileSync4(path, body + "\n", "utf-8");
235
+ writeFileSync5(path, body + "\n", "utf-8");
235
236
  };
236
237
  }
237
238
  });
@@ -307,8 +308,8 @@ var init_sender = __esm({
307
308
  });
308
309
 
309
310
  // src/core/logger.ts
310
- import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync5 } from "fs";
311
- import { dirname as dirname4 } from "path";
311
+ import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync5 } from "fs";
312
+ import { dirname as dirname5 } from "path";
312
313
  var log, LINE_RE, parseLine, readHistory;
313
314
  var init_logger = __esm({
314
315
  "src/core/logger.ts"() {
@@ -316,7 +317,7 @@ var init_logger = __esm({
316
317
  init_paths();
317
318
  log = (status, detail) => {
318
319
  const path = logPath();
319
- mkdirSync5(dirname4(path), { recursive: true });
320
+ mkdirSync6(dirname5(path), { recursive: true });
320
321
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
321
322
  const line = `[${timestamp}] ${status.toUpperCase()} | ${detail}
322
323
  `;
@@ -953,17 +954,50 @@ var registerInit = (program2) => {
953
954
  };
954
955
 
955
956
  // src/cli/commands/setup.ts
956
- init_paths();
957
- init_config();
958
- init_secrets();
959
- init_schedule();
960
- init_prompt();
961
957
  import { spawnSync } from "child_process";
962
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
958
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
963
959
  import { homedir as homedir2 } from "os";
964
960
  import { join as join2 } from "path";
965
961
  import { createInterface } from "readline";
966
962
  import qrcode from "qrcode-terminal";
963
+
964
+ // src/core/qr.ts
965
+ init_paths();
966
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
967
+ import { dirname as dirname4 } from "path";
968
+ import { PNG } from "pngjs";
969
+ import jsQR from "jsqr";
970
+ var stripDataUrlPrefix = (dataUrl) => dataUrl.replace(/^data:image\/\w+;base64,/, "");
971
+ var decodeQrDataUrl = (dataUrl) => {
972
+ try {
973
+ const buffer = Buffer.from(stripDataUrlPrefix(dataUrl), "base64");
974
+ const png = PNG.sync.read(buffer);
975
+ const result = jsQR(new Uint8ClampedArray(png.data), png.width, png.height);
976
+ return result?.data ?? null;
977
+ } catch {
978
+ return null;
979
+ }
980
+ };
981
+ var saveQrImage = (dataUrl) => {
982
+ try {
983
+ const buffer = Buffer.from(stripDataUrlPrefix(dataUrl), "base64");
984
+ const path = qrImagePath();
985
+ mkdirSync4(dirname4(path), { recursive: true });
986
+ writeFileSync4(path, buffer);
987
+ return path;
988
+ } catch (err) {
989
+ const msg = err instanceof Error ? err.message : String(err);
990
+ console.warn(`Could not save QR image: ${msg}`);
991
+ return null;
992
+ }
993
+ };
994
+
995
+ // src/cli/commands/setup.ts
996
+ init_paths();
997
+ init_config();
998
+ init_secrets();
999
+ init_schedule();
1000
+ init_prompt();
967
1001
  import { z as z5 } from "zod";
968
1002
  var SessionResponseSchema = z5.object({
969
1003
  id: z5.string().optional(),
@@ -976,8 +1010,76 @@ var QrResponseSchema = z5.object({
976
1010
  var StatusResponseSchema = z5.object({
977
1011
  status: z5.string().optional()
978
1012
  });
1013
+ var SESSION_NAME = "waify";
1014
+ var SPINNER_FRAMES = [
1015
+ "\u280B",
1016
+ "\u2819",
1017
+ "\u2839",
1018
+ "\u2838",
1019
+ "\u283C",
1020
+ "\u2834",
1021
+ "\u2826",
1022
+ "\u2827",
1023
+ "\u2807",
1024
+ "\u280F"
1025
+ ];
1026
+ var createSpinner = (message) => {
1027
+ let current = message;
1028
+ let frame = 0;
1029
+ const interval = setInterval(() => {
1030
+ process.stderr.write(`\r${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]} ${current}`);
1031
+ frame++;
1032
+ }, 80).unref();
1033
+ return {
1034
+ update: (msg) => {
1035
+ current = msg;
1036
+ },
1037
+ succeed: (msg) => {
1038
+ clearInterval(interval);
1039
+ process.stderr.write(`\r\u2713 ${msg}
1040
+ `);
1041
+ },
1042
+ fail: (msg) => {
1043
+ clearInterval(interval);
1044
+ process.stderr.write(`\r\u2717 ${msg}
1045
+ `);
1046
+ },
1047
+ stop: () => {
1048
+ clearInterval(interval);
1049
+ process.stderr.write("\r\x1B[K");
1050
+ }
1051
+ };
1052
+ };
979
1053
  var wait = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
980
1054
  var fetchWithTimeout = (url, opts = {}, timeoutMs = 5e3) => fetch(url, { ...opts, signal: AbortSignal.timeout(timeoutMs) });
1055
+ var renderQrInTerminal = (dataUrl) => {
1056
+ const raw = decodeQrDataUrl(dataUrl);
1057
+ if (!raw) return false;
1058
+ qrcode.generate(raw, { small: true });
1059
+ return true;
1060
+ };
1061
+ var presentQr = (dataUrl, sessionId, baseUrl, apiKey) => {
1062
+ const rendered = renderQrInTerminal(dataUrl);
1063
+ if (!rendered) {
1064
+ console.warn(
1065
+ " (Could not decode QR image \u2014 use the saved PNG or curl command below)"
1066
+ );
1067
+ }
1068
+ const savedPath = saveQrImage(dataUrl);
1069
+ if (savedPath) {
1070
+ console.warn(`
1071
+ QR also saved to: ${savedPath}`);
1072
+ console.warn(` Open it with: open ${savedPath}`);
1073
+ }
1074
+ console.warn("\n To re-fetch the QR if it expires:");
1075
+ console.warn(
1076
+ ` curl -s -H "X-API-Key: ${apiKey}" ${baseUrl}/api/sessions/${sessionId}/qr \\`
1077
+ );
1078
+ console.warn(
1079
+ ` | sed 's/.*"qrCode":"data:image\\/png;base64,//;s/".*//' \\`
1080
+ );
1081
+ console.warn(` | base64 -d > waify-qr.png`);
1082
+ };
981
1083
  var composeTemplate = () => `services:
982
1084
  openwa-api:
983
1085
  image: ghcr.io/deckasoft/openwa:latest
@@ -1004,21 +1106,37 @@ var composeTemplate = () => `services:
1004
1106
  volumes:
1005
1107
  openwa-data:
1006
1108
  `;
1109
+ var finalizeSetup = (sessionId) => {
1110
+ saveConfig({ ...loadConfig(), openwaSessionId: sessionId });
1111
+ if (!existsSync6(promptPath())) {
1112
+ savePrompt(defaultPrompt);
1113
+ }
1114
+ if (!existsSync6(scheduleJsonPath())) {
1115
+ saveSchedule(defaultSchedule);
1116
+ }
1117
+ console.warn("\n\u2713 All done! Run `waify send` to send your first message.");
1118
+ };
1007
1119
  var promptLine = (rl, question) => new Promise((resolve2) => rl.question(question, resolve2));
1008
- var renderQr = (qrString) => new Promise((resolve2) => qrcode.generate(qrString, { small: true }, () => resolve2()));
1009
1120
  var registerSetup = (program2) => {
1010
- program2.command("setup").description("Guided first-run wizard: installs OpenWA, authenticates WhatsApp, and configures waify").action(async () => {
1011
- const rl = createInterface({ input: process.stdin, output: process.stdout });
1121
+ program2.command("setup").description(
1122
+ "Guided first-run wizard: installs OpenWA, authenticates WhatsApp, and configures waify"
1123
+ ).action(async () => {
1124
+ const rl = createInterface({
1125
+ input: process.stdin,
1126
+ output: process.stdout
1127
+ });
1012
1128
  try {
1013
1129
  console.warn("Checking Docker...");
1014
1130
  const dockerCheck = spawnSync("docker", ["info"], { stdio: "pipe" });
1015
1131
  if (dockerCheck.status !== 0) {
1016
- console.error("Docker is not running or not installed. Please install Docker and start it before running setup.");
1132
+ console.error(
1133
+ "Docker is not running or not installed. Please install Docker and start it before running setup."
1134
+ );
1017
1135
  process.exitCode = 1;
1018
1136
  return;
1019
1137
  }
1020
1138
  console.warn("Creating config directory...");
1021
- mkdirSync4(join2(homedir2(), ".config", "waify"), { recursive: true });
1139
+ mkdirSync5(join2(homedir2(), ".config", "waify"), { recursive: true });
1022
1140
  const baseUrl = loadConfig().openwaBaseUrl;
1023
1141
  let geminiKey = "";
1024
1142
  while (!geminiKey.trim()) {
@@ -1038,24 +1156,42 @@ var registerSetup = (program2) => {
1038
1156
  "Enter your recipient's WhatsApp number (e.g. 5511999998888 \u2014 digits only, no + or spaces):\n> "
1039
1157
  );
1040
1158
  if (!phoneRegex.test(recipientNumber.trim())) {
1041
- console.warn("Invalid number format. Use digits only, 8\u201315 characters. Please try again.");
1159
+ console.warn(
1160
+ "Invalid number format. Use digits only, 8\u201315 characters. Please try again."
1161
+ );
1042
1162
  }
1043
1163
  }
1044
1164
  const chatId = `${recipientNumber.trim()}@c.us`;
1045
1165
  saveSecrets({ GEMINI_API_KEY: geminiKey.trim(), OPENWA_API_KEY: "" });
1046
1166
  saveConfig({ ...loadConfig(), recipients: [{ chatId }] });
1047
1167
  console.warn("Writing docker-compose.yml...");
1048
- writeFileSync5(composePath(), composeTemplate(), "utf-8");
1049
- console.warn("Starting OpenWA containers (this may take a minute on first run)...");
1050
- const upResult = spawnSync("docker", ["compose", "-f", composePath(), "up", "-d", "--no-deps", "openwa-api"], {
1051
- stdio: "inherit"
1052
- });
1168
+ writeFileSync6(composePath(), composeTemplate(), "utf-8");
1169
+ console.warn(
1170
+ "Starting OpenWA containers (this may take a minute on first run)..."
1171
+ );
1172
+ const upResult = spawnSync(
1173
+ "docker",
1174
+ [
1175
+ "compose",
1176
+ "-f",
1177
+ composePath(),
1178
+ "up",
1179
+ "-d",
1180
+ "--no-deps",
1181
+ "openwa-api"
1182
+ ],
1183
+ {
1184
+ stdio: "inherit"
1185
+ }
1186
+ );
1053
1187
  if (upResult.status !== 0) {
1054
- console.error("Failed to start OpenWA containers. Check docker compose logs for details.");
1188
+ console.error(
1189
+ "Failed to start OpenWA containers. Check docker compose logs for details."
1190
+ );
1055
1191
  process.exitCode = 1;
1056
1192
  return;
1057
1193
  }
1058
- console.warn("Waiting for OpenWA API to start...");
1194
+ const apiSpinner = createSpinner("Waiting for OpenWA API to start...");
1059
1195
  let apiReady = false;
1060
1196
  for (let attempt = 0; attempt < 30; attempt++) {
1061
1197
  try {
@@ -1069,53 +1205,84 @@ var registerSetup = (program2) => {
1069
1205
  await wait(2e3);
1070
1206
  }
1071
1207
  if (!apiReady) {
1072
- console.error(
1073
- "OpenWA API did not become ready in time. Check logs with: docker compose -f " + composePath() + " logs openwa-api"
1208
+ apiSpinner.fail(
1209
+ `OpenWA API did not become ready in time. Check logs with: docker compose -f ${composePath()} logs openwa-api`
1074
1210
  );
1075
1211
  process.exitCode = 1;
1076
1212
  return;
1077
1213
  }
1214
+ apiSpinner.succeed("OpenWA API is ready");
1078
1215
  console.warn("Reading API key from container...");
1079
1216
  const keyResult = spawnSync(
1080
1217
  "docker",
1081
- ["compose", "-f", composePath(), "exec", "-T", "openwa-api", "cat", "/app/data/.api-key"],
1218
+ [
1219
+ "compose",
1220
+ "-f",
1221
+ composePath(),
1222
+ "exec",
1223
+ "-T",
1224
+ "openwa-api",
1225
+ "cat",
1226
+ "/app/data/.api-key"
1227
+ ],
1082
1228
  { encoding: "utf-8" }
1083
1229
  );
1084
1230
  const openwaApiKey = keyResult.stdout?.trim();
1085
1231
  if (keyResult.status !== 0 || !openwaApiKey) {
1086
1232
  const errorMsg = keyResult.stderr?.trim() || "Could not read API key from container.";
1087
- throw new Error(`${errorMsg} Check logs with: docker compose -f ${composePath()} logs openwa-api`);
1233
+ throw new Error(
1234
+ `${errorMsg} Check logs with: docker compose -f ${composePath()} logs openwa-api`
1235
+ );
1088
1236
  }
1089
- saveSecrets({ GEMINI_API_KEY: geminiKey.trim(), OPENWA_API_KEY: openwaApiKey });
1237
+ saveSecrets({
1238
+ GEMINI_API_KEY: geminiKey.trim(),
1239
+ OPENWA_API_KEY: openwaApiKey
1240
+ });
1090
1241
  saveConfig({ ...loadConfig(), openwaApiKey, recipients: [{ chatId }] });
1091
1242
  console.warn("Creating WhatsApp session...");
1092
- const sessionRes = await fetchWithTimeout(`${baseUrl}/api/sessions`, {
1093
- method: "POST",
1094
- headers: {
1095
- "X-API-Key": openwaApiKey,
1096
- "Content-Type": "application/json"
1243
+ const sessionRes = await fetchWithTimeout(
1244
+ `${baseUrl}/api/sessions`,
1245
+ {
1246
+ method: "POST",
1247
+ headers: {
1248
+ "X-API-Key": openwaApiKey,
1249
+ "Content-Type": "application/json"
1250
+ },
1251
+ body: JSON.stringify({ name: SESSION_NAME })
1097
1252
  },
1098
- body: JSON.stringify({ name: "waify" })
1099
- }, 1e4);
1253
+ 1e4
1254
+ );
1100
1255
  let sessionId;
1101
1256
  if (sessionRes.status === 409) {
1102
- const listRes = await fetchWithTimeout(`${baseUrl}/api/sessions`, {
1103
- headers: { "X-API-Key": openwaApiKey }
1104
- }, 1e4);
1257
+ const listRes = await fetchWithTimeout(
1258
+ `${baseUrl}/api/sessions`,
1259
+ {
1260
+ headers: { "X-API-Key": openwaApiKey }
1261
+ },
1262
+ 1e4
1263
+ );
1105
1264
  if (!listRes.ok) {
1106
- throw new Error(`Failed to list sessions: ${listRes.status} ${listRes.statusText}`);
1265
+ throw new Error(
1266
+ `Failed to list sessions: ${listRes.status} ${listRes.statusText}`
1267
+ );
1107
1268
  }
1108
1269
  const sessions = SessionListSchema.parse(await listRes.json());
1109
- const existing = sessions.find((s) => s.name === "waify");
1270
+ const existing = sessions.find((s) => s.name === SESSION_NAME);
1110
1271
  if (!existing?.id) {
1111
- throw new Error('Session "waify" already exists but could not be retrieved');
1272
+ throw new Error(
1273
+ 'Session "waify" already exists but could not be retrieved'
1274
+ );
1112
1275
  }
1113
1276
  sessionId = existing.id;
1114
1277
  } else if (!sessionRes.ok) {
1115
- throw new Error(`Failed to create session: ${sessionRes.status} ${sessionRes.statusText}`);
1278
+ throw new Error(
1279
+ `Failed to create session: ${sessionRes.status} ${sessionRes.statusText}`
1280
+ );
1116
1281
  } else {
1117
- const sessionData = SessionResponseSchema.parse(await sessionRes.json());
1118
- sessionId = sessionData.id ?? sessionData.name ?? "waify";
1282
+ const sessionData = SessionResponseSchema.parse(
1283
+ await sessionRes.json()
1284
+ );
1285
+ sessionId = sessionData.id ?? sessionData.name ?? SESSION_NAME;
1119
1286
  }
1120
1287
  spawnSync(
1121
1288
  "docker",
@@ -1128,25 +1295,54 @@ var registerSetup = (program2) => {
1128
1295
  "openwa-api",
1129
1296
  "sh",
1130
1297
  "-c",
1131
- "rm -f /app/data/sessions/session-waify/Singleton*"
1298
+ `rm -f /app/data/sessions/session-${SESSION_NAME}/Singleton*`
1132
1299
  ],
1133
1300
  { encoding: "utf-8" }
1134
1301
  );
1135
1302
  console.warn("Starting WhatsApp engine...");
1136
- const startRes = await fetchWithTimeout(`${baseUrl}/api/sessions/${sessionId}/start`, {
1137
- method: "POST",
1138
- headers: { "X-API-Key": openwaApiKey }
1139
- }, 1e4);
1303
+ const startRes = await fetchWithTimeout(
1304
+ `${baseUrl}/api/sessions/${sessionId}/start`,
1305
+ {
1306
+ method: "POST",
1307
+ headers: { "X-API-Key": openwaApiKey }
1308
+ },
1309
+ 1e4
1310
+ );
1140
1311
  if (!startRes.ok && startRes.status !== 400) {
1141
- throw new Error(`Failed to start session: ${startRes.status} ${startRes.statusText}`);
1312
+ throw new Error(
1313
+ `Failed to start session: ${startRes.status} ${startRes.statusText}`
1314
+ );
1142
1315
  }
1143
- console.warn("Waiting for QR code...");
1316
+ try {
1317
+ const preStatusRes = await fetchWithTimeout(
1318
+ `${baseUrl}/api/sessions/${sessionId}`,
1319
+ { headers: { "X-API-Key": openwaApiKey } }
1320
+ );
1321
+ if (preStatusRes.ok) {
1322
+ const preStatus = StatusResponseSchema.safeParse(
1323
+ await preStatusRes.json()
1324
+ );
1325
+ if (preStatus.success && preStatus.data.status === "ready") {
1326
+ console.warn("\u2713 WhatsApp already linked \u2014 skipping QR scan");
1327
+ finalizeSetup(sessionId);
1328
+ return;
1329
+ }
1330
+ }
1331
+ } catch {
1332
+ }
1333
+ const qrSpinner = createSpinner("Waiting for QR code (Chromium is starting)...");
1144
1334
  let qrCode;
1145
- for (let attempt = 0; attempt < 30; attempt++) {
1335
+ const qrStart = Date.now();
1336
+ for (let attempt = 0; attempt < 150; attempt++) {
1337
+ const elapsed = Math.round((Date.now() - qrStart) / 1e3);
1338
+ qrSpinner.update(`Waiting for QR code... (${elapsed}s / 5 min)`);
1146
1339
  try {
1147
- const qrRes = await fetchWithTimeout(`${baseUrl}/api/sessions/${sessionId}/qr`, {
1148
- headers: { "X-API-Key": openwaApiKey }
1149
- });
1340
+ const qrRes = await fetchWithTimeout(
1341
+ `${baseUrl}/api/sessions/${sessionId}/qr`,
1342
+ {
1343
+ headers: { "X-API-Key": openwaApiKey }
1344
+ }
1345
+ );
1150
1346
  if (qrRes.ok) {
1151
1347
  const qrData = QrResponseSchema.parse(await qrRes.json());
1152
1348
  if (qrData.qrCode) {
@@ -1158,23 +1354,36 @@ var registerSetup = (program2) => {
1158
1354
  }
1159
1355
  await wait(2e3);
1160
1356
  }
1161
- console.warn("\n\u{1F4F1} Scan the QR code with WhatsApp to link your device:");
1357
+ qrSpinner.stop();
1358
+ console.warn(
1359
+ "\n\u{1F4F1} Scan the QR code below with WhatsApp to link your device:"
1360
+ );
1162
1361
  console.warn(" Settings \u2192 Linked Devices \u2192 Link a Device\n");
1163
1362
  if (qrCode) {
1164
- await renderQr(qrCode);
1165
- console.warn("\n (QR expires in ~20s \u2014 re-run setup if it expires before you scan)");
1363
+ presentQr(qrCode, sessionId, baseUrl, openwaApiKey);
1364
+ console.warn(
1365
+ "\n (QR expires in ~20s \u2014 re-run setup if it expires before you scan)"
1366
+ );
1166
1367
  } else {
1167
- console.warn(` QR not yet ready. Check: ${baseUrl}/api/sessions/${sessionId}/qr`);
1168
- console.warn(` (Add header: X-API-Key: ${openwaApiKey})`);
1368
+ console.warn(
1369
+ " QR code was not ready in time. Re-run `waify setup` to try again."
1370
+ );
1169
1371
  }
1170
- console.warn(" Waiting up to 2 minutes for you to scan...\n");
1372
+ const connectSpinner = createSpinner(
1373
+ "Waiting for you to scan the QR code..."
1374
+ );
1171
1375
  let connected = false;
1172
1376
  for (let attempt = 0; attempt < 60; attempt++) {
1173
1377
  try {
1174
- const statusRes = await fetchWithTimeout(`${baseUrl}/api/sessions/${sessionId}`, {
1175
- headers: { "X-API-Key": openwaApiKey }
1176
- });
1177
- const parsed = StatusResponseSchema.safeParse(await statusRes.json());
1378
+ const statusRes = await fetchWithTimeout(
1379
+ `${baseUrl}/api/sessions/${sessionId}`,
1380
+ {
1381
+ headers: { "X-API-Key": openwaApiKey }
1382
+ }
1383
+ );
1384
+ const parsed = StatusResponseSchema.safeParse(
1385
+ await statusRes.json()
1386
+ );
1178
1387
  if (!parsed.success) continue;
1179
1388
  if (parsed.data.status === "ready") {
1180
1389
  connected = true;
@@ -1185,19 +1394,14 @@ var registerSetup = (program2) => {
1185
1394
  await wait(2e3);
1186
1395
  }
1187
1396
  if (!connected) {
1188
- console.error("WhatsApp did not connect within 2 minutes. Please re-run `waify setup` to try again.");
1397
+ connectSpinner.fail(
1398
+ "WhatsApp did not connect within 2 minutes. Please re-run `waify setup` to try again."
1399
+ );
1189
1400
  process.exitCode = 1;
1190
1401
  return;
1191
1402
  }
1192
- console.warn("\u2713 WhatsApp connected!");
1193
- saveConfig({ ...loadConfig(), openwaSessionId: sessionId });
1194
- if (!existsSync6(promptPath())) {
1195
- savePrompt(defaultPrompt);
1196
- }
1197
- if (!existsSync6(scheduleJsonPath())) {
1198
- saveSchedule(defaultSchedule);
1199
- }
1200
- console.warn("\n\u2713 All done! Run `waify send` to send your first message.");
1403
+ connectSpinner.succeed("WhatsApp connected!");
1404
+ finalizeSetup(sessionId);
1201
1405
  } catch (err) {
1202
1406
  console.error(err instanceof Error ? err.message : String(err));
1203
1407
  process.exitCode = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deckasoft/waify",
3
- "version": "0.3.10",
3
+ "version": "0.4.1",
4
4
  "description": "AI-powered daily message sender for WhatsApp, powered by OpenWA",
5
5
  "keywords": [
6
6
  "whatsapp",
@@ -41,6 +41,8 @@
41
41
  "ink-select-input": "^6.2.0",
42
42
  "ink-spinner": "^5.0.0",
43
43
  "ink-text-input": "^6.0.0",
44
+ "jsqr": "^1.4.0",
45
+ "pngjs": "^7.0.0",
44
46
  "qrcode-terminal": "^0.12.0",
45
47
  "react": "^19.2.6",
46
48
  "zod": "^3.24.0"
@@ -49,8 +51,11 @@
49
51
  "@semantic-release/changelog": "^6.0.3",
50
52
  "@semantic-release/git": "^10.0.1",
51
53
  "@types/node": "^22.0.0",
54
+ "@types/pngjs": "^6.0.5",
55
+ "@types/qrcode": "^1.5.6",
52
56
  "@types/qrcode-terminal": "^0.12.2",
53
57
  "@types/react": "^19.2.15",
58
+ "qrcode": "^1.5.4",
54
59
  "semantic-release": "^25.0.3",
55
60
  "tsup": "^8.5.1",
56
61
  "tsx": "^4.19.0",