@rackbops/plugin-tracker 0.31.4 → 0.33.1
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/README.md +5 -2
- package/dist/plugin.js +4118 -3535
- package/package.json +25 -4
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ as in a server (plan 5.5, item 39).
|
|
|
46
46
|
| `/price url [name] [hours] [drop] [baseline] [near]` | registered | A price (docket's `price`, a `poll` schedule every `hours`, default 12, at most 168). `url` may be any public web page (Page reads, under What it does), eBay's and BGG's included, or a link that redirects to one; only the want-list watcher refuses eBay. The page is read once at once, and nothing is created unless a price is found in it; then the first check, within a minute, DMs the starting price, and a drop of `drop` percent or more (default 10) from the `baseline` -- the last (default), first or highest price seen -- DMs the owner once per crossing. `near` is the words just before the price, for a page with no structured price. At most 20 per person. |
|
|
47
47
|
| `/research question [context] [deadline] [at] [sources]` | registered | A one-off research request (docket's `research`, a `once` schedule at `at`, default now). A research run splits the question into its parts and looks each up on the web (read-and-web tools only, plan 5.6), naming each part's best source and trying it -- BoardGameGeek included, read honestly, a page that refuses it listed as one it could not open (with the web area; without it, named as a gap) -- and never opening eBay (eBay's User Agreement, section 3; since 0.30.0, docket-types 0.10.0), a reviewer run checks the draft against its sources, and only a passed answer is DMed to you (and accepted recipients); its claims are kept as findings on the task's page. `deadline` (docket's `parseWhen`) goes into the request; a run that would start past it makes no call and tells you. `question` must hold the question itself: fewer than 3 characters, or no letter at all (a stray `0`), is refused before anything is made. At most 5 waiting per person (a request waiting for pages for a fuller pass, below, not counted: its first answer is delivered); every run counts against the daily budget (below). `sources: browser` (0.28.0, docket-types 0.8.0's `sources: "sent"`; default `web`) answers from pages your own browser gathers instead -- ask Claude in Chrome to gather them and send them in through the task API (`POST /api/v1/tasks/<id>/sources`, then `/sources/ready`; Task API below), for pages a web run cannot or may not read, such as a BoardGameGeek page that refuses it or eBay, which the research never opens: it answers only from the pages sent in, and the request waits for them, its run queued 14 days out or 10 minutes before the deadline, whichever is sooner (a deadline less than 20 minutes away is refused; the margin makes a missed deadline unlikely, not impossible, since a run held by the budget or behind another Job can still start late), and moved to now by `ready`; a request whose `ready` never comes runs then on whatever pages are in, and with none makes no model call and DMs you so. `at` does not go with it, and it needs the web area. **A fuller pass** (0.29.0, docket-types 0.9.0; with the web area only): when the research run lists pages it needed and could not open (a best source that refused it, BoardGameGeek's included), the reviewed answer is DMed with a "Couldn't open:" list, and the request waits for pages for up to 14 days (or until 10 minutes before its deadline), sending, running and charging nothing; ask Claude in Chrome to gather them and send them in as for a browser request, and `ready` starts a second pass that DMs an "Update to ..." of what it adds or corrects. With no `ready` by then, the second pass runs on whatever pages are in, and with none the request ends and the first answer stands. Answers that research is not available while the city-hall Executor is not set up. |
|
|
48
48
|
| `/scout new interests [lens] [for] [notes] [every]` | registered | An interest scout (docket-types' `scout` type, since 0.6.0; plan 1.2 row 1): every `every` days (default 1, at most 30) at your preferred hour, a model run looks on the web for 5 to 10 things published or available in the last 30 days that fit the `interests` (separated by commas, at most 20, each at most 80 characters) through the `lens` -- `general` (the default), `birthday` (fun or a little grandiose), `anniversary` (a romantic angle) or `christmas` (tied to their interests) -- for `for` (someone else, e.g. Anne; empty means you), weighing `notes`, and DMs them to you (and accepted recipients). A link it showed before is not shown again (it remembers the newest 300); fewer than five come with the run's reason. At most 3 scouts per person; each run counts against the daily budget (below), up to 1.50 USD. Answers that the scout is not available while the city-hall Executor is not set up. |
|
|
49
|
-
| `/want name source [target] [max] [currency] [hours] [judge]` | registered | A want-list watch (docket-types' `wantlist` type, or `wantjudge` when judged, since 0.6.0; plan 1.2 row 2). `source` is `page`: `target` is a shop's listing or search page, checked like `/price`'s page and read once at once -- nothing is made unless its structured product data (JSON-LD `ItemList`s and `Product`s) lists something; `bgg`: `target` is a BoardGameGeek game's address or id, read through BGG's XML API while `TRACKER_BGG_TOKEN` is set; or `ebay`: an **inbox watch** (since 0.19.0; docket-types' `inbox` source), whose listings are sent in rather than read -- the watcher never opens eBay
|
|
49
|
+
| `/want name source [target] [max] [currency] [hours] [judge]` | registered | A want-list watch (docket-types' `wantlist` type, or `wantjudge` when judged, since 0.6.0; plan 1.2 row 2). `source` is `page`: `target` is a shop's listing or search page, checked like `/price`'s page and read once at once -- nothing is made unless its structured product data (JSON-LD `ItemList`s and `Product`s) lists something; `bgg`: `target` is a BoardGameGeek game's address or id, read through BGG's XML API while `TRACKER_BGG_TOKEN` is set; `shopify` (since 0.32.0): `target` is the words to search Shopify stores for (the name when empty), one Shopify catalog search per look through Firecrawl's Alexandria while `TRACKER_FIRECRAWL_KEY` is set (refused without it), for new copies in stock at or under `max`; a product counts only when its title has every word, and each look costs 5 Firecrawl credits, so `hours` is 24 to 168 for it; or `ebay`: with the eBay keys (`TRACKER_EBAY_CLIENT_ID` and `TRACKER_EBAY_CLIENT_SECRET`, since 0.33.0), one search per look through eBay's own Browse API (docket-types' `ebaySource`; eBay US, the newest fifty, narrowed by `max`), for listings whose title has every word of `target` (the words, the name when empty), new and used, an auction at its current bid (or its Buy It Now price, when it has one), the seller's name, feedback percentage and score shown; every hour by default, and the watcher still never opens an eBay page. Without the keys, `ebay` is an **inbox watch** (since 0.19.0; docket-types' `inbox` source), whose listings are sent in rather than read -- the watcher never opens eBay. A watch made before the keys were set stays an inbox watch; one made with them that outlives the keys fails each look and tells its owner once. An eBay API watch takes no alert emails: an alert for its words is left without a DM. It keeps no eBay data, so that the keyset may opt out of eBay's account-deletion notices: no finding is stored, its listings are in the DM only, six hours after a run its stored copy of the DM keeps only its first line and a link to eBay's own search (`ebay-forget.ts`; a send still owed then sends that), and it is never judged, whatever `judge` says, since a judged watch's Job is kept by city-hall's queue. For an inbox eBay watch, `target` is the words to search eBay for (the name when empty); the answer gives the watch's id, how listings reach it -- you search eBay in your own browser, such as with Claude in Chrome, and send what it finds to `POST /api/v1/tasks/<id>/listings` (Task API below), or, while the alert-email route is on (0.25.0, Alert emails below), forward eBay's saved-search alert emails to your alert address, which the answer gives -- and the eBay search itself, with `max` as eBay's own top price, to save on eBay too. `bgg` while the token is unset (BGG declined the application) is an inbox watch the same way: `target` is then a BGG game (kept as its id) or the words to look for (the name when empty). An inbox watch needs the web area (`TRACKER_WEB_URL`), since its listings arrive through the task API; without it, it is refused. It also needs the bot's `HTTP_PORT`, which the tracker cannot see: with `TRACKER_WEB_URL` set and `HTTP_PORT` unset, the watch is made but nothing can send it listings. Every `hours` (default 12 for a page, 24 for BGG and Shopify, 1 for eBay's API and for an inbox watch, whose reads cost nothing, at most 168) on the `poll` tick, the watch DMs each listing at or under `max` (then only listings that show a price) and in `currency` (when set) that it has not DMed before, up to 5 lines with the rest in `/task history`'s second message (up to 20, as many as fit in one message, the newest kept; the task's page in the web area shows the newest 50), with a Done button that ends the watch. Each listing DMed is a finding (an eBay API watch's excepted, above: its extra listings are left to eBay's own search). Three reads in a row that find nothing tell you once (a page whose list is empty is a search with no results, not nothing read). At most 20 new listings a run, the rest the next run. A redirect to eBay is refused on every hop. At most 20 watches per person, judged or not. eBay's pages are refused as a `page`. **Judged** (`judge`, default yes while the model runner is set up; `judge: true` without it is refused; fixed once made): the reads stay plain code, and new listings within the limits go to one `claude -p` Job first (docket-types' `wantjudge`, plan 5.4, item 63), which may open only those listings' pages on the watch's own site (never eBay, no web search) and says for each `match`, `maybe` or `no`, why, and what its page shows about the seller, as signals; the DM shows the `match` and `maybe` ones with those notes, and every listing is a finding with its verdict. 12 turns, 0.50 USD, 5 minutes a look; a look that fails for good sends the listings unchecked. Its reads are uncharged and its looks charged, like any model run; both wait while its owner is at a daily ceiling (with the budgets on). The judge may open only pages on the watch's own host (the pasted page's, or BGG's), and none at all for an inbox, Shopify or eBay watch: it judges from what each listing says. |
|
|
50
50
|
| `/scout edit task [interests] [lens] [for] [notes] [every]` | registered | Changes your scout: `interests` replaces the whole list; anything left out stays; `for` or `notes` set to `-` clears it. A change of `every` keeps the start day. |
|
|
51
51
|
| `/tasks` | registered | Your active tasks and the ones you receive, each with its next run; then your paused ones, each naming up to three people it could not DM (then how many more). A list longer than one message (Discord's 2000 characters) is cut between whole lines and keeps your paused tasks in preference to the active ones that do not fit, since their ids are what `/task resume` needs (any paused ones that still do not fit are counted); it says how many active ones it left out and, when the web area is set up, points to `/web`, which lists them all. |
|
|
52
52
|
| `/task done task` / `/task snooze task [until]` | the task's owner | Answers the task's latest reminder (snooze: an hour, or until `until`). |
|
|
@@ -640,7 +640,7 @@ a research request cannot be edited, here or in Discord, and a `PATCH` of one an
|
|
|
640
640
|
| `GET /api/v1/tasks/<id>` | One of your tasks (a deleted one too), with its history: the newest runs and changes, as `/task history` shows them. |
|
|
641
641
|
| `PATCH /api/v1/tasks/<id>` | Edits it: the fields to change. A price's page is not editable. |
|
|
642
642
|
| `POST /api/v1/tasks/<id>/pause`, `/resume` | Body `{}`. As the web's Pause and Resume, and `/task resume`. |
|
|
643
|
-
| `POST /api/v1/tasks/<id>/listings` | Sends listings in to your inbox watch (an eBay watch, or a BGG one made without BGG's API): `{"listings": [{"title", "url", "price", "currency", "condition", "seller"}, ...]}`, 1 to 40 of them, `title` and an absolute http(s) `url` on the watch's own site (one of eBay's own domains, `EBAY_DOMAINS` in `src/web/api-listings.ts`, or a subdomain, for an eBay watch; `boardgamegeek.com` or a subdomain for a BGG one) required, `price` a number or text such as `"$30.00"`, `currency` three letters. Each is cleaned by docket-types' `submittedListing` (an eBay item's address becomes its bare `/itm/<number>`, so one item stays one listing); the usable ones are kept, the others counted: `200 {"accepted", "rejected", "message"}`. None usable, or the wrong shape, is `400`; more than 40 is `400 too_many`; anyone else's, a deleted or finished watch, or not a watch, is `404`; a page or
|
|
643
|
+
| `POST /api/v1/tasks/<id>/listings` | Sends listings in to your inbox watch (an eBay watch made without the bot's eBay keys, or a BGG one made without BGG's API): `{"listings": [{"title", "url", "price", "currency", "condition", "seller"}, ...]}`, 1 to 40 of them, `title` and an absolute http(s) `url` on the watch's own site (one of eBay's own domains, `EBAY_DOMAINS` in `src/web/api-listings.ts`, or a subdomain, for an eBay watch; `boardgamegeek.com` or a subdomain for a BGG one) required, `price` a number or text such as `"$30.00"`, `currency` three letters. Each is cleaned by docket-types' `submittedListing` (an eBay item's address becomes its bare `/itm/<number>`, so one item stays one listing); the usable ones are kept, the others counted: `200 {"accepted", "rejected", "message"}`. None usable, or the wrong shape, is `400`; more than 40 is `400 too_many`; anyone else's, a deleted or finished watch, or not a watch, is `404`; a page, BGG-API, Shopify or eBay-API watch is `409` (its listings are read, not sent). The next look DMs the new ones within the watch's limits, each once. The newest 100 per watch are kept, as many as one look reads: more than 100 sent between two looks loses the oldest. Body up to 64 KiB. |
|
|
644
644
|
| `POST /api/v1/tasks/<id>/sources` | Sends pages in to your research request made with `sources: browser` (0.28.0, #125), or to one waiting for pages for a fuller pass (0.29.0, its JSON's `waitingForPages`): `{"pages": [{"url", "title", "text"}, ...]}`, 1 to 10 of them, an absolute http(s) `url` and the page's main `text` required. Each is cleaned by docket-types' `gatheredPages` (the text cut to 12,000 characters, the title to one line), and a second page with the same address in one request is left out; a page whose address the request already holds replaces it in its place: `200 {"accepted", "rejected", "overBudget", "pages", "message"}`, `accepted` being how many were stored, `rejected` how many were unusable (no absolute http(s) address or no text, or an address sent twice), `overBudget` how many were left out whole because their text would take the request past 99,800 characters of page text in all (what docket's 100,000-character budget across a request's pages uses whole, the text a replaced page held not counted), and `pages` how many it holds. A request holds at most 10 pages: more is `409 too_many`, saying how many it holds; when no page fits the text budget, `409 too_much_text`. None usable, or the wrong shape, is `400`; more than 10 in one request `400 too_many`; anyone else's, a deleted or finished one, or not a research request, is `404`; a web request not waiting for a fuller pass, or one whose run was moved or has started, is `409`. Body up to 1 MiB. |
|
|
645
645
|
| `POST /api/v1/tasks/<id>/sources/ready` | Body `{}`. Starts that request on the pages it holds: its queued run is moved to now (docket-core's `reschedule`), and the execute lane submits it within a minute or two. For a request waiting for a fuller pass (0.29.0), it moves the request to now and then deletes the queued follow-up its wait rests on, under the task's lock, so the next execute tick runs the second pass on the pages; refused (`409`) once that follow-up has started, fired or has its Job out at the runner. No page in yet, a paused request, or one no longer waiting is `409`; the others as for `/sources`. From then the request takes no more pages; nor does it once a run of it has started, `ready` or not (the run at its expiry). Both routes take the task's lock: while the model runner has the task, either is `409 busy`. |
|
|
646
646
|
| `DELETE /api/v1/tasks/<id>` | Deletes it as the web does: archived, nothing more sent, history kept. No confirmation step. |
|
|
@@ -738,6 +738,9 @@ $ curl -s https://clerk.example.com/tracker/api/v1/tasks/t9 -H "Authorization: B
|
|
|
738
738
|
| `TRACKER_CITY_HALL_KEY` | yes | The source bearer key city-hall checks on `/api/execute/jobs` (its `CITY_HALL_API_KEY`, city-hall#18). Visible ASCII only: a value with whitespace inside it or any other character outside visible ASCII refuses to load (whitespace at either end is trimmed), naming the variable. Never logged. Kept in the instance's own env, never in a repo. |
|
|
739
739
|
| `TRACKER_CITY_HALL_CAPABILITY` | no | The capability tag every Job names, one only docket-runner carries (plan item 71), e.g. `claude-cli:subscription`. No default: a guessed tag could send the tracker's Jobs to another agent. |
|
|
740
740
|
| `TRACKER_CITY_HALL_ACCESS_CLIENT_ID`, `TRACKER_CITY_HALL_ACCESS_CLIENT_SECRET` | yes | A Cloudflare Access service token for city-hall's edge (`CF-Access-Client-Id` / `-Secret`), when one is in front. Both or neither, each visible ASCII only, as for the key. |
|
|
741
|
+
| `TRACKER_FIRECRAWL_KEY` | yes | A Firecrawl API key for `/want source: shopify` (0.32.0): each look is one Shopify catalog search through Firecrawl's Alexandria (`shopify` / `catalog/search_catalog`, 5 credits), sent only to `https://api.firecrawl.dev/v2/scrape` with no redirect followed. Unset or empty = `source: shopify` is refused. Never logged. The runner's Firecrawl key can be used here too, in which case both draw on that one account's credits. |
|
|
742
|
+
| `TRACKER_EBAY_CLIENT_ID` | yes | The App ID (client id) of an eBay developer Production keyset, for `/want source: ebay` through eBay's own Browse API (0.33.0). Goes with `TRACKER_EBAY_CLIENT_SECRET`: both set = `source: ebay` searches eBay US through the API; both unset or empty = `source: ebay` is an inbox watch; one without the other refuses to load. Sent only to `https://api.ebay.com`, with no redirect followed. Never logged. eBay's Dev ID is not needed. |
|
|
743
|
+
| `TRACKER_EBAY_CLIENT_SECRET` | yes | The Cert ID (client secret) of that keyset. The tracker mints an application token from the pair (client credentials, scope `https://api.ebay.com/oauth/api_scope`), keeps it in memory only, and mints again a minute before it ends or once when a search answers 401. A look is one of the keyset's Browse calls (5,000 a day). eBay US prices in USD, so an API eBay watch takes no other `currency`. Never logged. |
|
|
741
744
|
| `TRACKER_BGG_TOKEN` | yes | BoardGameGeek's XML API Bearer token for the tracker's registered application. Set = `/want source: bgg` watches a game's BGG marketplace; unset or empty = `/want source: bgg` makes an inbox watch instead, whose listings are sent in (as for eBay). Never logged. BGG declined the tracker's application (applied 2026-09-29, non-commercial), so no token exists today; if BGG ever grants one, put it in the instance's `.env` and recreate the bot. BGG's terms ask for attribution: every BGG line in a DM says "via BoardGameGeek". |
|
|
742
745
|
| `TRACKER_USR_URL` | no | The https origin of our usr (Rackbops/usr), e.g. `https://id.example.com` (no path). With `TRACKER_USR_KEY` it turns the usr link on (0.20.0): people stay in the tracker, and each is linked to a usr account by usr's user id (`users.usr_subject`). Unset = off, and the tracker behaves as before (migration 9 still adds the empty column). `/allow` and `/register` use it since 0.21.0, and the web sign-in since 0.22.0 (Signing in through usr, above). |
|
|
743
746
|
| `TRACKER_USR_KEY` | yes | The tracker's usr API key, with the role `usr:discord` only (usr's `/api/discord/allow` and `/register-link`), never `tracker:admin` or `tracker:register`, and a usr Discord service (`PUT /api/discord/services/<keyId>`) set to the tracker's app. Required with `TRACKER_USR_URL`; set without it, or with whitespace in it, refuses to load. Never logged. |
|