@deckasoft/waify 0.4.4 → 0.5.0

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 +68 -13
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -119,16 +119,42 @@ var init_prompt = __esm({
119
119
 
120
120
  // src/core/schedule.ts
121
121
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
122
- import { dirname as dirname3, resolve } from "path";
122
+ import { dirname as dirname3 } from "path";
123
123
  import { z as z3 } from "zod";
124
- var ScheduledJobSchema, ScheduleSchema, defaultSchedule, scheduleJsonPath2, loadSchedule, saveSchedule, ofeliaRuntime, renderJob, renderOfeliaIni, regenerateOfeliaIni, addJob, removeJob;
124
+ var CRON_RANGES, STEP_RE, RANGE_RE, isValidCronField, isValidCron, ScheduledJobSchema, ScheduleSchema, defaultSchedule, scheduleJsonPath2, loadSchedule, saveSchedule, ofeliaRuntime, renderJob, renderOfeliaIni, regenerateOfeliaIni, addJob, removeJob;
125
125
  var init_schedule = __esm({
126
126
  "src/core/schedule.ts"() {
127
127
  "use strict";
128
128
  init_paths();
129
+ CRON_RANGES = [
130
+ [0, 59],
131
+ // seconds
132
+ [0, 59],
133
+ // minutes
134
+ [0, 23],
135
+ // hours
136
+ [1, 31],
137
+ // day-of-month
138
+ [1, 12],
139
+ // month
140
+ [0, 7]
141
+ // day-of-week
142
+ ];
143
+ STEP_RE = /^(\*|\d+)\/\d+$/;
144
+ RANGE_RE = /^\d+-\d+$/;
145
+ isValidCronField = (field, [min, max]) => {
146
+ if (field === "*") return true;
147
+ if (STEP_RE.test(field) || RANGE_RE.test(field)) return true;
148
+ const n = Number(field);
149
+ return Number.isInteger(n) && n >= min && n <= max;
150
+ };
151
+ isValidCron = (value) => {
152
+ const fields = value.trim().split(/\s+/);
153
+ return fields.length === 6 && fields.every((f, i) => isValidCronField(f, CRON_RANGES[i]));
154
+ };
129
155
  ScheduledJobSchema = z3.object({
130
156
  name: z3.string().min(1).regex(/^[a-z0-9-]+$/, "name must be lowercase alphanumeric with dashes"),
131
- schedule: z3.string().min(1),
157
+ schedule: z3.string().min(1).refine(isValidCron, { message: "schedule must be a 6-field cron expression (e.g. 0 0 9 * * *)" }),
132
158
  command: z3.string().min(1).default("send")
133
159
  });
134
160
  ScheduleSchema = z3.object({
@@ -156,8 +182,8 @@ var init_schedule = __esm({
156
182
  ofeliaRuntime = () => ({
157
183
  image: process.env["WAIFY_SENDER_IMAGE"] ?? "openwa-scripts-sender:latest",
158
184
  network: process.env["WAIFY_NETWORK"] ?? "waify-network",
159
- hostDataDir: process.env["WAIFY_HOST_DATA_DIR"] ?? resolve(process.cwd(), "data"),
160
- hostEnvFile: process.env["WAIFY_HOST_ENV_FILE"] ?? resolve(process.cwd(), ".env")
185
+ hostDataDir: process.env["WAIFY_HOST_DATA_DIR"] ?? dataDir(),
186
+ hostEnvFile: process.env["WAIFY_HOST_ENV_FILE"] ?? envPath()
161
187
  });
162
188
  renderJob = (job, runtime) => [
163
189
  `[job-run "${job.name}"]`,
@@ -1054,7 +1080,7 @@ var createSpinner = (message) => {
1054
1080
  }
1055
1081
  };
1056
1082
  };
1057
- var wait = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
1083
+ var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1058
1084
  var fetchWithTimeout = (url, opts = {}, timeoutMs = 5e3) => fetch(url, { ...opts, signal: AbortSignal.timeout(timeoutMs) });
1059
1085
  var renderQrInTerminal = (dataUrl) => {
1060
1086
  const raw = decodeQrDataUrl(dataUrl);
@@ -1110,17 +1136,45 @@ var composeTemplate = () => `services:
1110
1136
  volumes:
1111
1137
  openwa-data:
1112
1138
  `;
1113
- var finalizeSetup = (sessionId) => {
1139
+ var finalizeSetup = (sessionId, jobs) => {
1114
1140
  saveConfig({ ...loadConfig(), openwaSessionId: sessionId });
1115
1141
  if (!existsSync6(promptPath())) {
1116
1142
  savePrompt(defaultPrompt);
1117
1143
  }
1118
- if (!existsSync6(scheduleJsonPath())) {
1119
- saveSchedule(defaultSchedule);
1120
- }
1144
+ saveSchedule({ jobs });
1121
1145
  console.warn("\n\u2713 All done! Run `waify send` to send your first message.");
1122
1146
  };
1123
- var promptLine = (rl, question) => new Promise((resolve2) => rl.question(question, resolve2));
1147
+ var promptLine = (rl, question) => new Promise((resolve) => rl.question(question, resolve));
1148
+ var promptUntilValid = async (promptFn, question, validate, errorMsg) => {
1149
+ const answer = (await promptFn(question)).trim();
1150
+ if (validate(answer)) return answer;
1151
+ process.stderr.write(errorMsg + "\n");
1152
+ return promptUntilValid(promptFn, question, validate, errorMsg);
1153
+ };
1154
+ var collectJobs = async (promptFn, accumulated = []) => {
1155
+ const name = await promptUntilValid(
1156
+ promptFn,
1157
+ "Job name: ",
1158
+ (v) => /^[a-z0-9-]+$/.test(v),
1159
+ "Name must be lowercase letters, numbers, and dashes only."
1160
+ );
1161
+ const schedule = await promptUntilValid(
1162
+ promptFn,
1163
+ "Cron pattern (e.g. 0 0 9 * * *): ",
1164
+ isValidCron,
1165
+ "Invalid cron pattern. Use 6 space-separated fields, e.g. 0 0 9 * * *"
1166
+ );
1167
+ const job = ScheduledJobSchema.parse({ name, schedule, command: "send" });
1168
+ const jobs = [...accumulated, job];
1169
+ const more = (await promptFn("Add another schedule? (y/N) ")).trim().toLowerCase();
1170
+ return more === "y" ? collectJobs(promptFn, jobs) : jobs;
1171
+ };
1172
+ var promptScheduleJobs = async (promptFn) => {
1173
+ process.stderr.write(
1174
+ "\nConfigure your message schedule (at least one job required).\nJob names: lowercase letters, numbers, and dashes only.\nCron pattern: 6 fields, e.g. 0 0 9 * * * (sec min hour dom month dow)\n\n"
1175
+ );
1176
+ return collectJobs(promptFn);
1177
+ };
1124
1178
  var registerSetup = (program2) => {
1125
1179
  program2.command("setup").description(
1126
1180
  "Guided first-run wizard: installs OpenWA, authenticates WhatsApp, and configures waify"
@@ -1168,6 +1222,7 @@ var registerSetup = (program2) => {
1168
1222
  const chatId = `${recipientNumber.trim()}@c.us`;
1169
1223
  saveSecrets({ GEMINI_API_KEY: geminiKey.trim(), OPENWA_API_KEY: "" });
1170
1224
  saveConfig({ ...loadConfig(), recipients: [{ chatId }] });
1225
+ const jobs = await promptScheduleJobs((q) => promptLine(rl, q));
1171
1226
  console.warn("Writing docker-compose.yml...");
1172
1227
  writeFileSync6(composePath(), composeTemplate(), "utf-8");
1173
1228
  console.warn(
@@ -1338,7 +1393,7 @@ var registerSetup = (program2) => {
1338
1393
  );
1339
1394
  if (preStatus.success && preStatus.data.status === "ready") {
1340
1395
  console.warn("\u2713 WhatsApp already linked \u2014 skipping QR scan");
1341
- finalizeSetup(sessionId);
1396
+ finalizeSetup(sessionId, jobs);
1342
1397
  return;
1343
1398
  }
1344
1399
  }
@@ -1415,7 +1470,7 @@ var registerSetup = (program2) => {
1415
1470
  return;
1416
1471
  }
1417
1472
  connectSpinner.succeed("WhatsApp connected!");
1418
- finalizeSetup(sessionId);
1473
+ finalizeSetup(sessionId, jobs);
1419
1474
  } catch (err) {
1420
1475
  console.error(err instanceof Error ? err.message : String(err));
1421
1476
  process.exitCode = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deckasoft/waify",
3
- "version": "0.4.4",
3
+ "version": "0.5.0",
4
4
  "description": "AI-powered daily message sender for WhatsApp, powered by OpenWA",
5
5
  "keywords": [
6
6
  "whatsapp",