Healthcare-Adjacent · CLI Tool · Privacy-First

WhatsApp Reminder CLI — Privacy-First
Medication & Appointment Reminders Built on OpenClaw

A typed CLI that schedules WhatsApp medication and appointment reminders on top of OpenClaw's own cron engine, instead of reimplementing a scheduler. Every command executes with a strict argv array — never a shell string — and the data model has no field capable of storing a diagnosis or clinical detail. Built to demonstrate the judgment healthcare-adjacent automation actually requires: knowing exactly where the compliance boundary sits, and enforcing it in the schema, not just in a policy document.

Project Type Portfolio / Reference Implementation
Core Engine OpenClaw Cron (Source of Truth)
Data No PHI — Free-Text Reminders Only
Industry Healthcare-Adjacent / Personal Automation
WhatsApp Reminder CLI architecture — user runs the remind CLI, input is validated, the OpenClaw wrapper passes argv-only calls to OpenClaw's cron engine (source of truth) and WhatsApp delivery, while SQLite stays a local metadata cache

At a Glance

MetricValue
Toolremind — a Node.js/TypeScript CLI, 7 commands
SchedulingOpenClaw's native cron engine — no custom scheduler built or maintained
DeliveryReal WhatsApp messages via openclaw message send
StorageSQLite as a metadata cache — OpenClaw remains the source of truth for scheduling state
Command ExecutionexecFile/spawn argv arrays only — zero shell string interpolation, anywhere
ComplianceNo PHI fields exist in the schema — only free-text label/message the user authors
TestsVitest — argv-safety suite covers quotes, semicolons, ampersands, pipes, Unicode, emoji
StatusAll 7 commands live-verified against a real OpenClaw install and real WhatsApp delivery

Architecture Highlights

OpenClaw-First Transaction Ordering Zero Shell Interpolation SQLite as Metadata Cache, Not Source of Truth PHI Boundary Enforced by Schema Live-Verified Against Real OpenClaw Install Conditional --tz Handling

Overview

WhatsApp Reminder CLI is a reusable command-line tool that schedules medication and appointment reminders, delivered over WhatsApp, using OpenClaw's own cron engine for both scheduling and delivery. It does not reimplement a scheduler — it wraps openclaw cron and openclaw message send, and keeps a local SQLite store purely as a friendly, typed CRUD layer on top. It was scoped deliberately as a personal reminder utility, not a clinical system: no dosage validation, no adherence analytics, no diagnosis storage, no multi-patient or caregiver management.

All seven CLI commands, the OpenClaw cron wrapper, and the WhatsApp delivery path are real and were run end-to-end against a live OpenClaw install — including real reminder messages delivered to a real phone, shown in the screenshots below.

The Problem

A "simple reminder tool" is a deceptively easy place to make three expensive mistakes, especially once the reminders are medication-related:

Reinventing a Scheduler

Building a custom cron/retry engine duplicates infrastructure that already exists, and introduces a second source of truth to keep in sync — a whole class of bugs a reliable platform already solved.

Shell Injection Risk

User-authored reminder text and phone numbers are exactly the kind of untrusted input that becomes dangerous the moment a tool concatenates them into a shell command.

Silent PHI Creep

"Just add a dosage field" or "just log whether they took it" are reasonable-sounding asks that quietly turn a to-do app into an unregulated clinical record system.

This project builds a reminder tool that leans on an existing, reliable scheduling engine, treats every user-supplied value as unsafe for shell interpretation, and keeps the compliance boundary enforced by the database schema itself — not by a policy someone has to remember to follow.

The Solution

The CLI is a thin, typed layer over OpenClaw's own cron engine — OpenClaw is always the source of truth for scheduling state, and SQLite is only a local read cache for metadata the CLI needs to show back to the user:

LayerJob
CLI (remind)Parses add / list / show / edit / delete / pause / resume via Commander.js
OpenClaw Cron EngineSource of truth — schedules, enables/disables, and tracks run history for every job
SQLiteMetadata cache only — label, message, recipient, schedule, and the linked openclaw_job_id
openclaw message sendDelivery — runs directly as the cron job's command payload, no LLM in the send path

