issuary 0.1.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/LICENSE +15 -0
- package/README.md +519 -0
- package/dist/cli.js +3528 -0
- package/dist/cli.js.map +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucas Merencia
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
# issuary
|
|
2
|
+
|
|
3
|
+
CLI to monitor and AI-compact GitHub issues across multiple repositories.
|
|
4
|
+
|
|
5
|
+
`issuary` keeps a local, incremental mirror of the issues in the repos you watch,
|
|
6
|
+
tells you what changed since the last sync (new issues, closed issues, new
|
|
7
|
+
comments), and offers a compaction layer: structured summaries written and
|
|
8
|
+
consumed by AIs so an agent can understand a whole project's issues without
|
|
9
|
+
re-fetching from GitHub or blowing its context window.
|
|
10
|
+
|
|
11
|
+
The name is "issuary" (issue + -ary): an archive of a project's issues, distilled
|
|
12
|
+
into something an agent can read at a glance.
|
|
13
|
+
|
|
14
|
+
## Core idea
|
|
15
|
+
|
|
16
|
+
- **Local incremental mirror.** `issuary` mirrors issues from many repos into a
|
|
17
|
+
local SQLite database and only fetches what changed since the last sync.
|
|
18
|
+
- **Change detection.** Each sync records events (opened, closed, reopened, new
|
|
19
|
+
comments) so you can see what moved across every watched repo at a glance.
|
|
20
|
+
- **AI compaction layer.** `issuary` never calls an LLM. It stores raw issue
|
|
21
|
+
content, exposes which issues need a summary, and accepts the summary back. The
|
|
22
|
+
agent that consumes the tool is the one that writes the summaries. A compact
|
|
23
|
+
saves context tokens for that agent, not disk space: the raw is never deleted.
|
|
24
|
+
|
|
25
|
+
The core (mirror, change detection, digests) works on its own. Compaction is an
|
|
26
|
+
optional layer on top.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npm install -g issuary
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Requirements:
|
|
35
|
+
|
|
36
|
+
- **Node.js >= 20.**
|
|
37
|
+
- **A GitHub token.** Either export `GITHUB_TOKEN` (a personal access token with
|
|
38
|
+
read access to the repos you watch, the `repo` scope or `public_repo` for
|
|
39
|
+
public repos only) or run `issuary login` to authenticate via the browser. See
|
|
40
|
+
[Authentication](#authentication). Commands that hit the GitHub API (`add`,
|
|
41
|
+
`sync`, and `show --raw`) require a token; purely local commands do not.
|
|
42
|
+
|
|
43
|
+
### Environment variables
|
|
44
|
+
|
|
45
|
+
| Variable | Purpose | Default |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `GITHUB_TOKEN` | GitHub personal access token used to reach the API. Takes precedence over a token stored by `issuary login`. | (required for API commands unless `issuary login` was run) |
|
|
48
|
+
| `GITHUB_API_URL` | REST API base URL. Set this for GitHub Enterprise, e.g. `https://github.example.com/api/v3`. Trailing slashes are trimmed. | `https://api.github.com` |
|
|
49
|
+
| `ISSUARY_HOME` | Directory holding local state (the SQLite database and `issuary login` credentials). | `~/.issuary` |
|
|
50
|
+
| `ISSUARY_GITHUB_CLIENT_ID` | OAuth App client id used by `issuary login` (device flow). Overrides the baked-in default. | (build default) |
|
|
51
|
+
| `ISSUARY_GITHUB_SCOPE` | OAuth scope requested by `issuary login`. | `repo` |
|
|
52
|
+
|
|
53
|
+
The database lives at `$ISSUARY_HOME/db.sqlite` (so `~/.issuary/db.sqlite` by default).
|
|
54
|
+
|
|
55
|
+
## Authentication
|
|
56
|
+
|
|
57
|
+
Commands that hit the GitHub API (`add`, `sync`, `show --raw`) need a token.
|
|
58
|
+
There are two ways to provide one:
|
|
59
|
+
|
|
60
|
+
1. **Export a token.** Set `GITHUB_TOKEN` to a GitHub personal access token with
|
|
61
|
+
read access to the repos you watch (the `repo` scope, or `public_repo` for
|
|
62
|
+
public repos only):
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
export GITHUB_TOKEN=ghp_...
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
2. **`issuary login` (device flow).** Authenticate in the browser, no manual token
|
|
69
|
+
handling:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
issuary login
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
It prints a short code and a URL. Open the URL, enter the code, and approve.
|
|
76
|
+
`issuary` then stores the resulting token and confirms with `Logged in as <you>.`
|
|
77
|
+
The default scope requested is `repo` so private repos work; override it with
|
|
78
|
+
`ISSUARY_GITHUB_SCOPE` if you only need public access. `issuary login --json` emits
|
|
79
|
+
`{ "ok": true, "login": "<you>", "scopes": [...] }`. The token itself is never
|
|
80
|
+
printed.
|
|
81
|
+
|
|
82
|
+
**Precedence.** When both are present, the `GITHUB_TOKEN` environment variable
|
|
83
|
+
wins over the stored token. So an explicitly exported token always takes effect,
|
|
84
|
+
and `issuary login` is the fallback when no env token is set.
|
|
85
|
+
|
|
86
|
+
**Where the token is stored.** `issuary login` writes the token to
|
|
87
|
+
`~/.issuary/credentials.json` (under `$ISSUARY_HOME`), created with file mode `0600`
|
|
88
|
+
(owner read/write only). The token is never logged.
|
|
89
|
+
|
|
90
|
+
**Log out.** `issuary logout` removes the stored token locally:
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
issuary logout
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This only deletes the local credentials file; it does not revoke the token on
|
|
97
|
+
GitHub. `issuary logout --json` emits `{ "ok": true, "removed": boolean }`.
|
|
98
|
+
|
|
99
|
+
### Maintainer setup (device login)
|
|
100
|
+
|
|
101
|
+
`issuary login` uses the GitHub OAuth **device flow**, which requires a registered
|
|
102
|
+
GitHub OAuth App with "Device Flow" enabled. The app's **public** client id must
|
|
103
|
+
be available to the CLI: either baked into `DEFAULT_GITHUB_CLIENT_ID` in
|
|
104
|
+
`src/auth/client-id.ts` (a device-flow client id is not a secret, so it is safe
|
|
105
|
+
to commit) or supplied at runtime via the `ISSUARY_GITHUB_CLIENT_ID` environment
|
|
106
|
+
variable. Until a client id is configured, `issuary login` exits with a clear error;
|
|
107
|
+
the `GITHUB_TOKEN` path keeps working regardless.
|
|
108
|
+
|
|
109
|
+
## Quickstart
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
# 1. Watch a couple of repos (each is validated against the API).
|
|
113
|
+
issuary add octocat/hello-world
|
|
114
|
+
issuary add facebook/react
|
|
115
|
+
|
|
116
|
+
# 2. Mirror their issues locally (incremental: only what changed is fetched).
|
|
117
|
+
issuary sync
|
|
118
|
+
|
|
119
|
+
# 3. See what changed everywhere, as an aggregated inbox.
|
|
120
|
+
issuary digest
|
|
121
|
+
|
|
122
|
+
# 4. List what is open right now, across all repos (read-only, no API calls).
|
|
123
|
+
issuary issues
|
|
124
|
+
|
|
125
|
+
# 5. Get the full project-wide view of one repo's issues.
|
|
126
|
+
issuary repo-digest facebook/react
|
|
127
|
+
|
|
128
|
+
# 6. Read a single issue (compact if present, otherwise raw body).
|
|
129
|
+
issuary show facebook/react#123
|
|
130
|
+
|
|
131
|
+
# Read the same issue's full raw body and comments.
|
|
132
|
+
issuary show facebook/react#123 --raw
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Every command also supports `--json` for machine and AI consumption.
|
|
136
|
+
|
|
137
|
+
## Command reference
|
|
138
|
+
|
|
139
|
+
All commands accept `--json`, which prints a single JSON document to stdout and
|
|
140
|
+
suppresses the human formatting. Expected, user-facing errors (a malformed
|
|
141
|
+
argument, an unwatched repo, a missing issue) print a message to stderr and exit
|
|
142
|
+
with a non-zero status.
|
|
143
|
+
|
|
144
|
+
Four commands answer four different questions, so it helps to keep them apart:
|
|
145
|
+
|
|
146
|
+
- `issuary list` lists the **repos** you watch.
|
|
147
|
+
- `issuary issues` is the **filterable issue list**: "what issues match these
|
|
148
|
+
filters right now?" (state, repo, label, author, since, search, compaction).
|
|
149
|
+
- `issuary digest` is the **inbox**: "what changed since I last looked?"
|
|
150
|
+
- `issuary repo-digest` is **one project's full memory**: every issue of a single
|
|
151
|
+
repo, compacted where possible.
|
|
152
|
+
|
|
153
|
+
### `issuary add <owner/repo>`
|
|
154
|
+
|
|
155
|
+
Start watching a repo. Validates that the repo exists and is accessible via the
|
|
156
|
+
GitHub API before recording it. Re-adding a previously removed repo reactivates
|
|
157
|
+
it. Requires `GITHUB_TOKEN`.
|
|
158
|
+
|
|
159
|
+
- Argument: `<owner/repo>`, e.g. `octocat/hello-world`.
|
|
160
|
+
- `--json` emits `{ "ok": true, "repo": "<owner/repo>", "status": "added" | "already-watched" | "reactivated" }`.
|
|
161
|
+
|
|
162
|
+
### `issuary remove <owner/repo>`
|
|
163
|
+
|
|
164
|
+
Stop watching a repo. This deactivates it; it never deletes, so the repo's
|
|
165
|
+
issues and compacts are kept. Local only, no token required.
|
|
166
|
+
|
|
167
|
+
- Argument: `<owner/repo>`.
|
|
168
|
+
- `--json` emits `{ "ok": true, "repo": "<owner/repo>", "status": "removed" | "already-inactive" }`.
|
|
169
|
+
|
|
170
|
+
### `issuary list`
|
|
171
|
+
|
|
172
|
+
List watched repos with their state and last sync time. Active repos first, then
|
|
173
|
+
inactive. Local only.
|
|
174
|
+
|
|
175
|
+
- `--json` emits an array of `{ "repo": "<owner/repo>", "active": boolean, "lastSyncedAt": string | null }`.
|
|
176
|
+
|
|
177
|
+
### `issuary sync [repo]`
|
|
178
|
+
|
|
179
|
+
Fetch issue updates for watched repos and record what changed. With no argument
|
|
180
|
+
it syncs every active repo; with a `[repo]` argument it limits the sync to that
|
|
181
|
+
single watched repo. The fetch is incremental (see [How it works](#how-it-works)).
|
|
182
|
+
Requires `GITHUB_TOKEN`.
|
|
183
|
+
|
|
184
|
+
- Argument (optional): `[repo]` as `owner/repo`.
|
|
185
|
+
- `--quiet`: print nothing when there was no activity across all repos (no
|
|
186
|
+
events and no errors), so a scheduled/cron run stays silent on a no-op cycle.
|
|
187
|
+
A concise summary is still printed when something changed, and failed repos
|
|
188
|
+
are always printed. Has no effect on `--json`. See [Scheduling](#scheduling).
|
|
189
|
+
- `--json` emits `{ "repos": [ { "repo", "notModified", "opened", "closed", "reopened", "commented", "processed" } ] }`,
|
|
190
|
+
one entry per synced repo. `notModified` is `true` when the repo returned a 304
|
|
191
|
+
(nothing changed); the counts are then all zero.
|
|
192
|
+
|
|
193
|
+
The command exits `0` on success (even when nothing changed) and non-zero when
|
|
194
|
+
any repo failed to sync, so a scheduler or monitor can detect failures.
|
|
195
|
+
|
|
196
|
+
### `issuary digest`
|
|
197
|
+
|
|
198
|
+
Show an aggregated inbox of issue changes across all watched repos, grouped by
|
|
199
|
+
repo and then by change type (new issues, closed, new comments, closed with new
|
|
200
|
+
comment, reopened).
|
|
201
|
+
|
|
202
|
+
Three modes:
|
|
203
|
+
|
|
204
|
+
- **Default (inbox):** shows unseen events, then marks them seen so each change
|
|
205
|
+
appears only once.
|
|
206
|
+
- `--since <when>`: a read-only time window showing events at or after `<when>`.
|
|
207
|
+
Accepts an ISO-8601 date or a simple relative duration: `<n>d` (days) or
|
|
208
|
+
`<n>h` (hours), e.g. `7d` or `24h`. Does not mark anything seen.
|
|
209
|
+
- `--all`: every event, seen and unseen. Does not mark anything seen.
|
|
210
|
+
|
|
211
|
+
Options:
|
|
212
|
+
|
|
213
|
+
- `--since <when>`: ISO date or `Nd` / `Nh` duration.
|
|
214
|
+
- `--all`: show all events without marking any seen.
|
|
215
|
+
- `--repo <owner/repo>`: narrow any mode to a single watched repo.
|
|
216
|
+
- `--json` emits `{ "mode": "inbox" | "since" | "all", "total": number, "repos": [ { "repo", "groups": [ { "type", "events": [...] } ] } ] }`.
|
|
217
|
+
|
|
218
|
+
Local only, no token required.
|
|
219
|
+
|
|
220
|
+
### `issuary issues`
|
|
221
|
+
|
|
222
|
+
List issues across watched repos, with filters. Read-only: it never calls the
|
|
223
|
+
GitHub API and never changes local state (it does not mark anything seen). With
|
|
224
|
+
no flags it shows OPEN issues across all watched repos, sorted by most recently
|
|
225
|
+
updated, grouped by repo, with a count header. Local only, no token required.
|
|
226
|
+
|
|
227
|
+
Options:
|
|
228
|
+
|
|
229
|
+
- `--state <open|closed|all>`: which issues to include (default `open`).
|
|
230
|
+
- `--repo <owner/repo>`: scope to a watched repo. Repeatable to pass several.
|
|
231
|
+
- `--label <name>`: match issues carrying any of these labels. Repeatable; the
|
|
232
|
+
labels are OR-ed (an issue matches if it has at least one).
|
|
233
|
+
- `--author <login>`: restrict to issues opened by this user.
|
|
234
|
+
- `--state-reason <completed|not_planned>`: restrict by GitHub's close reason.
|
|
235
|
+
- `--since <when>`: only issues with `updated_at >=` an ISO date or a relative
|
|
236
|
+
duration (`Nd` / `Nh`, e.g. `7d`, `24h`).
|
|
237
|
+
- `--search <text>`: case-insensitive substring match on the issue title.
|
|
238
|
+
- `--uncompacted` | `--stale` | `--compacted`: filter by compaction state.
|
|
239
|
+
Mutually exclusive (passing more than one is an error).
|
|
240
|
+
- `--sort <updated|created|number>` (default `updated`) and
|
|
241
|
+
`--order <asc|desc>` (default `desc`).
|
|
242
|
+
- `--limit <n>`: cap the number of issues returned.
|
|
243
|
+
- `--json` (see shape below).
|
|
244
|
+
|
|
245
|
+
Examples:
|
|
246
|
+
|
|
247
|
+
```sh
|
|
248
|
+
# What is open right now, everywhere.
|
|
249
|
+
issuary issues
|
|
250
|
+
|
|
251
|
+
# Everything, including closed.
|
|
252
|
+
issuary issues --state all
|
|
253
|
+
|
|
254
|
+
# One project, only bugs.
|
|
255
|
+
issuary issues --repo facebook/react --label bug
|
|
256
|
+
|
|
257
|
+
# Issues touched in the last week.
|
|
258
|
+
issuary issues --since 7d
|
|
259
|
+
|
|
260
|
+
# Issues whose memory still needs writing.
|
|
261
|
+
issuary issues --uncompacted
|
|
262
|
+
|
|
263
|
+
# Find by title, as JSON for an agent.
|
|
264
|
+
issuary issues --search "timezone" --json
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Sample human output:
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
3 open issues across 2 repos (filter: labels=bug)
|
|
271
|
+
|
|
272
|
+
facebook/react:
|
|
273
|
+
#321 [open] Hooks break with timezones {bug, timezone} (4c) (uncompacted)
|
|
274
|
+
#204 [open] Crash on hydrate {bug} (2c)
|
|
275
|
+
|
|
276
|
+
octocat/hello-world:
|
|
277
|
+
#12 [open] Typo in error message {bug}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The `{...}` are labels, `(Nc)` is the comment count, and a trailing `(stale)` or
|
|
281
|
+
`(uncompacted)` marks issues whose compact is missing or out of date (nothing is
|
|
282
|
+
shown when the compact is fresh).
|
|
283
|
+
|
|
284
|
+
`--json` emits:
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
{
|
|
288
|
+
"filters": {
|
|
289
|
+
"state": "open", "repos": null, "labels": ["bug"], "author": null,
|
|
290
|
+
"stateReason": null, "since": null, "search": null, "compaction": null,
|
|
291
|
+
"sort": "updated", "order": "desc", "limit": null
|
|
292
|
+
},
|
|
293
|
+
"summary": { "total": 3, "open": 3, "closed": 0, "repos": 2 },
|
|
294
|
+
"issues": [
|
|
295
|
+
{
|
|
296
|
+
"repo": "facebook/react", "number": 321, "title": "Hooks break with timezones",
|
|
297
|
+
"state": "open", "stateReason": null, "author": "ann",
|
|
298
|
+
"labels": ["bug", "timezone"], "commentCount": 4,
|
|
299
|
+
"createdAt": "...", "updatedAt": "...",
|
|
300
|
+
"compact": null, "compactTldr": null, "compacted": false, "stale": false,
|
|
301
|
+
"refs": ["#204"]
|
|
302
|
+
}
|
|
303
|
+
]
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
The `compact` field carries the full canonical compact when one exists,
|
|
308
|
+
`compactTldr` its one-line headline, and `compacted` / `stale` say whether it is
|
|
309
|
+
fresh. Raw bodies and comments are intentionally not included here; use
|
|
310
|
+
`issuary show <repo>#<n> --raw` for those.
|
|
311
|
+
|
|
312
|
+
### `issuary repo-digest <repo>`
|
|
313
|
+
|
|
314
|
+
Consume all issues of one watched repo as a project-wide, AI-optimized view. For
|
|
315
|
+
each issue it prefers a fresh compact and falls back to the raw body, flagging
|
|
316
|
+
which issues an AI may want to (re)compact. The header summarizes totals (open,
|
|
317
|
+
closed, compacted, stale or uncompacted). Local only.
|
|
318
|
+
|
|
319
|
+
- Argument: `<repo>` as `owner/repo`.
|
|
320
|
+
- `--headlines`: list every issue using only its cheap `tldr` headline (roughly
|
|
321
|
+
20 tokens per issue), falling back to the issue title when there is no `tldr`.
|
|
322
|
+
- `--json` (full) emits `{ "repo", "summary": { "total", "open", "closed", "compacted", "staleOrUncompacted" }, "issues": [ { "number", "state", "stateReason", "title", "representation", "compacted", "stale", "refs" } ] }`.
|
|
323
|
+
- `--headlines --json` emits `{ "repo", "summary": {...}, "headlines": [ { "number", "state", "headline", "fromTldr" } ] }`.
|
|
324
|
+
|
|
325
|
+
### `issuary show <target>`
|
|
326
|
+
|
|
327
|
+
Display a single issue from the local store. By default it shows the compact if a
|
|
328
|
+
fresh one exists, otherwise the raw body. Local only by default.
|
|
329
|
+
|
|
330
|
+
- Argument: `<target>` as `owner/repo#number`, e.g. `facebook/react#123`.
|
|
331
|
+
- `--raw`: include the full raw body and the comment thread. Comments are fetched
|
|
332
|
+
on demand the first time and then cached, so `--raw` requires `GITHUB_TOKEN`.
|
|
333
|
+
- `--json` emits the issue's fields: `{ "repo", "number", "title", "state", "stateReason", "author", "labels", "commentCount", "createdAt", "updatedAt", "closedAt", "compact", "compactStale", "rawBody", "refs" }`, plus `"comments"` when `--raw` is set.
|
|
334
|
+
|
|
335
|
+
### `issuary compact list`
|
|
336
|
+
|
|
337
|
+
List issues with their compaction status (`compacted`, `stale`, or
|
|
338
|
+
`uncompacted`), grouped by repo. Local only.
|
|
339
|
+
|
|
340
|
+
- `--pending`: narrow to the actionable set, only issues that are uncompacted or
|
|
341
|
+
stale (the work an AI needs to do). Each pending item carries a `reason`.
|
|
342
|
+
- `--repo <owner/repo>`: restrict to a single watched repo.
|
|
343
|
+
- `--json` emits an array of `{ "repo", "number", "title", "state", "status", "reason", "rawBody", "commentsNeedFetch" }`.
|
|
344
|
+
`reason` is `"uncompacted"` or `"stale"` for pending issues and `null` for
|
|
345
|
+
fresh ones. `commentsNeedFetch` is `true` when the issue has comments that have
|
|
346
|
+
not been pulled yet, a hint to run `issuary show <repo>#<n> --raw` before
|
|
347
|
+
compacting.
|
|
348
|
+
|
|
349
|
+
### `issuary compact set <target> --from-file <file>`
|
|
350
|
+
|
|
351
|
+
Persist a compact for an issue from a file in the canonical format. The file is
|
|
352
|
+
parsed and validated; an invalid compact is rejected. Saving a compact clears the
|
|
353
|
+
issue's stale flag. Local only.
|
|
354
|
+
|
|
355
|
+
- Argument: `<target>` as `owner/repo#number`.
|
|
356
|
+
- `--from-file <file>` (required): path to the compact file to read.
|
|
357
|
+
- `--json` emits `{ "ok": true, "repo", "number", "tldr" }`.
|
|
358
|
+
|
|
359
|
+
### `issuary protocol`
|
|
360
|
+
|
|
361
|
+
Print the AI compaction protocol, the contract AI consumers follow. This is the
|
|
362
|
+
self-describing usage that an agent can read to discover how compaction works.
|
|
363
|
+
|
|
364
|
+
- `--json` emits `{ "protocol": string, "compactFormat": { "doc", "frontmatterFields", "bodyFields", "persistCommand" } }`.
|
|
365
|
+
|
|
366
|
+
### `issuary skill`
|
|
367
|
+
|
|
368
|
+
Emit issuary's neutral agent skill, or install it for an AI agent. The content is
|
|
369
|
+
vendor-neutral: it teaches an agent what issuary is, when to reach for it, and where
|
|
370
|
+
to find the exact contract (`issuary protocol`, `issuary --help`).
|
|
371
|
+
|
|
372
|
+
- No flags: print the skill to stdout. This is the universal path: paste it into
|
|
373
|
+
any agent's system prompt or rules file.
|
|
374
|
+
- `--install --format claude` (the default format): write
|
|
375
|
+
`~/.claude/skills/issuary/SKILL.md` (override the skills root with `--dir` or
|
|
376
|
+
`CLAUDE_SKILLS_DIR`).
|
|
377
|
+
- `--install --format agents`: insert or replace a delimited, idempotent issuary
|
|
378
|
+
section in an `AGENTS.md` at the project root (override the directory with
|
|
379
|
+
`--dir`). Running it twice yields exactly one section; existing unrelated
|
|
380
|
+
content is preserved.
|
|
381
|
+
- `--json` emits `{ "name", "description", "path", "content", "format" }`.
|
|
382
|
+
|
|
383
|
+
### `issuary login`
|
|
384
|
+
|
|
385
|
+
Authenticate with GitHub via the OAuth device flow and store the token at
|
|
386
|
+
`~/.issuary/credentials.json` (mode `0600`). Prints a user code and a verification
|
|
387
|
+
URL to open in the browser, polls until you authorize, then confirms with
|
|
388
|
+
`Logged in as <you>.` See [Authentication](#authentication).
|
|
389
|
+
|
|
390
|
+
- `--json` emits `{ "ok": true, "login": "<you>", "scopes": [...] }`.
|
|
391
|
+
|
|
392
|
+
### `issuary logout`
|
|
393
|
+
|
|
394
|
+
Remove the locally stored token. Local only; it does not revoke the token on
|
|
395
|
+
GitHub.
|
|
396
|
+
|
|
397
|
+
- `--json` emits `{ "ok": true, "removed": boolean }`.
|
|
398
|
+
|
|
399
|
+
## For AI agents
|
|
400
|
+
|
|
401
|
+
`issuary` does not call any LLM itself. It stores raw issue content, exposes which
|
|
402
|
+
issues need a summary, and accepts the summary back. The agent that consumes the
|
|
403
|
+
tool is the compaction CPU; `issuary` only stores and serves.
|
|
404
|
+
|
|
405
|
+
### issuary vs GitHub's MCP
|
|
406
|
+
|
|
407
|
+
They are complementary, not competing. GitHub's MCP server gives live, raw access
|
|
408
|
+
to issues, use it when you need the current, unfiltered state of an issue or its
|
|
409
|
+
comment thread. `issuary` is not another raw-issue reader: its value is the
|
|
410
|
+
persistent, compacted memory of issues plus the cross-repo digest of what changed
|
|
411
|
+
since you last looked. Use GitHub's MCP for live, raw access, and `issuary` for the
|
|
412
|
+
distilled memory and the "what changed" digest.
|
|
413
|
+
|
|
414
|
+
### Reading the memory with filters
|
|
415
|
+
|
|
416
|
+
`issuary issues --json` is the filtered entry point into the memory. Pass any of
|
|
417
|
+
the filters (`--state`, `--repo`, `--label`, `--author`, `--since`, `--search`,
|
|
418
|
+
`--uncompacted` / `--stale` / `--compacted`) and you get back the matching issues
|
|
419
|
+
with their `compact`, `compactTldr`, `refs`, and the `compacted` / `stale` flags,
|
|
420
|
+
without raw bodies. It complements the other two read paths:
|
|
421
|
+
`issuary repo-digest <repo> --json` for one project's full dump, and
|
|
422
|
+
`issuary show <repo>#<n> --json` for a single issue (add `--raw` for the body and
|
|
423
|
+
comments). Reach for `issues --json` when you want a slice of the memory ("open
|
|
424
|
+
bugs across all repos", "anything touched this week", "what still needs
|
|
425
|
+
compacting") rather than a whole project or a single issue.
|
|
426
|
+
|
|
427
|
+
### Teaching an agent to use issuary
|
|
428
|
+
|
|
429
|
+
`issuary skill` emits a neutral skill document that explains all of this. Print it
|
|
430
|
+
(`issuary skill`) and paste it into any agent's system prompt or rules file, or
|
|
431
|
+
install it: `issuary skill --install --format claude` writes
|
|
432
|
+
`~/.claude/skills/issuary/SKILL.md` for Claude Code, and
|
|
433
|
+
`issuary skill --install --format agents` inserts an idempotent issuary section into a
|
|
434
|
+
project `AGENTS.md`. See the [`issuary skill`](#issuary-skill) command reference.
|
|
435
|
+
|
|
436
|
+
Each issue carries two fields that drive the workflow:
|
|
437
|
+
|
|
438
|
+
- `compact`: the AI-written structured summary, or `null` if none exists.
|
|
439
|
+
- `compact_stale`: `true` when the compact no longer reflects the issue (set by
|
|
440
|
+
`sync` when a new comment lands on an already-compacted issue).
|
|
441
|
+
|
|
442
|
+
The protocol:
|
|
443
|
+
|
|
444
|
+
1. **If `compact != null` and `compact_stale == false`, use the compact.** Do not
|
|
445
|
+
read the raw, do not recompact. It is trusted and current.
|
|
446
|
+
2. **If `compact == null` or `compact_stale == true`, recompact.** Read the raw,
|
|
447
|
+
write a fresh compact in the canonical format, and persist it.
|
|
448
|
+
|
|
449
|
+
A typical agent loop:
|
|
450
|
+
|
|
451
|
+
```sh
|
|
452
|
+
# 1. Find the work: issues that are uncompacted or stale.
|
|
453
|
+
issuary compact list --pending --json
|
|
454
|
+
|
|
455
|
+
# 2. Read the raw body and comments for one of them
|
|
456
|
+
# (comments are fetched on demand).
|
|
457
|
+
issuary show owner/repo#123 --raw --json
|
|
458
|
+
|
|
459
|
+
# 3. Write a compact in the canonical format to a file, then persist it.
|
|
460
|
+
# Persisting clears the stale flag.
|
|
461
|
+
issuary compact set owner/repo#123 --from-file compact.md
|
|
462
|
+
|
|
463
|
+
# 4. Re-compact whenever an issue goes stale again after a future sync.
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Run `issuary protocol` to get the contract as text (or `issuary protocol --json` for
|
|
467
|
+
the structured form). The full, authoritative, field-by-field compact format,
|
|
468
|
+
with rules and worked examples, is in
|
|
469
|
+
[docs/compact-format.md](./docs/compact-format.md).
|
|
470
|
+
|
|
471
|
+
To automate this loop, see the optional auto-compaction worker in
|
|
472
|
+
[examples/auto-compact/](./examples/auto-compact/): a small companion script that
|
|
473
|
+
batches the pending set (`compact list --pending --limit`), calls an LLM, and
|
|
474
|
+
writes the compacts back. It lives outside the CLI on purpose: `issuary` itself
|
|
475
|
+
never calls an LLM, so the worker keeps that dependency in the example, not the
|
|
476
|
+
core.
|
|
477
|
+
|
|
478
|
+
## How it works
|
|
479
|
+
|
|
480
|
+
- **Local SQLite mirror.** State lives in a single SQLite database at
|
|
481
|
+
`~/.issuary/db.sqlite` (override the directory with `ISSUARY_HOME`).
|
|
482
|
+
- **Incremental sync.** `sync` fetches issues with the GitHub `since` parameter
|
|
483
|
+
and an `ETag`. When nothing changed the API returns `304 Not Modified`, which
|
|
484
|
+
does not spend your rate limit and is reported as `unchanged`.
|
|
485
|
+
- **Comments on demand.** Comment threads are not pulled on every sync. They are
|
|
486
|
+
fetched the first time you need them (via `show --raw`) and then cached.
|
|
487
|
+
- **Raw is never deleted.** Compacting adds a summary layer on top of the raw
|
|
488
|
+
body and comments; it never removes them. You can always re-read the raw and
|
|
489
|
+
re-compact. The win from compaction is context tokens for the consuming agent,
|
|
490
|
+
not disk space.
|
|
491
|
+
- **Removal is deactivation.** `remove` deactivates a repo rather than deleting
|
|
492
|
+
it, so its issues and compacts are preserved as history.
|
|
493
|
+
|
|
494
|
+
## Scheduling
|
|
495
|
+
|
|
496
|
+
`issuary` is not a daemon. To keep the mirror fresh, let your OS scheduler run
|
|
497
|
+
`issuary sync --quiet` on an interval. Quiet mode stays silent on a no-op cycle (no
|
|
498
|
+
events, no errors), prints a summary when something changed, always prints
|
|
499
|
+
failed repos, and exits non-zero when any repo failed so a monitor can react.
|
|
500
|
+
|
|
501
|
+
See [docs/scheduling.md](./docs/scheduling.md) for ready-to-use crontab and
|
|
502
|
+
macOS launchd recipes, plus notes on rate limits and how failures surface.
|
|
503
|
+
|
|
504
|
+
## Development
|
|
505
|
+
|
|
506
|
+
```sh
|
|
507
|
+
npm install
|
|
508
|
+
npm run check # lint + format:check + typecheck + test
|
|
509
|
+
npm run build # bundle to dist/cli.js
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
`npm run check` is the quality gate: ESLint, Prettier (`format:check`),
|
|
513
|
+
`tsc --noEmit`, and the Vitest suite. CI (`.github/workflows/ci.yml`) runs the
|
|
514
|
+
same gate plus the build across the Node 20, 22, and 24 matrix; a PR is only
|
|
515
|
+
mergeable with CI green.
|
|
516
|
+
|
|
517
|
+
## License
|
|
518
|
+
|
|
519
|
+
ISC, Lucas Merencia.
|