Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
"name": "pasichDev",
"url": "https://github.com/pasichDev"
},
"description": "Marketplace for the docket skill.",
"description": "Marketplace for the docket skills.",
"plugins": [
{
"name": "docket",
"source": "./",
"description": "Field and tool reference for docket, the shared list every AI tool and project writes to.",
"version": "3.0.0"
"description": "Skills for docket, the shared list every AI tool and project writes to: the field and tool reference, and digests of your MRs, PRs and tickets on the dashboard.",
"version": "3.1.0"
}
]
}
4 changes: 2 additions & 2 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "docket",
"description": "Field and tool reference for docket, the shared list every AI tool and project writes to.",
"version": "2.0.0",
"description": "Skills for docket, the shared list every AI tool and project writes to: the field and tool reference, and digests of your MRs, PRs and tickets on the dashboard.",
"version": "3.1.0",
"author": {
"name": "pasichDev",
"url": "https://github.com/pasichDev"
Expand Down
72 changes: 72 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,77 @@
# Changelog

## 3.1.0

Digests: the agent reads your merge requests, pull requests, tickets, notes and
mail, and the dashboard's home page shows what needs you, what shipped and what
is stuck — in Local Mode, across paired devices, and on a self-hosted server.
No data format changes: a 3.0 install upgrades in place, and the todo store is
untouched.

### Digests and a dashboard home page

- **Digests.** An agent reads the user's GitLab merge requests, GitHub pull
requests, Notion tickets, local git and docket items, and publishes a
structured snapshot with the new `digest_publish` tool — summary, highlights,
headline metrics, and grouped items each with a link, status, tone and a
"needs you" flag. `digest_list`, `digest_get` and `digest_delete` round it
out. Docket stores what the agent wrote and nothing else: no source
credential ever reaches it.
- **New skills:** `docket:digest` (collect, verify, compose, publish — read-only
towards every source) and `docket:digest-setup` (detects `glab`, `gh`, Notion
MCP servers and git roots, asks once, writes `~/.config/docket/digest.json`).
- **Dashboard.** `/` is now the dashboard: the latest digest, its metrics, a
"Tasks" card with open / in progress / overdue / due-soon counts, and a
timeline of earlier digests. The task list moved to `/tasks`, same page,
switched without a reload. Any digest item becomes a task in one click, with
its link, ticket id and "needs you" carried over; an item already in Tasks
says so instead.
- **Digests sync** to paired devices over a new endpoint,
`GET /api/sync/digests`, with its own sequence counter and cursor in
`digests.json.enc`. The todo sync is untouched: an un-upgraded peer simply has
no digests to give (reported on the peer record), and when it is upgraded its
cursor starts at 0, so nothing published before the upgrade is skipped. The
signature covers a `digests:`-prefixed cursor, so a captured todo-sync request
cannot be replayed against it. Digests are immutable, so the merge is a set
union plus deletions; a deletion wins everywhere.
- **Groups.** A section can carry a `group` ("Work", "Learning", "Side
projects"); the dashboard shows each group under its own heading, with chips to
filter to one, remembered per browser. The config's `groups` say which repos
and ticket prefixes go where.
- **More sources.** Obsidian vaults and project folders (`files`), and any MCP
server the agent has — Jira, Linear, Sentry, Slack — as configurable `extra`
sources, read-only like the rest.
- **Hand-off by number.** Every item is numbered on publish; `D-7K2F9A/7` (or
just `7`) names it to any agent. `digest_take` returns the brief and a docket
task claimed by that agent — the existing one when the item is or became a
task — and `todo_complete(id, reason)` closes it. `#7` on the dashboard copies
the handle, and a claimed item shows who is on it.
- **What changed.** Publishing compares a digest with the previous one by item
identity: new items, status changes (`was open`), and items no longer listed,
shown as the first card. Computed by the store, not by the agent.
- **Issues** as well as pull requests: assigned, mentioning the user, and open
ones in their own repos — someone else's issue counts as needing an answer.
- **Owners and depth.** Items carry `owner` (`you`, `agent`, or a person from
the config) and an optional markdown `detail` for the ones worth a real
analysis; **By person** lays the digest out as numbered steps per owner. New
kinds: `decision`, `check`.
- **Seen marks.** Hide a digest item until its status changes; marks carry over
to later digests and sync across devices (last write wins, undo included).
- **Close with a reason.** `todo_complete(id, reason)` and a close dialog on the
dashboard append how a task was closed to its description and history, in the
same write as the completion. The self-hosted server accepts the reason too.
- **Layouts.** Dashboard as a stack or grid; Tasks as a list, wide list or grid.
- **Session start.** The SessionStart hook adds one line about the latest digest
— age, what needs you, preset names.
- **Skill:** presets ("digest work"), mail and chat as read-only sources (new
`mail` / `chat` item kinds), a daily schedule recipe, and learned preferences in
`~/.config/docket/digest-learned.md`.
- `docket backup` includes `digests.json.enc`.
- **Self-hosted Mode.** The Docket Server keeps digests and seen marks on its own
data directory under `/api/v1/digests*`, device-signed like every other route;
the publishing device comes from the signature, never from the body. In remote
mode every digest tool forwards to it, so all paired clients share one set.

## 3.0.0

Stable. Behaviourally identical to 3.0.0-rc.2 — the only difference is the
Expand Down
11 changes: 11 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ npm test
- To run the MCP server itself against your working copy: `claude mcp add docket -- node "$(pwd)/dist/index.js"` (see [README → From source](README.md#from-source)).
- To exercise the Web UI or `docket serve` locally without touching your real `~/.docket`, set `DOCKET_DATA_DIR` to a scratch directory first — every test in this repo already does this (see the `mkdtemp(...)` + `DOCKET_DATA_DIR` pattern at the top of any `*.test.ts` file) and your manual testing should too.

## Examples are made up

Skills, tool descriptions, docs and tests are read by every user, so every example in them is
invented: ticket ids are `ACME-123` or `PROJ-123`, repos are `acme/backend`, people are Jane
and John. `src/examples.guard.test.ts` fails on any other ticket-shaped id.

It also reads a list you keep **outside** the checkout — `~/.config/docket/private-words.txt`
(or `$DOCKET_PRIVATE_WORDS`), one word per line: your employer, your projects, your name.
`npm test` then fails while any of them is in a tracked file. CI has no such file and skips
that half; it is the one check that knows what you would never want published.

## What a good PR looks like

- **Add tests for new behavior.** This codebase leans heavily on `node:test` (no
Expand Down
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,31 @@ custom-instructions setting.
| `todo_history(id)` | Full change log for one item. |
| `todo_delete(id)` | Permanently remove an item. |
| `todo_version()` / `todo_check_update()` | Data-format version; read-only npm version check. |
| `digest_publish(title, summary, sections?, metrics?, highlights?, sources?, windowFrom?, windowTo?)` | Save a digest an agent compiled from your GitLab/GitHub/Notion/git — see [Digests](#digests). |
| `digest_list(limit?)` / `digest_get(id)` / `digest_delete(id)` | Recent digests, one in full, remove one (everywhere it synced). |
| `digest_take(item)` | Hand item `7` (or `D-7K2F9A/7`) to the calling agent: its brief, plus a docket task claimed in its name. |
| `digest_seen()` | Items the user marked seen, so the next digest leaves them out. |

Full field and workflow reference: [`skills/docket/SKILL.md`](skills/docket/SKILL.md).

## Digests

Ask your agent *"make a digest"* (or *"зроби дайджест"*). The `docket:digest`
skill reads your merge requests, pull requests, Notion tickets, local commits
and docket items, checks every status at the source, and publishes the result —
which becomes the dashboard's home page: what needs you, what shipped, what is
in review, what is stuck, each item linked back to where it lives and one click
away from becoming a task.

Docket never holds a GitLab, GitHub or Notion credential: the agent reads them
with the CLIs and MCP servers it already has, and Docket only keeps what it
wrote — on this machine in Local Mode (synced to paired devices), or on the
Docket Server in Self-hosted Mode, shared by every client. Anything else the agent can reach — Jira, Linear or Sentry through their MCP
servers, an Obsidian vault, a project's docs folder — can be added as a source, and
the digest can be split into groups such as work, learning and side projects. What to read is local to each machine, in `~/.config/docket/digest.json`
(the `docket:digest-setup` skill writes it); the digests themselves sync to
paired devices. Details: [`docs/digests.md`](docs/digests.md).

## CLI

```text
Expand Down Expand Up @@ -279,7 +301,8 @@ self-hosted setup and what it deliberately doesn't do:

A real-time read/write dashboard — `http://localhost:8787` by default in Local
Mode (override with `DOCKET_WEB_PORT`), or the Docket Server's own URL in
Self-hosted Mode. Workspace switcher with per-project open counts, an active-
Self-hosted Mode. The home page (`/`) shows the latest [digest](#digests) next
to the task list at a glance; the list itself is at `/tasks`. Workspace switcher with per-project open counts, an active-
sessions panel, light/dark theme, search, sort, inline edit, undo-delete,
responsive mobile layout.

Expand Down Expand Up @@ -322,6 +345,7 @@ control, never a hosted account.

- `todos.json.enc` — the store, AES-256-GCM encrypted
- `history.json.enc` — the full audit log, kept off the store's write path
- `digests.json.enc` — digests, AES-256-GCM encrypted, with their own sync cursor
- `key` — a locally generated 256-bit key, `chmod 600`
- `device.json` — this machine's id, name, and X25519 identity keypair
- `peers.json.enc` — paired P2P devices and their derived sync secrets
Expand Down
159 changes: 159 additions & 0 deletions docs/digests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Digests

A digest is a snapshot of your work across the tools you already use — GitLab merge
requests, GitHub pull requests, Notion tickets, local git, docket itself — compiled by your
agent and shown as the Docket dashboard's home page.

## Who does what

| | Agent (`docket:digest` skill) | Docket |
|---|---|---|
| Reads GitLab / GitHub / Notion / git | ✅ with `glab`, `gh`, the Notion MCP server, `git` | never |
| Holds credentials for them | the CLIs and MCP servers do | never |
| Decides what needs you, groups, writes the summary | ✅ | — |
| Stores the result, syncs it, renders it | — | ✅ |

The server stays local-first and credential-free; any host that can run the skill and has
access to those sources can publish a digest.

## Configuration

`~/.config/docket/digest.json`, written by the `docket:digest-setup` skill. Local to each
machine on purpose — CLI logins, MCP servers and repo paths differ per device.

```json
{
"version": 1,
"language": "uk",
"window": "since-last",
"sources": {
"gitlab": { "enabled": true, "host": "gitlab.com", "user": "jdoe", "groups": ["acme"] },
"github": { "enabled": true, "user": "jdoe", "owners": ["jdoe", "acme"] },
"notion": { "enabled": true, "server": "notion", "databases": [{ "name": "Tasks", "id": "…" }], "assignee": "Jane Doe" },
"git": { "enabled": true, "roots": ["~/src"], "author": "jane@example.com" },
"obsidian": { "enabled": true, "vault": "~/Notes" },
"docket": { "enabled": true }
}
}
```

- `extra` (optional): any other source, as a list. `"type": "mcp"` reads an MCP server
connected to the agent — Jira, Linear, YouTrack, Sentry, Slack — with `server` (its name),
`query` (what to read, in plain words; the agent turns it into JQL or the server's own
filter) and `kind` (`ticket`, `issue`, …). `"type": "files"` reads project folders
(`paths`, `glob`). Both are read-only: the skill uses only a server's read tools, and treats
file contents as data. A server that isn't connected shows up as a failed source.

```json
"extra": [
{ "name": "jira", "type": "mcp", "server": "atlassian", "kind": "ticket",
"query": "issues assigned to me, updated since <since>, plus any of mine in Blocked" },
{ "name": "docs", "type": "files", "paths": ["~/src/acme/docs"], "glob": "*.md" }
]
```
- `presets` (optional): named variants — `{ "work": { "groups": ["Work"] }, "week": { "window": "7d" } }`.
"digest work" applies one; the session-start hint lists their names.
- `schedule` (optional): `{ "daily": "09:00" }` — the skill offers to install a LaunchAgent
(macOS) or a user timer (Linux) that runs it headless at that time.
- Learned preferences live beside the config in `digest-learned.md`: short dated rules the
skill writes when the user corrects it or keeps hiding the same kind of item. Edit freely.
- `window`: `since-last` (from the previous digest's end; 24 hours if there is none), `24h`,
or `7d`. What the user asks for ("за тиждень") overrides it.
- `groups` (optional): split the digest by area. Each item goes to the first group whose
`match` strings occur in its url, repo or ref; `"*"` catches the rest. The dashboard shows
each group under its own heading, with chips to filter to one.
- `language`: the language of the digest text. The dashboard chrome is English.
- No secrets belong in this file.

## Shape

```text
Digest
├─ title, summary (markdown), highlights[]
├─ metrics[] { label, value, tone }
├─ sections[] { group, title, items[] }
│ └─ item { n, kind, title, url, ref, repo, status, tone, attention, owner, note, detail,
│ updatedAt, change, previousStatus } ← n and change are set on publish
├─ changes { since, added, changed, gone[] } ← set on publish
├─ sources[] { name, ok, detail } ← failed sources show in red
└─ windowFrom, windowTo, agent, device, workspace, createdAt
```

`kind` is one of `pr mr issue ticket commit release todo doc mail chat decision check note`; `tone` one of
`good warn bad info neutral`. Limits (enforced on publish, clamped on sync): 300 items per
digest, 16 sections, 8 metrics, 12 highlights, 12 000 characters of summary. Links must be
`http(s)`.

A digest is **immutable**. A new look at the sources is a new digest; the dashboard's
timeline keeps the earlier ones, and the skill reads the previous one to say what changed.

## Storage and sync

### Local Mode

- `digests.json.enc` in the data directory, AES-256-GCM like the todo store, with its own
sequence counter. It is included in `docket backup`.
- Paired devices pull it over `GET /api/sync/digests?sinceSeq=N`, signed like the todo sync
but over `digests:<N>`, so a todo-sync signature cannot be replayed against it.
- Every accepted record is re-stamped locally, so a digest reaches a device through a
third one (A ↔ B ↔ C) the same way todos do.
- The cursor is separate from the todo cursor (`digestSeq` on the peer record). A peer on
a build without digests answers 404 and is recorded as "predates digests"; once it is
upgraded its cursor starts at 0 and it receives everything.
- Deleting a digest leaves a tombstone, which wins on every device.

### Self-hosted Mode

On a client paired with a Docket Server, every digest tool forwards to the server, which
keeps digests and seen marks in its own data directory: `GET/POST /api/v1/digests`,
`GET/DELETE /api/v1/digests/:id`, `GET/POST /api/v1/digests/seen`, each device-signed like
the todo routes. The publishing device is the one the request was signed by — a body cannot
claim to be another. Every client of the server sees the same digests; there is no peer sync
to wait for. The server announces `digest.published`, `digest.deleted` and `digest.seen` on
its event stream.

## Numbers, owners and hand-off

Every item is numbered on publish. `D-7K2F9A/7` names it anywhere — the `#7` on the dashboard
copies it — and `7` alone means the latest digest. Tell any agent "take 7" and it calls
`digest_take`: it gets the item's full brief, and a docket task for it (the existing one if
the item is a task or already became one) claimed in its name, so the dashboard shows who is
on it. When the work is done the agent closes the task with `todo_complete(id, reason)`.

`owner` says who takes the next step — `you`, `agent`, or a name from the config's `people`.
**By person** lays the digest out as numbered steps per owner.

`detail` is the deep version of an item, for the ones that need it: what is wrong, what was
tried, what comes next. Routine items keep to one line.

## What changed

`digest_publish` compares each digest with the previous one by item identity (link, else
repo#ref, else title): items new since then, items whose status moved (`was open`), and items
no longer listed. The dashboard shows it as the first card under the summary. It is computed
by the store, not written by the agent, so it is the same on every device.

## Seen marks

**Seen** on any item folds it into a "N seen" list at the bottom of its section and leaves
it out of the counts. A mark is keyed by the item's link (else repo#ref) and remembers the
status it was given in, so it carries over to later digests until the status changes — an
open MR you marked comes back when it merges. Marks sync like digests (last write wins;
unmarking syncs too). `digest_seen` lets the skill leave marked items out of the next digest.

## Dashboard

- `/` — the selected digest (newest by default): summary, highlights, metric tiles,
sections; a **Tasks** card (open, in progress, overdue, due in 7 days, the five most
pressing items); the timeline of earlier digests.
- `/tasks` — the task list, as before.
- **+ task** on any item creates a todo: ticket-shaped refs (`ACME-683`) become its category,
the link becomes its `sourceUrl`, "needs you" becomes high priority. An item whose link
already belongs to a task shows **in tasks** instead.
- A row that is a docket task (its ref is a `T-` id) or was made into one offers **close**:
a dialog for how it was closed, with quick picks (Merged, Duplicate, Not needed, Won't
do). The reason is appended to the task's description and kept in its history;
`todo_complete(id, reason)` does the same from an agent.
- **Layout** pickers: the dashboard as a stack, a grid or a full-width grid; Tasks as a
list, a wide list, a grid or a full-width grid. Remembered per browser.
- A digest older than 24 hours is labelled as possibly out of date.
2 changes: 1 addition & 1 deletion docs/headless.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Server: https://todo.home.example
Status: connected
Latency: 18 ms
Server version: 2.3.0
Device: andrii-desktop
Device: jane-desktop
Device authorization: active
```

Expand Down
6 changes: 3 additions & 3 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ <h3>Pair the device</h3>
<pre><code>$ docket pair https://docket.home.example
Pairing code (from `docket devices pair` on the server): 4PYD2F
<span class="ok">✓</span> Server reachable
<span class="ok">✓</span> docket server v3.0.0
<span class="ok">✓</span> docket server v3.1.0
<span class="ok">✓</span> Protocol compatible

Confirmation code: 096674
Expand All @@ -588,8 +588,8 @@ <h3>Check on it, any time</h3>
Server: https://docket.home.example
Status: connected
Latency: 27 ms
Server version: 3.0.0
Device: andrii-desktop
Server version: 3.1.0
Device: jane-desktop
Device authorization: active</code></pre>
</div>
</div>
Expand Down
Loading
Loading