Every mutating command follows one non-negotiable ordering rule: the openclaw cron call always executes first, and the local SQLite write only happens if that call succeeds. A failed OpenClaw call leaves the database exactly as it was and prints the underlying error verbatim — there is no such thing as a "draft" reminder row with no backing job.

LayerTechnology
RuntimeNode.js + TypeScript
CLI FrameworkCommander
Storagebetter-sqlite3 — single-file, synchronous, zero external DB dependency
TestingVitest — DB layer, OpenClaw wrapper (mocked execFile/spawn), and CLI command tests

Key Engineering Decisions

OpenClaw-First Transaction Ordering

Every mutating command executes the openclaw cron call first and only writes SQLite on success — insert for add, update for edit/pause/resume, delete for delete. A reminder row's existence is the claim that a matching OpenClaw job exists.

No Shell Interpolation, Anywhere

The OpenClaw wrapper invokes the binary via execFile/spawn with an argument array only — never exec() or string concatenation. Required unit tests assert this holds for reminder text and recipient values containing quotes, semicolons, ampersands, pipes, Unicode, and emoji.

--command-argv, Not --command

OpenClaw's cron add exposes two payload flags: --command (genuinely shell-interpreted at fire time) and --command-argv (a literal JSON argv array, no shell). This tool always uses the latter — closing the injection surface at the Gateway's own execution step, not just at this tool's own invocation of openclaw.

A Wrong Default Found by Running the Real Thing

Without --no-deliver, a job's delivery mode defaults to an "announce back to the session" fallback that has no session to announce to for a headless CLI-created job — surfacing as a confusing status: error even though the actual WhatsApp send succeeded. Found by inspecting a real job's run history against a live install, not assumed from documentation, then fixed by always passing --no-deliver.

Conditional --tz Handling

A live failed call revealed openclaw cron add rejects --tz alongside --every outright. The SPEC's original assumption (always safe to pass) was corrected: --tz is now only sent for cron schedules or offset-less at datetimes, while the local timezone column still stores whatever the user supplied regardless.

PHI Boundary Enforced by Schema, Not Policy

The reminders table has exactly one free-text field for content (message) and no structured clinical fields — no dosage units, no ICD codes, no provider-as-entity. The compliance boundary holds because the database literally cannot store what it was never given a column for.

An Honest Engineering Call: What's Not Built, and Why

File permissions don't hold on every filesystem — and the README says so.

The SQLite file is chmod'd 0600 on first run, verified working on native Linux filesystems (ext4). On a Windows-drive/DrvFs mount (e.g. WSL's /mnt/c/...) without the metadata mount option, chmod succeeds without error but NTFS has no POSIX bits to persist, so the file stays 777 regardless — confirmed by a direct probe on the dev machine. Where this applies, the real safeguard is the no-PHI schema, not the file permission.

Crash-between-steps is a documented residual risk, not a solved one.

If the process is killed after a successful OpenClaw call but before the matching SQLite write completes, the DB can under-represent OpenClaw's actual state. No two-phase commit or reconciliation command was built for v1 — intentionally, since it's a single-user local CLI where "don't write the DB on failure" already covers the common case, and OpenClaw remains recoverable manually via openclaw cron list if the rare race is ever hit.

Adherence tracking was scoped out on purpose, not forgotten.

The tool confirms a reminder was sent — never whether medication was taken. Building "did they take it" tracking would cross from a personal to-do utility into clinical monitoring territory, which is explicitly out of scope for this project.

This is the kind of trade-off documentation a real engagement should produce — not just a working CLI, but a record of what's solid, what's a known limitation, and why, kept in the project's spec rather than silently worked around.

Data & Compliance Posture

Stated plainly, because a tool that hides its posture is worth less than one that states it:

