@snevins/nudge
v0.2.1
Published
Sequential reminder queue engine
Readme
nudge
nudge is a tiny sequential reminder queue engine. It creates UUID queues, checks them (exit codes for gating), advances them (next), and auto-cleans up completed queues (default: archive).
Install
Requires Node 22.13.0+.
pnpm add -g @snevins/nudgeQuick start (wrapper-style)
ID="$(nudge queue "Run migrations" "Update changelog")"
nudge "$ID" # exit 1 until complete
nudge next "$ID"
nudge next "$ID" # completes + archives by default
nudge "$ID" # now exists:false (and archived:true)Storage
nudge picks a root directory automatically:
- inside a git repo:
.git/nudge/ - outside a git repo:
./.nudge/
If a git repo is detected but .git/nudge/ is not writable, nudge falls back to ./.nudge/ at the repo root.
Layout:
NUDGE_ROOT/
nudge.sqliteCommands
nudge version
Print the current installed nudge version.
nudge version
nudge --versionnudge queue <reminder...>
Create a new queue (UUID v4 generated).
nudge queue "Run migrations" "Update changelog"
nudge queue --json "Run migrations"
nudge queue -- "-starts-with-dash"nudge <queueId>
Check a queue.
- exit
1when pending reminders exist - exit
0when missing or complete
nudge <queueId>
nudge <queueId> --jsonnudge next <queueId>
Acknowledge the next unacked item and advance cursor sequentially.
On completion, cleanup runs automatically (default: archive).
If the queue is already complete but still marked active in the SQLite store (e.g., a crash between final write and cleanup), calling nudge next <queueId> again applies the cleanup policy (unless --cleanup keep).
Archiving is idempotent: once a completed queue is marked archived in the SQLite store, later check / next calls continue returning archived:true.
In human mode (no --json):
- prints the acked reminder text when an item is acknowledged
- exits
1when it advances the queue (an item was acknowledged) - exits
0when missing or already complete (no-op) - prints nothing when missing or already complete
nudge next <queueId>
nudge next <queueId> --json
nudge next <queueId> --cleanup archive|delete|keepnudge clear <queueId>
Delete active queue state (idempotent; does not purge archive).
nudge clear <queueId>
nudge clear <queueId> --jsonnudge --all
List active queues.
- exit
1when any pending exists - exit
0when none are pending
nudge --all
nudge --all --jsonOrdering: --all output is sorted by queueId ascending for stability.
Exit codes (gating API)
0: missing or complete (nudge <queueId>); none pending (nudge --all);nextno-op (missing/already complete); success (queue,clear)1: pending reminders exist (nudge <queueId>/nudge --all);nextadvanced (acked an item)2: error (invalid UUID, lock timeout, IO/corrupt state)
JSON output
--json makes stdout stable JSON (no extra stdout text). By default:
queue,check,next: full snapshot includingitems--all: summary only (noitems)
All timestamp fields (createdAt, updatedAt, ackAt) are RFC3339 strings (e.g. 2026-03-04T20:00:00.000Z).
Full snapshot schema (queue, check, next)
{
"queueId": "uuid",
"createdAt": "2026-03-04T20:00:00.000Z",
"updatedAt": "2026-03-04T20:00:00.000Z",
"cursor": 0,
"done": 0,
"total": 2,
"complete": false,
"next": "Run migrations",
"items": [
{ "index": 0, "text": "Run migrations", "createdAt": "2026-03-04T20:00:00.000Z", "ackAt": null },
{ "index": 1, "text": "Update changelog", "createdAt": "2026-03-04T20:00:00.000Z", "ackAt": null }
]
}Missing queue (check)
{ "queueId": "uuid", "exists": false, "archived": true }(archived is only present when a matching archived queue exists in the SQLite store.)
next action envelope
{
"action": "next",
"acked": { "index": 0, "text": "Run migrations", "ackAt": "2026-03-04T20:05:00.000Z" },
"cleanup": { "policy": "archive", "action": "archived" }
}When present, cleanup means a cleanup action was applied (most commonly on completion).
clear output
{ "queueId": "uuid", "action": "clear", "removed": true }--all output
{
"queues": [
{ "queueId": "uuid", "done": 1, "total": 2, "complete": false, "next": "Update changelog", "updatedAt": "2026-03-04T20:06:00.000Z" }
],
"anyPending": true
}JSON errors
When --json is set and an error occurs (exit code 2), stdout is:
{ "error": { "code": "IO_ERROR", "message": "..." } }Known error.code values:
INVALID_UUIDLOCK_TIMEOUTCORRUPT_STATEIO_ERRORUSAGE_ERRORINTERNAL_ERROR
Environment
NUDGE_LOCK_TIMEOUT_MS overrides the lock acquisition timeout (default: 5000).
Development
This repo uses pnpm (see pnpm-lock.yaml).
The repo requires Node 22.13.0+ and includes .nvmrc for nvm use.
nvm use
pnpm install
pnpm test
pnpm run buildManual Verification
Build first:
pnpm run buildVerify the default archive flow:
CLI="$PWD/dist/src/index.js"
TMP_DIR="$(mktemp -d)"
mkdir -p "$TMP_DIR/.git"
ID="$(
cd "$TMP_DIR" &&
node "$CLI" queue "Run migrations" "Update changelog"
)"
test -f "$TMP_DIR/.git/nudge/nudge.sqlite"
(
cd "$TMP_DIR" &&
node "$CLI" "$ID" --json &&
node "$CLI" next "$ID" --json &&
node "$CLI" next "$ID" --json &&
node "$CLI" "$ID" --json
)Expected:
- the SQLite file exists at
.git/nudge/nudge.sqlite - the first
checkshowscomplete: false - the second
nextreturnscleanup: { "policy": "archive", "action": "archived" } - the final
checkreturns{ "exists": false, "archived": true }
Verify delete cleanup:
CLI="$PWD/dist/src/index.js"
TMP_DIR="$(mktemp -d)"
mkdir -p "$TMP_DIR/.git"
ID="$(
cd "$TMP_DIR" &&
node "$CLI" queue "One"
)"
(
cd "$TMP_DIR" &&
node "$CLI" next "$ID" --json --cleanup delete &&
node "$CLI" "$ID" --json
)Expected:
nextreturnscleanup: { "policy": "delete", "action": "deleted" }- the final
checkreturns{ "exists": false }withoutarchived: true
Verify fallback storage outside git:
CLI="$PWD/dist/src/index.js"
TMP_DIR="$(mktemp -d)"
ID="$(
cd "$TMP_DIR" &&
node "$CLI" queue "Outside git"
)"
test -f "$TMP_DIR/.nudge/nudge.sqlite"
(
cd "$TMP_DIR" &&
node "$CLI" "$ID" --json
)Git hook (optional)
If you want “block pushes when any pending queue exists in this repo”:
cp hooks/pre-push .git/hooks/pre-push
chmod +x .git/hooks/pre-push