@deckasoft/waify 0.3.5 → 0.3.6
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 -61
- 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(),
|
|
@@ -1009,17 +1010,50 @@ volumes:
|
|
|
1009
1010
|
openwa-data:
|
|
1010
1011
|
`;
|
|
1011
1012
|
var promptLine = (rl, question) => new Promise((resolve2) => rl.question(question, resolve2));
|
|
1013
|
+
var renderQr = (qrString) => new Promise((resolve2) => qrcode.generate(qrString, { small: true }, () => resolve2()));
|
|
1012
1014
|
var registerSetup = (program2) => {
|
|
1013
1015
|
program2.command("setup").description("Guided first-run wizard: installs OpenWA, authenticates WhatsApp, and configures waify").action(async () => {
|
|
1016
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1014
1017
|
try {
|
|
1015
1018
|
console.warn("Checking Docker...");
|
|
1016
1019
|
const dockerCheck = spawnSync("docker", ["info"], { stdio: "pipe" });
|
|
1017
1020
|
if (dockerCheck.status !== 0) {
|
|
1018
1021
|
console.error("Docker is not running or not installed. Please install Docker and start it before running setup.");
|
|
1019
|
-
process.
|
|
1022
|
+
process.exitCode = 1;
|
|
1023
|
+
return;
|
|
1020
1024
|
}
|
|
1021
1025
|
console.warn("Creating config directory...");
|
|
1022
1026
|
mkdirSync4(join2(homedir2(), ".config", "waify"), { recursive: true });
|
|
1027
|
+
const baseUrl = loadConfig().openwaBaseUrl;
|
|
1028
|
+
const rawApiKey = await promptLine(
|
|
1029
|
+
rl,
|
|
1030
|
+
'OpenWA API key (press Enter to use default "dev-admin-key"):\n> '
|
|
1031
|
+
);
|
|
1032
|
+
const openwaApiKey = rawApiKey.trim() || "dev-admin-key";
|
|
1033
|
+
let geminiKey = "";
|
|
1034
|
+
while (!geminiKey.trim()) {
|
|
1035
|
+
geminiKey = await promptLine(
|
|
1036
|
+
rl,
|
|
1037
|
+
"Enter your Gemini API key (get one free at https://aistudio.google.com/apikey):\n> "
|
|
1038
|
+
);
|
|
1039
|
+
if (!geminiKey.trim()) {
|
|
1040
|
+
console.warn("Gemini API key cannot be empty. Please try again.");
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
let recipientNumber = "";
|
|
1044
|
+
const phoneRegex = /^\d{8,15}$/;
|
|
1045
|
+
while (!phoneRegex.test(recipientNumber.trim())) {
|
|
1046
|
+
recipientNumber = await promptLine(
|
|
1047
|
+
rl,
|
|
1048
|
+
"Enter your recipient's WhatsApp number (e.g. 5511999998888 \u2014 digits only, no + or spaces):\n> "
|
|
1049
|
+
);
|
|
1050
|
+
if (!phoneRegex.test(recipientNumber.trim())) {
|
|
1051
|
+
console.warn("Invalid number format. Use digits only, 8\u201315 characters. Please try again.");
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
const chatId = `${recipientNumber.trim()}@c.us`;
|
|
1055
|
+
saveSecrets({ GEMINI_API_KEY: geminiKey.trim(), OPENWA_API_KEY: openwaApiKey });
|
|
1056
|
+
saveConfig({ ...loadConfig(), openwaApiKey, recipients: [{ chatId }] });
|
|
1023
1057
|
console.warn("Writing docker-compose.yml...");
|
|
1024
1058
|
writeFileSync5(composePath(), composeTemplate(), "utf-8");
|
|
1025
1059
|
console.warn("Starting OpenWA containers (this may take a minute on first run)...");
|
|
@@ -1028,13 +1062,14 @@ var registerSetup = (program2) => {
|
|
|
1028
1062
|
});
|
|
1029
1063
|
if (upResult.status !== 0) {
|
|
1030
1064
|
console.error("Failed to start OpenWA containers. Check docker compose logs for details.");
|
|
1031
|
-
process.
|
|
1065
|
+
process.exitCode = 1;
|
|
1066
|
+
return;
|
|
1032
1067
|
}
|
|
1033
1068
|
console.warn("Waiting for OpenWA API to start...");
|
|
1034
1069
|
let apiReady = false;
|
|
1035
1070
|
for (let attempt = 0; attempt < 30; attempt++) {
|
|
1036
1071
|
try {
|
|
1037
|
-
const res = await fetchWithTimeout(
|
|
1072
|
+
const res = await fetchWithTimeout(`${baseUrl}/api/health`);
|
|
1038
1073
|
if (res.status >= 200 && res.status < 300) {
|
|
1039
1074
|
apiReady = true;
|
|
1040
1075
|
break;
|
|
@@ -1047,24 +1082,11 @@ var registerSetup = (program2) => {
|
|
|
1047
1082
|
console.error(
|
|
1048
1083
|
"OpenWA API did not become ready in time. Check logs with: docker compose -f " + composePath() + " logs openwa-api"
|
|
1049
1084
|
);
|
|
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);
|
|
1085
|
+
process.exitCode = 1;
|
|
1086
|
+
return;
|
|
1065
1087
|
}
|
|
1066
1088
|
console.warn("Creating WhatsApp session...");
|
|
1067
|
-
const sessionRes = await fetchWithTimeout(
|
|
1089
|
+
const sessionRes = await fetchWithTimeout(`${baseUrl}/api/sessions`, {
|
|
1068
1090
|
method: "POST",
|
|
1069
1091
|
headers: {
|
|
1070
1092
|
"X-API-Key": openwaApiKey,
|
|
@@ -1077,9 +1099,8 @@ var registerSetup = (program2) => {
|
|
|
1077
1099
|
}
|
|
1078
1100
|
const sessionData = SessionResponseSchema.parse(await sessionRes.json());
|
|
1079
1101
|
const sessionId = sessionData.id ?? sessionData.name ?? "waify";
|
|
1080
|
-
saveConfig({ ...loadConfig(), openwaApiKey, openwaSessionId: sessionId });
|
|
1081
1102
|
console.warn("Starting WhatsApp engine...");
|
|
1082
|
-
const startRes = await fetchWithTimeout(
|
|
1103
|
+
const startRes = await fetchWithTimeout(`${baseUrl}/api/sessions/${sessionId}/start`, {
|
|
1083
1104
|
method: "POST",
|
|
1084
1105
|
headers: { "X-API-Key": openwaApiKey }
|
|
1085
1106
|
}, 1e4);
|
|
@@ -1087,16 +1108,16 @@ var registerSetup = (program2) => {
|
|
|
1087
1108
|
throw new Error(`Failed to start session: ${startRes.status} ${startRes.statusText}`);
|
|
1088
1109
|
}
|
|
1089
1110
|
console.warn("Waiting for QR code...");
|
|
1090
|
-
let
|
|
1111
|
+
let qrCode;
|
|
1091
1112
|
for (let attempt = 0; attempt < 30; attempt++) {
|
|
1092
1113
|
try {
|
|
1093
|
-
const qrRes = await fetchWithTimeout(
|
|
1114
|
+
const qrRes = await fetchWithTimeout(`${baseUrl}/api/sessions/${sessionId}/qr`, {
|
|
1094
1115
|
headers: { "X-API-Key": openwaApiKey }
|
|
1095
1116
|
});
|
|
1096
1117
|
if (qrRes.ok) {
|
|
1097
1118
|
const qrData = QrResponseSchema.parse(await qrRes.json());
|
|
1098
1119
|
if (qrData.qrCode) {
|
|
1099
|
-
|
|
1120
|
+
qrCode = qrData.qrCode;
|
|
1100
1121
|
break;
|
|
1101
1122
|
}
|
|
1102
1123
|
}
|
|
@@ -1105,16 +1126,19 @@ var registerSetup = (program2) => {
|
|
|
1105
1126
|
await wait(2e3);
|
|
1106
1127
|
}
|
|
1107
1128
|
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
|
|
1129
|
+
console.warn(" Settings \u2192 Linked Devices \u2192 Link a Device\n");
|
|
1130
|
+
if (qrCode) {
|
|
1131
|
+
await renderQr(qrCode);
|
|
1132
|
+
console.warn("\n (QR expires in ~20s \u2014 re-run setup if it expires before you scan)");
|
|
1133
|
+
} else {
|
|
1134
|
+
console.warn(` QR not yet ready. Check: ${baseUrl}/api/sessions/${sessionId}/qr`);
|
|
1135
|
+
console.warn(` (Add header: X-API-Key: ${openwaApiKey})`);
|
|
1112
1136
|
}
|
|
1113
1137
|
console.warn(" Waiting up to 2 minutes for you to scan...\n");
|
|
1114
1138
|
let connected = false;
|
|
1115
1139
|
for (let attempt = 0; attempt < 60; attempt++) {
|
|
1116
1140
|
try {
|
|
1117
|
-
const statusRes = await fetchWithTimeout(
|
|
1141
|
+
const statusRes = await fetchWithTimeout(`${baseUrl}/api/sessions/${sessionId}`, {
|
|
1118
1142
|
headers: { "X-API-Key": openwaApiKey }
|
|
1119
1143
|
});
|
|
1120
1144
|
const parsed = StatusResponseSchema.safeParse(await statusRes.json());
|
|
@@ -1129,38 +1153,11 @@ var registerSetup = (program2) => {
|
|
|
1129
1153
|
}
|
|
1130
1154
|
if (!connected) {
|
|
1131
1155
|
console.error("WhatsApp did not connect within 2 minutes. Please re-run `waify setup` to try again.");
|
|
1132
|
-
process.
|
|
1156
|
+
process.exitCode = 1;
|
|
1157
|
+
return;
|
|
1133
1158
|
}
|
|
1134
1159
|
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
|
-
}
|
|
1160
|
+
saveConfig({ ...loadConfig(), openwaSessionId: sessionId });
|
|
1164
1161
|
if (!existsSync6(promptPath())) {
|
|
1165
1162
|
savePrompt(defaultPrompt);
|
|
1166
1163
|
}
|
|
@@ -1170,7 +1167,9 @@ var registerSetup = (program2) => {
|
|
|
1170
1167
|
console.warn("\n\u2713 All done! Run `waify send` to send your first message.");
|
|
1171
1168
|
} catch (err) {
|
|
1172
1169
|
console.error(err instanceof Error ? err.message : String(err));
|
|
1173
|
-
process.
|
|
1170
|
+
process.exitCode = 1;
|
|
1171
|
+
} finally {
|
|
1172
|
+
rl.close();
|
|
1174
1173
|
}
|
|
1175
1174
|
});
|
|
1176
1175
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deckasoft/waify",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
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",
|