No PHI/Diagnosis Fields in the Schema
Local-Only SQLite Storage — No Cloud Sync
Zero Shell Interpolation for User Input
Compliance Note in README, Verbatim

The README states the boundary directly: don't type diagnosis, dosage rationale, or other clinical detail into a reminder message — treat it like a personal to-do text, not a medical record. Encryption at rest (e.g. SQLCipher) is documented as a target, not built in v1, since it isn't needed while reminder text stays non-clinical — but it's the first thing that would need to change before this tool could ever hold anything more sensitive.

Full CLI Lifecycle, Against a Real OpenClaw Install

Every command below was run against a real OpenClaw install and produced a real WhatsApp delivery:

WhatsApp message delivered by remind add — Take your multivitamin reminder
Real Delivery — a medication reminder created via remind add arrives on WhatsApp exactly as authored, no paraphrasing.
Second WhatsApp message delivered by remind add — test medication reminder
Verification Run — a second live-fired reminder, confirming delivery is repeatable and not a one-off.
remind add command output — reminder created with OpenClaw job id
remind add — creates the OpenClaw cron job first, then prints the new reminder id and its linked job id.
remind list command output — active reminder listed with schedule and recipient
remind list — active reminders read straight from the local SQLite metadata cache.
remind show command output — full reminder row plus live OpenClaw job status
remind show — the full stored row plus a live status check straight from openclaw cron get.
remind edit command output — reminder updated in place
remind edit — only the passed flags change; OpenClaw is updated before the local row is.
remind pause command output — reminder paused
remind pause — disables the OpenClaw job and marks the reminder paused, in that order.
remind list --all command output — active and paused reminders both shown
remind list --all — includes paused reminders alongside active ones.
remind resume command output — reminder resumed
remind resume — re-enables the OpenClaw job and flips status back to active.
remind delete command output — reminder and OpenClaw job removed
remind delete — removes the OpenClaw job and the local row, completing the full lifecycle.

Engineering Validation

7 / 7 CLI commands live-verified end-to-end against a real OpenClaw install
0 Shell string interpolations — every call uses execFile/spawn argv arrays
6 Argv-safety cases tested: quotes, semicolons, ampersands, pipes, Unicode, emoji
0 PHI/clinical fields in the schema — only free-text label/message

Honest Limitations

Stated deliberately, because a tool that hides its limitations is worth less than one that states them:

No retry/catch-up if the OpenClaw gateway is down at fire time — a reminder is simply missed. This project inherits whatever reliability the gateway install has.
No delivery confirmation loop back to the user beyond WhatsApp's own receipts — remind show confirms the job ran, not that a human saw it.
Crash-between-steps race (documented in "An Honest Engineering Call" above) — a known, unsolved edge case for a single-user local CLI.
No encryption at rest for the SQLite file — acceptable only while reminder text stays non-clinical, per the README's compliance note.
This is an educational/portfolio project — not a medical device, not for diagnosis, treatment, or clinical decision-making.

What This Case Study Demonstrates

  • Judgment about building a thin, typed CRUD layer on top of an existing reliable engine (OpenClaw's cron) instead of reimplementing scheduling from scratch.
  • Security discipline that goes past "we don't use exec()" — closing the injection surface at both this tool's invocation and the Gateway's own execution step, verified with a live probe job, not just read from documentation.
  • Willingness to state a compliance boundary precisely — schema-enforced, not policy-enforced — and document exactly what it does and doesn't cover, including a filesystem caveat most teams wouldn't think to test.
  • A live-verification habit: the spec was corrected multiple times after checking behavior against the real installed CLI's own --help output and real job runs, catching wrong assumptions (a missing --tz constraint, a wrong delivery-mode default) that documentation alone wouldn't have surfaced.

Automating Reminders or Notifications on Top of an Existing Platform?

Whether it's WhatsApp reminders, a scheduling layer over a platform you already trust, or any tool where the compliance boundary needs to be enforced by design rather than by policy, we're always interested in discussing privacy-first automation.