@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.
- package/dist/cli/index.js +60 -67
- 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.
|
|
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.
|
|
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(
|
|
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.
|
|
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(
|
|
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(
|
|
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
|
|
1105
|
+
let qrCode;
|
|
1091
1106
|
for (let attempt = 0; attempt < 30; attempt++) {
|
|
1092
1107
|
try {
|
|
1093
|
-
const qrRes = await fetchWithTimeout(
|
|
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
|
-
|
|
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
|
-
|
|
1110
|
-
|
|
1111
|
-
console.warn(" (QR
|
|
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(
|
|
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.
|
|
1150
|
+
process.exitCode = 1;
|
|
1151
|
+
return;
|
|
1133
1152
|
}
|
|
1134
1153
|
console.warn("\u2713 WhatsApp connected!");
|
|
1135
|
-
|
|
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.
|
|
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.
|
|
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",
|