← back to Discontinued Agent

README.md

116 lines

# discontinued-agent

Watches the **info@designerwallcoverings.com** Gmail inbox for vendor replies to
DW's *"Priority Request - Samples over 10 days old - Designer Wallcoverings"*
sample-follow-up emails. When a vendor **explicitly** says a product is
discontinued, it stamps the wallpaper record discontinued in FileMaker and closes
the open sample order — so the >10-day follow-up system stops re-asking.

Ships as **both** a standing poller daemon (this project) and a Claude skill
(`~/.claude/skills/discontinued-agent/SKILL.md`).

**Cost:** George (Gmail HTTP agent) + FileMaker Cloud are local / self-hosted =
**$0 per call**.

---

## How it works

1. **Read** — polls George (`http://127.0.0.1:9850`, account `info`) for threads
   whose subject contains *"Priority Request - Samples over 10 days old"*.
2. **Group** — groups messages by Gmail `threadId` (oldest → newest). Each thread
   has DW **outbound** message(s) (from `info@…`) listing SKUs under a
   *"Manufacturer Number"* block, plus **vendor reply** message(s).
3. **Extract** — pulls the mfr#(s) from the DW outbound's *Manufacturer Number*
   block (each item line is `<Pattern Name> <MFR#>`, e.g. `Shooting Star CM104-2180`).
   The address/phone footer is explicitly excluded.
4. **Classify** the newest vendor reply:
   - **COMMIT** — reply literally contains *discontinued* / *no longer available* /
     *can not order … discontinued*, **and** at least one clean mfr# was extracted.
   - **QUEUE** — ambiguous/sensitive: *no sample available*, *backordered / no
     restock*, relationship-ended, brand-attribution, legal, or "discontinued but
     no mfr# mapped". A memo is written to
     `~/.claude/yolo-queue/pending-approval/` and **no FileMaker write** happens.
   - **SKIP** — nothing actionable.
5. **Write** (COMMIT only) against FileMaker `WALLPAPER` /
   `REPORT ON SAMPLES ORDERED`, for every row matching the mfr# via `Mfr Pattern`
   (FileMaker word-match — `CM104-2180` matches stored `Shooting Star CM104-2180`):
   - **WRITE 1 — discontinue:** set `Date Discontinued` = today (MM/DD/YYYY) on
     every matching row whose `Date Discontinued` is currently blank (skip dated rows).
   - **WRITE 2 — close open order:** set `Date WP Sample Sent` = today on rows that
     are OPEN sample orders (non-empty `account`/`Clients::Company` **and** a
     `Date Sample Request printed for vendor` **and** a blank `Date WP Sample Sent`).
     This blank field is what the >10-day follow-up query keys on; stamping it
     removes the item from future blasts.
6. **Audit** — every action (committed, staged, queued) is appended to
   `data/ledger.jsonl`; a processed-state file `data/processed-threads.json`
   (keyed by `threadId` + a hash of the vendor message) prevents double-processing.

## SAFETY GATE

By default `DISCO_AUTOCOMMIT=0` → the daemon **only stages** (dry-run diffs) and
**queues**; it never commits a FileMaker write. Set `DISCO_AUTOCOMMIT=1` in `.env`
to actually commit. Dry-run diffs are always computed + logged first.

---

## Setup

```sh
cd ~/Projects/discontinued-agent
cp .env.example .env      # then adjust
```

Key `.env` values:

| var | meaning |
|-----|---------|
| `GEORGE_URL` | `http://127.0.0.1:9850` |
| `GEORGE_BASIC_AUTH` | base64 of `admin:<pw>` for George's `/api/*`. On this box the pw is empty → `YWRtaW46` (`admin:`). |
| `GEORGE_ACCOUNT` | `info` |
| `FM_MCP_DIR` | `~/Projects/filemaker-mcp` — its `src/fm-client.js` + `.env` are reused for FM auth (**no creds hardcoded here**). |
| `FM_DATABASE` / `FM_LAYOUT` | `WALLPAPER` / `REPORT ON SAMPLES ORDERED` |
| `POLL_INTERVAL_SEC` | daemon loop interval (default `10`) |
| `DISCO_AUTOCOMMIT` | `1` to commit; anything else = stage + queue only |

## Run on-demand (single scan, always safe to preview)

```sh
node --env-file=.env poller.js --once     # one scan, then exit
# or: npm run once
```

## Run as the daemon (loops every 10s)

The poller loops internally; launchd just keeps it alive.

```sh
# start (bootstrap the LaunchAgent):
launchctl bootstrap gui/$(id -u) ~/Projects/discontinued-agent/com.steve.discontinued-agent.plist

# stop:
launchctl bootout gui/$(id -u)/com.steve.discontinued-agent

# status / restart:
launchctl print gui/$(id -u)/com.steve.discontinued-agent | head
launchctl kickstart -k gui/$(id -u)/com.steve.discontinued-agent

# logs:
tail -f data/agent.out.log
```

> Note: the plist lives in this repo. To have launchd load it you must have the
> file at a stable path — it references this project directory directly, so it
> can be bootstrapped straight from the repo (no copy into `~/Library/LaunchAgents`
> required, though you may symlink it there if you prefer).

## Files

- `poller.js` — the daemon (single-scan `--once`, or infinite loop).
- `lib/george.js` — George Gmail HTTP client (read-only here).
- `lib/filemaker.js` — FM Cloud Data API wrapper (imports filemaker-mcp's client + creds).
- `lib/parse.js` — thread parse, mfr# extraction, gating classification.
- `data/ledger.jsonl` — append-only audit log (gitignored).
- `data/processed-threads.json` — dedupe state (gitignored).
- `com.steve.discontinued-agent.plist` — KeepAlive LaunchAgent.