@rackbops/plugin-tracker 0.6.0 → 0.8.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.
- package/README.md +154 -19
- package/dist/plugin.js +783 -110
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -8,8 +8,9 @@ command, delivered by DM, answered by button, with history -- the first slice of
|
|
|
8
8
|
(#80): sign-in by one-time link, my tasks, a task's history, and settings -- renewals and
|
|
9
9
|
the price tracker (#81, plan E6): two more types on the notify side, no model, nothing sent to
|
|
10
10
|
city-hall -- the web area's task editor (#80, slice 2): make, edit, pause, resume and delete
|
|
11
|
-
your own tasks from the web, under the commands' own rules --
|
|
12
|
-
slice 3)
|
|
11
|
+
your own tasks from the web, under the commands' own rules -- the admin view and forget-me (#80,
|
|
12
|
+
slice 3) -- and the JSON task API with personal API tokens (#80, slice 4), for a program acting as
|
|
13
|
+
you, such as a later intake agent (plan E10).
|
|
13
14
|
|
|
14
15
|
## Commands
|
|
15
16
|
|
|
@@ -32,10 +33,10 @@ as in a server (plan 5.5, item 39).
|
|
|
32
33
|
| `/settings hour hour` | registered | The preferred hour; recurring reminders with no time of their own move to it. |
|
|
33
34
|
| `/web` | registered | A one-time link to sign in to the web area (below), good for 10 minutes and one use. Says so when `TRACKER_WEB_URL` is unset. |
|
|
34
35
|
|
|
35
|
-
**Gates.** Every command and button checks, in order: membership of
|
|
36
|
-
server (when set), admission (in the tracker's store), then
|
|
37
|
-
`/allow`, admission for `/register`, registration for the
|
|
38
|
-
to ask an admin to `/allow` them. Decline and opt-out skip the membership and registration gates,
|
|
36
|
+
**Gates.** Every command and button checks, in order: membership of a `TRACKER_GUILD_ID`
|
|
37
|
+
server (when set; a member of any listed server passes), admission (in the tracker's store), then
|
|
38
|
+
what the action needs (an admin for `/allow`, admission for `/register`, registration for the
|
|
39
|
+
rest). A person nobody admitted is told to ask an admin to `/allow` them. Decline and opt-out skip the membership and registration gates,
|
|
39
40
|
so anyone can always stop the messages; the Reply button checks membership when its modal is sent.
|
|
40
41
|
|
|
41
42
|
**Buttons.** The owner's DM carries Done, Snooze 1h and Reply; a recipient's copy carries the
|
|
@@ -65,12 +66,15 @@ served at a hashed path, cached for a year).
|
|
|
65
66
|
| `/new/reminder`, `/new/renewal`, `/new/price` | The editor's new-task forms (GET), and making one (POST). |
|
|
66
67
|
| `/tasks/<id>/edit` | The owner's edit form (GET) and saving it (POST). |
|
|
67
68
|
| `/tasks/<id>/pause`, `/resume`, `/delete` | POST only. Delete answers a confirmation first; only a second post carrying `confirm=yes` deletes. |
|
|
68
|
-
| `/settings` | Preferred hour and time zone, checked as `/register` checks them. |
|
|
69
|
+
| `/settings` | Preferred hour and time zone, checked as `/register` checks them. Links to API tokens and Forget me. |
|
|
70
|
+
| `/tokens` | Your API tokens (below): each one's name, when it was made, last used and expires, with Revoke; and the form to make one (POST), which sends the new token to you by Discord DM -- never on the page. |
|
|
71
|
+
| `/tokens/<id>/revoke` | POST only: revokes one of your own tokens. Anyone else's answers the same 404 as an unknown id. |
|
|
69
72
|
| `/forget` | Forget me (below): what it deletes (GET); a POST asks for the confirmation; a POST with `confirm=yes` and the word `forget` erases. Linked from Settings. |
|
|
70
73
|
| `/admin` | Admins only: everyone on the list, the decline blocks in force (each with Lift), and a form to allow a person by Discord id. |
|
|
71
74
|
| `/admin/tasks` | Admins only: every task in the store, with its owner, status and how many receive it, each linking to its page. |
|
|
72
75
|
| `/admin/people/<id>` | Admins only: one person -- Discord id, registration, zone and hour, delivery, task counts, their tasks -- with Make admin or Revoke admin, Resume delivery (when paused), and Remove from the tracker. |
|
|
73
|
-
| `/admin/allow`, `/admin/people/<id>/grant`, `/revoke`, `/resume-delivery`, `/forget`, `/admin/blocks/<id>/lift` | Admins only, POST only: the acts below. |
|
|
76
|
+
| `/admin/allow`, `/admin/people/<id>/grant`, `/revoke`, `/resume-delivery`, `/forget`, `/admin/blocks/<id>/lift`, `/admin/tokens/<id>/revoke` | Admins only, POST only: the acts below. |
|
|
77
|
+
| `/api/v1/...` | The JSON task API (below): bearer tokens only, never the cookie. |
|
|
74
78
|
| `/signin` | Where a request that is not signed in is sent: says to run `/web`. |
|
|
75
79
|
| `/login?t=...` | The link `/web` gives. |
|
|
76
80
|
|
|
@@ -127,10 +131,10 @@ other than GET or POST (or the wrong one of the two for a path) is 405.
|
|
|
127
131
|
|
|
128
132
|
**Membership on the web.** With `TRACKER_GUILD_ID` set, `/web` issues a link only to a member,
|
|
129
133
|
and the session remembers when that was confirmed. A web request more than 15 minutes after the
|
|
130
|
-
last confirmation re-checks
|
|
131
|
-
queue), through the discord.js Client of an interaction the
|
|
132
|
-
(the host API has no member lookup of its own; the Client is
|
|
133
|
-
Not a member: every session of theirs ends, on a page that says why. A member: the time is
|
|
134
|
+
last confirmation re-checks membership (one lookup per listed server, all at once, the first yes enough; about 3
|
|
135
|
+
seconds at most, outside the write queue), through the discord.js Client of an interaction the
|
|
136
|
+
plugin has handled since it started (the host API has no member lookup of its own; the Client is
|
|
137
|
+
held in memory, never stored). Not a member of any listed server: every session of theirs ends, on a page that says why. A member: the time is
|
|
134
138
|
refreshed. A failed or slow lookup, or no interaction yet since a restart: they stay signed in
|
|
135
139
|
while the last confirmation is under 24 hours old, and are then signed out and told to run `/web`
|
|
136
140
|
again. Concurrent requests share one lookup per person, and after a failed one that person is not
|
|
@@ -162,6 +166,8 @@ the function the command path runs:
|
|
|
162
166
|
- **Resume delivery** of a person paused after failed DMs: `DeliveryHealth.resume`, what the
|
|
163
167
|
person's own next command does -- the count clears and the tasks nothing else holds go back on.
|
|
164
168
|
- **Lift** a decline block in force: docket's `liftBlock`, recorded as that admin's lift.
|
|
169
|
+
- **Revoke an API token** of anyone's, from their page, which lists their tokens (never the
|
|
170
|
+
secret). It stops working at once; the owner is not told. Logged by token id.
|
|
165
171
|
- **Remove from the tracker**: forget-me for that person (below), with the same confirmation.
|
|
166
172
|
|
|
167
173
|
Not here yet, though plan 5.10 lists them: grants, budgets and retry (they belong to the execute
|
|
@@ -178,7 +184,8 @@ in one SQLite transaction in the write queue, **deleted, not archived**:
|
|
|
178
184
|
made or that name them (an invitation of them, a pause for them), the run events and delivery
|
|
179
185
|
claims of DMs to them, and their pause rows;
|
|
180
186
|
- every decline block they are either side of;
|
|
181
|
-
- their admission, delivery health, web sessions
|
|
187
|
+
- their admission, delivery health, web sessions, unused sign-in links and API tokens, and their
|
|
188
|
+
person row.
|
|
182
189
|
|
|
183
190
|
What stays, and why: another person's task that they received stays that owner's; one paused only
|
|
184
191
|
because DMs to them failed goes back on. Another person's row keeps its own audit fact but not their
|
|
@@ -199,8 +206,8 @@ bot sent stay in the person's Discord DMs, and the host's log may hold their tra
|
|
|
199
206
|
|
|
200
207
|
Bytes: the database runs with `secure_delete` on, so a deleted row is overwritten, and the first
|
|
201
208
|
start of 0.6.0 on an existing database runs one `VACUUM`, so pages freed before that are rewritten
|
|
202
|
-
too. That one-time VACUUM is recorded in a small `tracker_meta` table, not by a schema bump
|
|
203
|
-
schema
|
|
209
|
+
too. That one-time VACUUM is recorded in a small `tracker_meta` table, not by a schema bump (0.6.0
|
|
210
|
+
kept the schema at 3; 0.7.0's API tokens are schema 4, see the CHANGELOG). If the VACUUM cannot run (a
|
|
204
211
|
reader holds the file), the start goes on, a warning is logged, and the next start tries again.
|
|
205
212
|
Not a VACUUM per erasure: it rewrites the whole file under an
|
|
206
213
|
exclusive lock, and `secure_delete` already covers every later delete. After an erasure the
|
|
@@ -223,6 +230,131 @@ are among the rows, so they are signed out everywhere at once, and the browser's
|
|
|
223
230
|
The `__Host-` prefix, which would stop that, requires `Path=/` and so cannot be used with
|
|
224
231
|
`Path=/tracker/`. Put the bot on a host whose sibling subdomains are all yours.
|
|
225
232
|
|
|
233
|
+
## Task API
|
|
234
|
+
|
|
235
|
+
A JSON API over your own tasks, for a program that acts as you -- a script, or the plain-language
|
|
236
|
+
intake agent the plan defers (E10, item 31), which would end its dialogue by posting a task here.
|
|
237
|
+
It is served under `/tracker/api/v1/` on the same origin as the web area, and only when
|
|
238
|
+
`TRACKER_WEB_URL` is set. Every write goes through the same functions as the web editor and the
|
|
239
|
+
slash commands -- the same fields, defaults, limits, caps and messages -- in the same write queue.
|
|
240
|
+
|
|
241
|
+
**Tokens.** Make one on the web area's `/tokens` page (linked from Settings): a name, and an expiry
|
|
242
|
+
of 30, 90 (the default) or 365 days -- every token expires. The token -- `trk_` and 43 characters --
|
|
243
|
+
is sent to you once, by Discord DM from the bot; it never appears in any web page or response, and
|
|
244
|
+
the tracker keeps only its SHA-256. If that DM cannot be delivered (DMs closed), the token is
|
|
245
|
+
deleted at once and the page says so: open your DMs and try again. At most 10 live tokens per
|
|
246
|
+
person, and at most 10 made per person in any rolling hour, revoked ones counted (each is a DM, so
|
|
247
|
+
a script riding your session cannot flood your DMs by making and revoking); the count is kept in
|
|
248
|
+
memory only, so a restart clears it. Each lists when it was made, last used (recorded at most once a minute) and expires, and
|
|
249
|
+
can be revoked there at once; an admin sees and revokes anyone's from the admin view's page for
|
|
250
|
+
that person. Forget-me erases a person's tokens with the rest. The log names a token by its id
|
|
251
|
+
(`k1`), never by its secret.
|
|
252
|
+
|
|
253
|
+
**Authentication.** `Authorization: Bearer <token>` on every request. The web area's session
|
|
254
|
+
cookie is never read here: every plugin shares one browser origin, so a cookie-authenticated JSON
|
|
255
|
+
API could be called by any script on it. A token acts as its owner with the owner's rights only,
|
|
256
|
+
and only on the owner's own tasks -- an admin's token included; an admin's wider reads stay on the
|
|
257
|
+
signed-in web pages. Every request re-reads the owner, as the web does a session: a person no
|
|
258
|
+
longer on the tracker, or no longer registered, is refused and their tokens deleted; with `TRACKER_GUILD_ID` set, membership
|
|
259
|
+
is re-checked on the web's schedule (after 15 minutes, one check, shared with the web), and one who
|
|
260
|
+
has left every listed server loses every token and every session; a lookup that keeps failing
|
|
261
|
+
lets them on for 24 hours from the last confirmation, then answers 503 until one succeeds (the token is kept).
|
|
262
|
+
A token is looked up by the SHA-256 of what was sent, so the comparison is over a hash the sender
|
|
263
|
+
cannot steer.
|
|
264
|
+
|
|
265
|
+
**Browsers.** No answer carries a CORS header, and a request with an `Origin` header -- which a
|
|
266
|
+
browser adds to every cross-origin request and to any same-origin one that is not a GET -- is
|
|
267
|
+
refused with 403: the API is for programs, not pages. A browser page cannot send a cross-origin
|
|
268
|
+
`Authorization` header without a CORS preflight, which is refused. A script of another plugin on
|
|
269
|
+
the same origin (or an XSS there) can ride a signed-in person's cookie and use the web area as
|
|
270
|
+
them for as long as the session lasts -- that is the shared origin's cost, as for the cookies above
|
|
271
|
+
-- but it cannot get a token's secret: the page that makes one never shows it, the secret goes
|
|
272
|
+
only to the person's Discord DMs (a token made that way is one they are told about and can
|
|
273
|
+
revoke), and every token expires within a year. Put nothing on that origin you do not trust.
|
|
274
|
+
|
|
275
|
+
**Limits.** Each token may make 60 requests at once, refilled at one per second; over that, `429`
|
|
276
|
+
with `Retry-After`. A limited request is refused before its owner is looked up. Requests whose
|
|
277
|
+
token does not look up (unknown, revoked, expired, malformed) share one global bucket of 30,
|
|
278
|
+
refilled at one every two seconds; past it they get `429` instead of `401`, and valid tokens are
|
|
279
|
+
unaffected. A body is at most
|
|
280
|
+
16 KiB, read no further. One new price tracker's page read in flight per person, shared with the
|
|
281
|
+
web editor (`409 busy`).
|
|
282
|
+
|
|
283
|
+
**Requests.** A `POST` or `PATCH` body is one JSON object with `Content-Type: application/json`
|
|
284
|
+
(else `415`), and names only the fields listed for that type (else `400 unknown_field`), each of
|
|
285
|
+
its JSON type: text as a string, a whole number or a number as a JSON number. A field left out of a
|
|
286
|
+
create gets the command's default; a field left out of an edit keeps what the task has, and an empty
|
|
287
|
+
string does the same, except that an empty `note` clears a renewal's note and an empty `name` gives
|
|
288
|
+
a price back the page's address -- exactly as the web editor's empty fields do. `null` is never a
|
|
289
|
+
value: it is refused as the wrong JSON type (send `""` to clear a note).
|
|
290
|
+
|
|
291
|
+
Which types: `GET /tasks`, `GET /tasks/<id>`, pause, resume and `DELETE` work on any task you own,
|
|
292
|
+
whatever its type; `POST /tasks` and `PATCH` take the three editor types (reminder, renewal,
|
|
293
|
+
price), and a `PATCH` of a task of another type answers `404`, as the web editor does.
|
|
294
|
+
|
|
295
|
+
| Method and path | What |
|
|
296
|
+
|---|---|
|
|
297
|
+
| `GET /api/v1/me` | The token's owner (id, name, zone, preferred hour) and the token (id, name, made, expires). |
|
|
298
|
+
| `GET /api/v1/types` | Each type's create and edit fields: name, JSON type, required (never, for an edit), description, and limits (`maxLength`, `minimum`, `maximum`, `enum`). This describes the tracker's own editor fields -- what these endpoints take -- and is **not** docket-core's `TaskType.intake` (`IntakeSpec`), which describes a type's config. |
|
|
299
|
+
| `GET /api/v1/tasks` | Your tasks that are not deleted -- active, paused and done -- oldest first. Not the ones shared with you. |
|
|
300
|
+
| `POST /api/v1/tasks` | Makes one: `type` is `reminder`, `renewal` or `price`, and the rest are that type's create fields. `201`, with `Location`. A price's page is read first, and nothing is made unless a price is found in it. |
|
|
301
|
+
| `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. |
|
|
302
|
+
| `PATCH /api/v1/tasks/<id>` | Edits it: the fields to change. A price's page is not editable. |
|
|
303
|
+
| `POST /api/v1/tasks/<id>/pause`, `/resume` | Body `{}`. As the web's Pause and Resume, and `/task resume`. |
|
|
304
|
+
| `DELETE /api/v1/tasks/<id>` | Deletes it as the web does: archived, nothing more sent, history kept. No confirmation step. |
|
|
305
|
+
|
|
306
|
+
A task is `{"id", "type", "title", "status", "cadence", "nextAt", "createdAt", "updatedAt",
|
|
307
|
+
"settings"}`, and a price also has a top-level `url`: `status` is `active`, `paused`, `done` or
|
|
308
|
+
`deleted`; `cadence` is the schedule in words, in your zone; `nextAt` the next run's instant (UTC),
|
|
309
|
+
null when paused or nothing is due; `settings` exactly the fields a `PATCH` takes, as the task has
|
|
310
|
+
them. A price's `url` is read-only -- another page is another tracker -- so it is not in `settings`
|
|
311
|
+
and a `PATCH` naming it is refused (`unknown_field`). A write answers
|
|
312
|
+
`{"task", "message"}`, `message` being the words the web and the command say.
|
|
313
|
+
|
|
314
|
+
**Errors** are `{"error": {"code": "...", "message": "..."}}`:
|
|
315
|
+
|
|
316
|
+
| Status | Code | When |
|
|
317
|
+
|---|---|---|
|
|
318
|
+
| 400 | `invalid` | A rule refused a value; `message` is the rule's own words, as the web editor shows them. Also a field of the wrong JSON type, or no `type`. |
|
|
319
|
+
| 400 | `unknown_field` | A field that type does not take (on a pause or resume, any field). |
|
|
320
|
+
| 400 | `invalid_json` | The body is not JSON, or not one object. |
|
|
321
|
+
| 401 | `unauthorized` | No `Authorization` header. With `WWW-Authenticate: Bearer realm="tracker"`. |
|
|
322
|
+
| 401 | `invalid_token` | A token unknown, revoked or expired, or whose owner is no longer registered (their tokens are then deleted): one answer for all. |
|
|
323
|
+
| 403 | `origin_refused` | The request carried an `Origin` header. |
|
|
324
|
+
| 403 | `not_member` | The owner has left every `TRACKER_GUILD_ID` server; their tokens are revoked. |
|
|
325
|
+
| 404 | `not_found` | No such endpoint, or no such task of yours. Anyone else's task -- one shared with you, or any task to an admin's token -- answers exactly as an unknown id. |
|
|
326
|
+
| 405 | `method_not_allowed` | With `Allow`. |
|
|
327
|
+
| 409 | `conflict` | A finished task edited, or a pause of a task not active (a resume of one not paused). |
|
|
328
|
+
| 409 | `limit_reached` | 200 active or paused tasks, or 20 price trackers, as the commands count them. |
|
|
329
|
+
| 409 | `busy` | Your last new price tracker's page is still being read. |
|
|
330
|
+
| 413 | `too_large` | Body over 16 KiB. |
|
|
331
|
+
| 415 | `unsupported_media_type` | Not `application/json`. |
|
|
332
|
+
| 429 | `rate_limited` | With `Retry-After`: this token's bucket, or the shared one for bad tokens. |
|
|
333
|
+
| 503 | `starting`, `membership_unknown`, `unavailable` | The plugin is starting; the owner's membership could not be checked for 24 hours; the type is not available on this bot. |
|
|
334
|
+
|
|
335
|
+
**Example.**
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
$ curl -s https://clerk.example.com/tracker/api/v1/tasks \
|
|
339
|
+
-H "Authorization: Bearer $TRACKER_TOKEN" -H "Content-Type: application/json" \
|
|
340
|
+
-d '{"type": "reminder", "text": "water the plants", "when": "tomorrow 9am", "repeat": "week"}'
|
|
341
|
+
{"task":{"id":"t7","type":"reminder","title":"water the plants","status":"active",
|
|
342
|
+
"cadence":"weekly on Fri at 9:00","nextAt":"2026-10-02T13:00:00.000Z",
|
|
343
|
+
"createdAt":"2026-10-01T12:00:00.000Z","updatedAt":"2026-10-01T12:00:00.000Z",
|
|
344
|
+
"settings":{"text":"water the plants","repeat":"week"}},
|
|
345
|
+
"message":"Reminder `t7` set: water the plants\nNext: Fri Oct 2, 9:00, weekly on Fri at 9:00."}
|
|
346
|
+
|
|
347
|
+
$ curl -s -X PATCH https://clerk.example.com/tracker/api/v1/tasks/t7 \
|
|
348
|
+
-H "Authorization: Bearer $TRACKER_TOKEN" -H "Content-Type: application/json" \
|
|
349
|
+
-d '{"repeat": "day"}'
|
|
350
|
+
|
|
351
|
+
$ curl -s -X POST https://clerk.example.com/tracker/api/v1/tasks/t7/pause \
|
|
352
|
+
-H "Authorization: Bearer $TRACKER_TOKEN" -H "Content-Type: application/json" -d '{}'
|
|
353
|
+
|
|
354
|
+
$ curl -s https://clerk.example.com/tracker/api/v1/tasks/t9 -H "Authorization: Bearer $TRACKER_TOKEN"
|
|
355
|
+
{"error":{"code":"not_found","message":"No such task."}}
|
|
356
|
+
```
|
|
357
|
+
|
|
226
358
|
## What it does
|
|
227
359
|
|
|
228
360
|
| Piece | File | Notes |
|
|
@@ -237,7 +369,8 @@ The `__Host-` prefix, which would stop that, requires `Path=/` and so cannot be
|
|
|
237
369
|
| Commands | `src/discord.ts`, `src/interactions.ts`, `src/discord-common.ts` (discord.js); `src/actions.ts`, `src/press.ts`, `src/history.ts`, `src/access.ts` | The discord.js files read options and render; the rest is Discord-free over the injected store, clock and notifier. Store writes are handled one at a time; Discord lookups and DMs run outside that queue. |
|
|
238
370
|
| Health | `src/health.ts` | `GET /tracker/healthz` (through the bot's HTTP router): `200` `ok`/`starting`, `503` `inactive`/`stale`/`blocked`. |
|
|
239
371
|
| Admin, forget-me | `src/admin.ts`, `src/roster.ts`; `src/web/admin.ts`, `src/web/admin-pages.ts` | The admin acts and forget-me, Discord-free; the SQL docket's Store has no method for (listing people and blocks, erasing a person); the routes and pages. |
|
|
240
|
-
| Web area | `src/web/` | `app.ts` authenticates and dispatches (pure over the injected store and clock), `routes.ts` names the paths; `pages.ts`, `editor-pages.ts` and `html.ts` render; `
|
|
372
|
+
| Web area | `src/web/` | `app.ts` authenticates and dispatches (pure over the injected store and clock), `routes.ts` names the paths; `pages.ts`, `editor-pages.ts` and `html.ts` render; `form-input.ts` reads the editor's fields and `editor.ts` calls the shared rules with them; `signin-link.ts` and `sessions.ts` hold the sign-in; `theme.ts` the stylesheet; `command.ts` is `/web`. |
|
|
373
|
+
| Task API | `src/web/api.ts`, `src/web/api-tasks.ts`, `src/web/api-tokens.ts`, `src/web/tokens.ts`, `src/web/token-pages.ts` | The JSON API's authentication, gates and rate limit; its task routes over editor.ts's writes; the tokens (SQL, hashed); the tokens page and its routes. |
|
|
241
374
|
| Task rules | `src/reminders.ts`, `src/tracked.ts`, `src/price.ts`, `src/edit.ts`, `src/manage.ts`, `src/limits.ts` | What makes, edits, pauses, resumes and deletes a task, and the limits on it, Discord-free, for the commands and the web editor alike. |
|
|
242
375
|
|
|
243
376
|
## Configuration
|
|
@@ -245,15 +378,17 @@ The `__Host-` prefix, which would stop that, requires `Path=/` and so cannot be
|
|
|
245
378
|
| Env key | Secret | Meaning |
|
|
246
379
|
|---|---|---|
|
|
247
380
|
| `TRACKER_ADMIN_DISCORD_IDS` | no | Comma-separated Discord user ids (spaces around commas allowed; an empty entry, as from a trailing comma, is refused and the plugin does not load) made admin at start. Unset = none. Removing an id does not revoke it. |
|
|
248
|
-
| `TRACKER_GUILD_ID` | no | The Discord server whose members may use the tracker. Checked through the interaction's client with a single-member lookup (no privileged intent). Unset = no membership gate, and a warning is logged each time the plugin activates; a malformed
|
|
381
|
+
| `TRACKER_GUILD_ID` | no | The Discord server whose members may use the tracker, or a comma-separated list of them (spaces around commas allowed; a repeated id counts once): a member of any listed server passes. Checked through the interaction's client with a single-member lookup per server (no privileged intent), all asked at once; a yes from any server is a member, a no from every server is not, and otherwise the answer is unknown (refused, never revoked). Inside a listed server, the person running a command needs no lookup. Unset = no membership gate, and a warning is logged each time the plugin activates; a malformed or empty entry anywhere in the list refuses to load, naming it. One store and one admission list serve every listed server; there are no per-server admins. |
|
|
249
382
|
| `TRACKER_WEB_URL` | no | The https origin the bot's HTTP is reached at through its tunnel, e.g. `https://clerk.example.com` (no path). `/web` links and the allowed `Origin` come from it, never from a request's `Host`. Unset = no web area (`/web` says so, the pages answer 404); anything but a bare https origin refuses to load. |
|
|
250
383
|
|
|
251
384
|
`/tracker/healthz` and the web area need the bot's `HTTP_PORT` set; without it there is no HTTP at all.
|
|
252
385
|
|
|
253
386
|
## Not yet
|
|
254
387
|
|
|
255
|
-
- The rest of the web area (E5): sharing a task from the web, the
|
|
256
|
-
|
|
388
|
+
- The rest of the web area (E5): sharing a task from the web, and the admin view's grants, budgets
|
|
389
|
+
and retry (with the execute lane). The task API covers reminders, renewals and prices only --
|
|
390
|
+
not `/task done`, `snooze`, `decide`, `share` or the settings -- and no intake agent uses it yet
|
|
391
|
+
(E10, deferred). Discord OAuth2 as a second sign-in
|
|
257
392
|
method, if chosen (plan item 41).
|
|
258
393
|
- The city-hall Executor adapter (the execute lane).
|
|
259
394
|
- Editing in Discord (the editor is on the web only), and changing a price tracker's page or `near` after it is made (make a new one); a free-form pattern for `price` (`near` is the safe subset: a user's regular expression run on a large page could hang the bot).
|