unblock-cli 0.1.0__py3-none-any.whl
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.
- unblock_cli/__init__.py +5792 -0
- unblock_cli/__main__.py +4 -0
- unblock_cli/index.html +6366 -0
- unblock_cli-0.1.0.dist-info/METADATA +436 -0
- unblock_cli-0.1.0.dist-info/RECORD +8 -0
- unblock_cli-0.1.0.dist-info/WHEEL +4 -0
- unblock_cli-0.1.0.dist-info/entry_points.txt +2 -0
- unblock_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: unblock-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A live map of your project, kept by your agents: a dependency-graph plan in your repo, with a CLI, an MCP server and a viewer.
|
|
5
|
+
Home-page: https://unblock.esh.sh
|
|
6
|
+
Author: Sqvist AB
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# Unblock
|
|
15
|
+
|
|
16
|
+
**A live map of your project, kept by your agents.** The plan is a dependency graph in your repo (`.github/unblock.json`). Your coding agents keep it current as they work: they pick the task that unblocks the most, claim it, and mark it done. You look, and see where things stand, what's next, what's in the way, and what a feature still depends on.
|
|
17
|
+
|
|
18
|
+
It isn't a ticket system. There are no tickets to groom, no sprints, no workflows to configure and no seats. Every task shows **how much of the work upstream of it is already done**, groups fold into one card, and every pull request shows what it does to the plan. It's the map, not the dispatcher: it works alongside whichever agent (Claude Code, Cursor, Codex) and tracker you use.
|
|
19
|
+
|
|
20
|
+
- `index.html`: the app. One self-contained file, no build, no server. Open it in a browser.
|
|
21
|
+
- `board.json`: an example board (the same one the app starts with).
|
|
22
|
+
- `unblock.py`: command-line tool for scripts and agents (Python 3.8+, standard library only).
|
|
23
|
+
- `schema.json`: JSON Schema for the board format.
|
|
24
|
+
- `skill.md`: instructions for coding agents (setup, the work loop, the format). Served at https://unblock.esh.sh/skill.md, with a short `llms.txt` next to it.
|
|
25
|
+
- `.github/unblock.json`: this project's own roadmap, kept as an Unblock board.
|
|
26
|
+
- `SECURITY.md`: how to report a security problem, and what the sign-in and the CLI can and can't do.
|
|
27
|
+
- `LICENSE`: MIT.
|
|
28
|
+
- `tests/`: the test suites (`python3 -m unittest discover -s tests`) and `mcp_host.py`, a small MCP Apps host for trying the board in a chat by hand.
|
|
29
|
+
- `snap/`: the service that draws pull request pictures (`snap.py`) and its `deploy.sh`.
|
|
30
|
+
- `plugin/`: Unblock as a Claude Code plugin with a mod (early), and `.claude-plugin/marketplace.json`, which makes this repository its marketplace.
|
|
31
|
+
- `relay/`: the sign-in relay for the hosted copy (`relay.py`, one file, standard library only) and `deploy.sh`, which runs it as a container next to the site.
|
|
32
|
+
- `landing/`: the landing page at https://unblock.esh.sh (with its launch video, made with [/brag](https://github.com/latent-spaces/brag)).
|
|
33
|
+
- `.caddy`: routing for the hosted copy: `/` is the landing page, `/app` and every `/gh/...` address serve the app.
|
|
34
|
+
|
|
35
|
+
## Track any repository's plan
|
|
36
|
+
|
|
37
|
+
Commit a board to a repository as `.github/unblock.json`, then open:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
https://unblock.esh.sh/gh/<owner>/<repo>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The page reads the board straight from GitHub. There's no sign-up, no server storage, and nothing to install. It's a read-only view that checks for new commits every few minutes, so progress an agent pushes shows up on its own. **Edit a copy** turns it into your own editable board.
|
|
44
|
+
|
|
45
|
+
- **Which file:** the first of `.github/unblock.json`, `unblock.json` or `board.json` on the default branch. To point at something else, use a GitHub-style address: `/gh/<owner>/<repo>/tree/<branch>` or `/gh/<owner>/<repo>/blob/<branch>/<path>`. You can also paste any GitHub link into the box at https://unblock.esh.sh/gh.
|
|
46
|
+
- **Plans kept by other tools:** with no Unblock board, the page shows the plan another tool keeps in the repository, turned into a board: [Task Master](https://github.com/eyaltoledano/claude-task-master) (`.taskmaster/tasks/tasks.json`), [Backlog.md](https://github.com/MrLesk/Backlog.md) (`backlog/tasks`), [Spec Kit](https://github.com/github/spec-kit) (`specs/*/tasks.md`) or [Kiro](https://kiro.dev) (`.kiro/specs/*/tasks.md`). Failing those, it shows the repository's GitHub issues, with sub-issues as groups and "blocked by" links as dependencies. Add `?source=issues` (or `taskmaster`, `backlog`, `speckit`, `kiro`) to choose. Nothing in the repository is changed; `unblock import` keeps the result as a board.
|
|
47
|
+
- **No board yet:** the page offers to create `.github/unblock.json` on GitHub with a starter board already filled in.
|
|
48
|
+
- **Private repositories:** choose **Sign in with GitHub**. You pick which repositories Unblock may use, approve, and you're back on the board. The app can read those repositories, and write to them only when you ask: saving your edits as a commit or pull request, or starting an agent (an issue for `@claude`, a Copilot agent task). It has no webhooks and nothing runs in the background. The page never holds the token: a small relay at `/auth` keeps it in cookies that page scripts can't read and makes the GitHub calls for the page, only the ones Unblock makes. It reads board and plan files (never other files), and writes only a board file that is already a board, `unblock/` branches and their pull requests, issues, comments and agent tasks. So even a script injected into the page couldn't read your code or push to it. **Sign out** in the side panel revokes it. If sign-in isn't available, the page falls back to a pasted [fine-grained token](https://github.com/settings/personal-access-tokens/new) with **Contents: Read-only**, kept in your browser.
|
|
49
|
+
- **Request limits:** without a token, GitHub allows 60 API requests an hour per network. A token raises that to 5,000. The viewer uses about three requests when it opens a board and one per check for new commits.
|
|
50
|
+
- **Edit and save back.** **Edit and save to GitHub** in the board panel makes the board editable in place. When you're done, **Open a pull request** (a new branch with your edits) or **Commit to** the branch directly. Signing in is enough when you've accepted the app's current permissions; a pasted [fine-grained token](https://github.com/settings/personal-access-tokens/new) needs **Contents: Read and write** (and **Pull requests: Read and write** for pull requests). If the file changed on GitHub meanwhile, it won't write over that change. Unsaved edits survive a reload.
|
|
51
|
+
- **Your view settings** (folded groups, focused group, layout) are remembered per repository in your browser. They never touch the file.
|
|
52
|
+
- **From a local copy of `index.html`:** use `index.html?gh=<owner>/<repo>` instead of the `/gh/` path.
|
|
53
|
+
|
|
54
|
+
`unblock.py` looks for the same files, so inside a repository `python3 unblock.py next` finds `.github/unblock.json` without `-f`.
|
|
55
|
+
|
|
56
|
+
## Using the app
|
|
57
|
+
|
|
58
|
+
Open `index.html` in Chrome, Edge, Safari or Firefox, or use the hosted copy at https://unblock.esh.sh/app.
|
|
59
|
+
|
|
60
|
+
| Do this | How |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| Add a task | **+ Task** or `N`. With a task selected, `A` adds a next step, `B` a prerequisite, `S` a subtask. |
|
|
63
|
+
| Connect two tasks | Drag from the dot on a card's right edge onto the card that should wait. Or use *Waits on* / *Unblocks* in the inspector. |
|
|
64
|
+
| Change status | Click the circle on a card, or press `Space`. To do → Doing → Done. |
|
|
65
|
+
| Fold a group | Click its arrow, or `E`. Links in and out of the group are kept on the folded card. |
|
|
66
|
+
| See why something is blocked | Select it. Everything it waits on lights up orange, everything it unblocks lights up blue, and the inspector lists *Do next to unblock*. |
|
|
67
|
+
| Switch layout | The two buttons in the toolbar, or `V`. **Layered** is a left-to-right flowchart of cards; **Force** shows every task as a circle (see below). |
|
|
68
|
+
| Focus on a group | The ↘ button on a group (or `D`) shows only what is inside it; you can keep diving into subgroups. Press `U` or Esc, or use the breadcrumb bar, to go back up. |
|
|
69
|
+
| Critical path | `C` or the route button. Highlights the longest chain of open work by estimate. |
|
|
70
|
+
| Move around | Drag the background, scroll to pan, pinch or ⌘/Ctrl-scroll to zoom, `F` to fit. Arrow keys move the selection. |
|
|
71
|
+
| Undo | `⌘Z` / `⇧⌘Z`. |
|
|
72
|
+
|
|
73
|
+
### Remaining work only
|
|
74
|
+
|
|
75
|
+
**Remaining** in the top bar (or `H`) hides the clutter of finished work. Finished groups disappear, with a note at the bottom of the board ("✓ 8 finished groups hidden · Show"); finished tasks inside open groups become a "✓ 4 done" chip in the group's header; and links from finished tasks go, since they no longer hold anything up. Progress, readiness and the critical path don't change; only the drawing does. It's on by default once a board is two thirds done. It's remembered per board in your browser and never written to the file, and the compare view always shows everything. A task you mark done stays in sight until you switch views. Going to a finished task, from the list or a search, shows everything again.
|
|
76
|
+
|
|
77
|
+
### Focusing on a group
|
|
78
|
+
|
|
79
|
+
Focusing on a group turns the canvas into that group alone: the rest of the board goes away, the task list shows only tasks inside it, and with nothing selected the inspector shows the group itself. Nothing is lost at the edges:
|
|
80
|
+
|
|
81
|
+
- tasks elsewhere that something inside waits on appear as dashed cards in the first column, and tasks elsewhere that wait on something inside appear in the last column. Double-click one to go to where it lives;
|
|
82
|
+
- links that apply to the whole group (its own `dependsOn`, or other tasks that wait on the whole group) are listed in the breadcrumb bar at the top.
|
|
83
|
+
|
|
84
|
+
Both layouts support this, and the app remembers where you were. Creating a task while focused puts it inside the group.
|
|
85
|
+
|
|
86
|
+
### Force view
|
|
87
|
+
|
|
88
|
+
Every task is a circle, sized by its estimate. The fill and outline show the state (orange ring for ready, blue half-disc for doing, green check for done, dashed grey for blocked), and the orange arc around a blocked circle is its % unblocked. An expanded group is an outline around its tasks, with a tab showing its progress; click the tab's − to fold the group into a single circle with a progress pie, and double-click that circle to unfold it again. Its tasks spill out from where it was.
|
|
89
|
+
|
|
90
|
+
Drag circles to untangle the graph; they settle back into their group when you let go. Drag from the dot on a selected circle's edge to link it. **Free** lets the forces decide; **Flow** pulls prerequisites to the left and the work they unblock to the right. The shake button re-runs the layout. Selection, the inspector, the critical path and the keyboard work the same in both views.
|
|
91
|
+
|
|
92
|
+
### Editing
|
|
93
|
+
|
|
94
|
+
- **Right-click** anything (a task, group, ghost card, link, the empty canvas, or a multi-selection) for everything you can do with it. The menu has a search field, so you can type to find an action. Long-press does the same on touch screens.
|
|
95
|
+
- **⌘K / Ctrl+K** searches tasks and actions from anywhere.
|
|
96
|
+
- **Rename in place**: double-click a card, or select it and press Enter. While renaming, Tab saves and adds the next step, Shift+Tab adds a prerequisite, ⌘Enter adds a task beside it, and Esc cancels (Esc on a brand-new, still untitled task removes it). New tasks open straight into rename, so N, type, Tab, type, Tab… lays out a chain.
|
|
97
|
+
- **Quick-add handles**: on a hovered or selected card, click the dot on the right to add a next step (drag it to link instead), or the + on the left to add a prerequisite.
|
|
98
|
+
- **Select several**: Shift-click or ⌘-click cards, Shift-drag on empty canvas, or ⌘A. A toolbar appears with status, group, link in sequence (each waits on the one to its left), move and delete.
|
|
99
|
+
- **Move into a group**: drag a card onto a group (in the force view, drop a circle onto a group outline), or press M to pick one. Dropping on empty canvas in the layered view moves it to the top level.
|
|
100
|
+
- **Clipboard**: ⌘C / ⌘X / ⌘V copy, cut and paste tasks, including everything inside groups and the links between them. ⌘D duplicates, ⌘G groups the selection, ⇧⌘G ungroups.
|
|
101
|
+
- **Inspector**: properties (status, estimate, owner, tags, group) edit inline, like a Notion page. The ⋯ button opens the same menu as a right-click, and ⇔ widens the panel for writing.
|
|
102
|
+
|
|
103
|
+
### Descriptions
|
|
104
|
+
|
|
105
|
+
The description is a rich-text editor, but it is saved as plain Markdown in `notes`, so agents and scripts read and write it as usual.
|
|
106
|
+
|
|
107
|
+
- Type Markdown and it formats as you go: `# ` `## ` `### ` headings, `- ` bullets, `1. ` numbers, `[] ` checklists, `> ` quotes, ```` ``` ```` code, `---` dividers, and `**bold**`, `*italic*`, `` `code` ``, `~~strike~~` inline.
|
|
108
|
+
- Type `/` for a menu of block types. Select text for a formatting bar (bold, italic, strike, code, link, headings, lists).
|
|
109
|
+
- ⌘B, ⌘I, ⌘E (code), ⌘K (link), ⇧⌘S (strike). Tab and Shift+Tab indent list items.
|
|
110
|
+
- Pasting from web pages, docs or Markdown keeps structure but drops styling.
|
|
111
|
+
- Checklist items (`- [ ]`, `- [x]`) show as progress on the card (for example 1/2), and `checklist: {done, total}` appears in the computed report.
|
|
112
|
+
|
|
113
|
+
The board is saved in the browser automatically. Open **JSON** to copy or edit it, import a file, or link a file on disk.
|
|
114
|
+
|
|
115
|
+
### Linking a file (Chrome and Edge)
|
|
116
|
+
|
|
117
|
+
**JSON → Open & link file…** (or **Save to file…**) connects the app to a real file such as `board.json`:
|
|
118
|
+
|
|
119
|
+
- every change in the app is written to the file;
|
|
120
|
+
- when something else changes the file (you, a script, an agent), the app reloads it within about two seconds.
|
|
121
|
+
|
|
122
|
+
That gives you a live view while an agent works through the board with `unblock.py` or by editing the JSON directly. Other browsers can import, download, copy and paste instead. You can also drop a `.json` file onto the window, or paste board JSON anywhere outside a text field.
|
|
123
|
+
|
|
124
|
+
## How the numbers work
|
|
125
|
+
|
|
126
|
+
- **Tasks and groups.** A task with children is a group. Only tasks without children (leaves) have a status; a group's progress comes from its leaves.
|
|
127
|
+
- **Dependencies.** `dependsOn` lists ids that must be done first. Depending on a group means depending on every task inside it. A group's own `dependsOn` applies to everything inside it.
|
|
128
|
+
- **Ready vs Blocked** are computed, never stored. A `todo` task is *ready* when all of its prerequisites are done; otherwise it is *blocked*.
|
|
129
|
+
- **% unblocked** for a task is the share of *all* the work upstream of it (the full transitive set of prerequisites, not just direct ones) that is done, weighted by `estimate` (default 1). For a group, it counts the upstream work outside the group. 100% means nothing upstream is left.
|
|
130
|
+
- **Do next to unblock** lists the open upstream tasks that can be worked on right now (ready or doing), ordered by how much downstream work each one unblocks.
|
|
131
|
+
- **Unblocks N** is the number of open tasks somewhere downstream.
|
|
132
|
+
- **Critical path** is the longest chain of open work, summed by estimate. The board can't finish sooner than this chain does.
|
|
133
|
+
|
|
134
|
+
## Board format
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"format": "unblock/v1",
|
|
139
|
+
"title": "Public beta launch",
|
|
140
|
+
"view": { "collapsed": ["research"] },
|
|
141
|
+
"tasks": [
|
|
142
|
+
{ "id": "backend", "title": "Backend", "dependsOn": ["scope"] },
|
|
143
|
+
{ "id": "auth-api", "title": "Auth & sessions API", "status": "doing", "parent": "backend",
|
|
144
|
+
"dependsOn": ["db-schema"], "estimate": 5, "assignee": "sam", "tags": ["api"], "notes": "…" }
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
| Field | Meaning |
|
|
150
|
+
| --- | --- |
|
|
151
|
+
| `id` | Unique, stable string. Links and parents refer to it. |
|
|
152
|
+
| `title` | What the task is. |
|
|
153
|
+
| `status` | `todo`, `doing` or `done`. Leaves only. |
|
|
154
|
+
| `parent` | Id of the group it sits in. |
|
|
155
|
+
| `dependsOn` | Ids that must be done first. A group id means every task inside it. |
|
|
156
|
+
| `estimate` | Optional number ≥ 0, default 1. Weights every percentage. |
|
|
157
|
+
| `assignee`, `tags`, `notes` | Optional. |
|
|
158
|
+
| `ref` | Optional: another board this task stands for, or one task in it: `owner/repo`, `owner/repo#task`, `owner/repo:path/board.json#task`, or a board on disk, `../api/.github/unblock.json#task`. The task follows it: done when that is done, doing once work there started (`unblock sync`, or the workflow after each merge). Cards show its live state, so work that spans repositories shows up in one plan. |
|
|
159
|
+
| `touches` | Optional: paths the task will likely edit, as patterns (`["src/auth/**"]`). Starting several agents warns when two picked tasks overlap. |
|
|
160
|
+
| `issue` | Optional: the GitHub issue this task is, `owner/repo#123` or `#123` in the board's repository. Cards show it with its live state, and the task is marked done when the issue closes: `unblock sync`, or the workflow after each merge (it needs `issues: read`). Set it with `unblock edit <id> --issue owner/repo#123`, or let `unblock export-issues <ids>` create the issues: a group becomes a parent issue with its open tasks as sub-issues, links become "blocked by", and each task gets its `issue`. Then GitHub's coding agents can be assigned from the issue. Only what you name is exported, and nothing syncs back except closed issues. |
|
|
161
|
+
| `view.collapsed` | Group ids shown folded. Display only. |
|
|
162
|
+
|
|
163
|
+
Any other fields, on tasks or at the top level, are kept as they are, so you can attach your own data (links, ticket numbers, due dates).
|
|
164
|
+
|
|
165
|
+
A board is rejected if an id is missing or duplicated, a `parent` or `dependsOn` points at an id that doesn't exist, parents form a loop, a task waits on its own group (or a group on its own subtask), or the dependencies form a loop. The error names the ids involved.
|
|
166
|
+
|
|
167
|
+
## For agents and scripts
|
|
168
|
+
|
|
169
|
+
**Point your agent at https://unblock.esh.sh/skill.md.** It explains how to install the CLI, start a board for a repository, work through it, and show you progress. It's written as a Claude Code skill, so you can also save it as `.claude/skills/unblock/SKILL.md`. For any other agent, paste the link.
|
|
170
|
+
|
|
171
|
+
To get the CLI on its own:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
curl -fsSL https://unblock.esh.sh/install.sh | sh
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
On Windows, in PowerShell: `irm https://unblock.esh.sh/install.ps1 | iex`. Either one takes the newest release, checks it against the sha256 the site lists, needs only Python 3.8 or newer, and puts `unblock` on your PATH (`~/.local/bin`, adding it to your shell's startup file when it's missing; `sh -s -- --no-modify-path` leaves that file alone). It has to be on the PATH: agents, Claude Code's hooks, the status line and the side pane all run `unblock` by name.
|
|
178
|
+
|
|
179
|
+
Prefer `unblock.py` over editing the JSON by hand: it validates every write, refuses loops, and keeps the file formatted the way the app writes it.
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
python3 unblock.py init # create .github/unblock.json (--example adds a few sample tasks)
|
|
183
|
+
python3 unblock.py init --draft # or draft it from what's here: README/TODO checklists, TODO comments, another tool's plan
|
|
184
|
+
python3 unblock.py init --agents # the Unblock lines in AGENTS.md (and CLAUDE.md), so any agent follows the loop
|
|
185
|
+
python3 unblock.py prime # for an agent starting a session: progress, in flight, what to pick up
|
|
186
|
+
python3 unblock.py view # open the board in the browser; edits save to the file
|
|
187
|
+
python3 unblock.py # summary: progress, in flight, what to start next
|
|
188
|
+
python3 unblock.py next --json # ready tasks, most unblocking first
|
|
189
|
+
python3 unblock.py next --waves # all open work in sets that can run in parallel: how many agents are worth running
|
|
190
|
+
python3 unblock.py now # one answer to "what should I do now?", and why
|
|
191
|
+
python3 unblock.py digest --since 7d # what changed in the plan: finished, now ready, new, blocked (or --since <ref>)
|
|
192
|
+
python3 unblock.py show docs # readiness, what to do next to unblock it, links
|
|
193
|
+
python3 unblock.py tree # the whole board
|
|
194
|
+
python3 unblock.py next --in backend # only what is ready inside one group (also works for tree)
|
|
195
|
+
python3 unblock.py report # everything computed, as JSON
|
|
196
|
+
python3 unblock.py set auth-api done # prints which tasks just became ready (--note "what you decided" adds a dated line)
|
|
197
|
+
python3 unblock.py claim auth-api # before starting: doing, and other agents skip it for 2h (--for 30m)
|
|
198
|
+
python3 unblock.py edit auth-api --assignee sam --notes "- [ ] rate limits" # any field; '' clears it
|
|
199
|
+
python3 unblock.py point auth-api --note "Starting this" # highlight tasks in the open viewer
|
|
200
|
+
python3 unblock.py diff main...feature # what a branch does to the plan (also: diff, diff HEAD~3, diff a.json b.json)
|
|
201
|
+
python3 unblock.py init --ci # comment that diff on every pull request (GitHub Actions)
|
|
202
|
+
python3 unblock.py init --git # merge the board task by task, so branches don't conflict on it
|
|
203
|
+
python3 unblock.py sync # tasks follow what they link to: closed issues, other boards (ref)
|
|
204
|
+
python3 unblock.py export-issues launch --dry-run # GitHub issues for chosen tasks (sub-issues, blocked-by), to assign agents
|
|
205
|
+
python3 unblock.py start --dry-run # the play button: Claude Code agents on the next ready tasks, at most 3 (then: unblock stop)
|
|
206
|
+
python3 unblock.py statusline # one line for Claude Code's status bar (init --agents sets it up)
|
|
207
|
+
python3 unblock.py heartbeat --state needs-you # tell the board your agent waits on a person (Claude Code's hooks do this)
|
|
208
|
+
python3 unblock.py prompt docs # what to tell an agent to work on a task (the app's Start menu uses the same text)
|
|
209
|
+
python3 unblock.py log # what each commit did to the plan: progress over time, what finished when
|
|
210
|
+
python3 unblock.py card # a 1200x630 picture of the board's progress, to share
|
|
211
|
+
python3 unblock.py graph # the board as it stands, as text: waiting on you, in progress, ready, and what they unblock
|
|
212
|
+
python3 unblock.py svg --out now.svg # the same as a picture (light and dark in one SVG); the Claude Code pane shows it
|
|
213
|
+
python3 unblock.py readme --add # a badge and a Mermaid graph of the plan in README.md, refreshed after merges
|
|
214
|
+
python3 unblock.py import # start from the plan Task Master, Backlog.md, Spec Kit or Kiro keeps here
|
|
215
|
+
python3 unblock.py import github --repo o/r # or from a repository's issues: sub-issues are groups, "blocked by" are links
|
|
216
|
+
python3 unblock.py add "Write release notes" --parent launch --depends-on docs --estimate 2
|
|
217
|
+
python3 unblock.py link docs auth-api # docs now waits on auth-api
|
|
218
|
+
python3 unblock.py unlink docs auth-api
|
|
219
|
+
python3 unblock.py rm site --ungroup # remove a group but keep its tasks
|
|
220
|
+
python3 unblock.py undo # put the board back as it was before unblock's last save (--list shows what can be undone)
|
|
221
|
+
python3 unblock.py validate
|
|
222
|
+
python3 unblock.py lint # worth a look: done while blocked, stuck in doing for a week
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Without `-f` it uses `UNBLOCK_FILE`, or else the first of `.github/unblock.json`, `unblock.json`, `board.json` in the current folder. A suggested loop for an agent: run `next --json`, pick the first task, `claim <id>`, do the work, `set <id> done`, repeat.
|
|
226
|
+
|
|
227
|
+
**Claims** keep parallel agents apart. `unblock claim <id>` marks the task doing and holds a lease on it (2 hours unless you pass `--for`; claiming again renews it). `next` skips tasks other agents hold, and finishing or putting a task back releases it. Claims live in the repository's shared git folder, so agents in different worktrees on the same machine see each other's claims before their branches merge. Across machines, the board's doing status is the signal. The holder is `$UNBLOCK_AGENT` if set, else `user@host:worktree`; `unblock claims` lists them and `unblock release <id>` gives one up.
|
|
228
|
+
|
|
229
|
+
### In Claude Code: the plugin (early)
|
|
230
|
+
|
|
231
|
+
`plugin/` is Unblock as a Claude Code plugin: one install brings the `unblock` command (on Claude's PATH while the plugin is enabled, so nothing else to install), the MCP server, the skill, the heartbeat hooks, and a [mod](https://code.claude.com/docs/en/plugins/mods/overview) (Claude Code v2.1.286 or later). The agent starts each conversation knowing the plan, and an approved plan-mode plan with several steps goes on the board. In the terminal and the desktop app's Code tab it shows the plan in the band above the prompt (`▸ Unblock ▰▰▰▰▰▰▰▱▱▱ 72% · 3 ready · 1 needs you · on auth-api`), writes a line in the transcript when a task finishes (`✓ Auth API done → now ready: Billing · 72% → 76%`) or an agent waits on you, and adds `/unblock`: where things stand, with the board in a pane (a picture in the desktop app). When the agent points at tasks, the pane opens on them with its note; while it works through a checklist (its task list), the pane shows that too, under the board, without writing it to the plan. `/unblock next` hands Claude the next ready task, `/unblock plan <feature>` has it plan a feature on the board after a few questions, and `/unblock standup [days]` says what moved. Unblock's own tool calls are drawn as the moment they are (`✓ Finished: Auth API`, with what became ready), and calls that do anything else are left alone. It reads the board and the claims file, draws the picture with the copy of `unblock.py` it ships, and hands Claude work only when you run one of those commands: no network, and it never writes. This repository is its marketplace:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
claude plugin marketplace add sqvist-ab/unblock
|
|
235
|
+
claude plugin install unblock@unblock
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
From a clone, `claude plugin marketplace add ./` works the same, and `claude --plugin-dir ./plugin` loads it for one session. Claude Code keeps an installed plugin as a cached copy and replaces it only when the version changes, so a change to `plugin/` goes out with `packaging/update-plugin.sh <version>` (it also refreshes the plugin's copies of `unblock.py` and `skill.md`), and a running session keeps its version until Claude Code restarts. `tests/test_mod.py` checks its numbers against `unblock.py` on every version of this repository's roadmap, and `claude plugin test plugin` runs its hooks in Claude Code's own engine (`plugin/hooks/register.test.ts`).
|
|
239
|
+
|
|
240
|
+
### Watching a board while an agent works
|
|
241
|
+
|
|
242
|
+
`unblock view` serves the app at http://127.0.0.1:7070 against the repository's board file and opens it in your browser:
|
|
243
|
+
|
|
244
|
+
- Edits you make in the browser are saved to the file.
|
|
245
|
+
- Changes made on disk by an agent, an editor or `git pull` show up within a second.
|
|
246
|
+
- If both change at once, nothing is overwritten: the page reloads the file and says so.
|
|
247
|
+
- It needs no GitHub account or server, and only answers on your own machine. Folding and focusing are remembered in the browser and never change the file.
|
|
248
|
+
|
|
249
|
+
It uses the `index.html` next to `unblock.py` if there is one. Otherwise it downloads the app from unblock.esh.sh once a day. Use `--port` to pick another port, and `--no-open` to skip opening a browser.
|
|
250
|
+
|
|
251
|
+
### Pointing at tasks
|
|
252
|
+
|
|
253
|
+
An agent can show you exactly what it means. A point rings one or more tasks or groups, brings them into view (unfolding groups and leaving a focused group if needed) and puts a short note beside them. It stays until you dismiss it (Escape or ×) or the next point replaces it.
|
|
254
|
+
|
|
255
|
+
- **In the open viewer:** `unblock point auth-api db-schema --note "Starting with these two"`. `unblock view` finds the running viewer for the board and shows it there. If none is running, `unblock view --point auth-api --note "…"` opens one on those tasks.
|
|
256
|
+
- **As a link:** add `#point=<id>,<id>¬e=<text>` to any board address, for example `https://unblock.esh.sh/gh/<owner>/<repo>#point=auth-api¬e=Blocked%20on%20this`. `unblock point` prints this link when the board is in a GitHub repository.
|
|
257
|
+
- **From MCP:** the `point` tool below, which also shows the board in the chat.
|
|
258
|
+
- **From page scripts:** `window.unblock.point(['auth-api'], {note: '…'})`.
|
|
259
|
+
|
|
260
|
+
### Starting an agent on a task
|
|
261
|
+
|
|
262
|
+
Every open task has **Start with an agent** in its panel and its right-click menu. **Claude Code** and **Terminal** open Claude Code (the desktop app, or a new terminal) in the project folder with the task written in; **Codex** does the same in the ChatGPT desktop app's Codex mode; **Claude on the web** opens a cloud session on the repository. You press Enter there, so the agent runs in your own app on your own login. Unblock never runs agents itself. The prompt tells the agent to `unblock claim` the task, work on a branch, and open a pull request that says `done: <id>`, so the task is marked done when it merges. **Copy prompt** (or `unblock prompt <id>`) gives the same text for any other agent.
|
|
263
|
+
|
|
264
|
+
**Start agents** (the play button at the top of the task list, in the local viewer) starts a wave. It offers the ready tasks nobody holds, most unblocking first, with three picked and a limit of three at once unless you raise it. It warns about tasks with no description, picked tasks likely to conflict (a task's optional `touches` paths overlap, or, without those, both sit in the same group), and pull requests already waiting for your review. On **Start**, each task is claimed for its agent and gets a background agent on your login, in its own worktree: a Claude Code session (`claude --bg --name unblock/<id>`), or Codex (`codex exec --json --worktree`, with its sandbox and approvals left on). For Codex, the board reads its event stream for what it's doing and what it last said, and gives the command to resume its thread. Nothing starts the wave after that; it waits for you. **Stop** stops the sessions Unblock started and puts their tasks back to ready. From the command line: `unblock start [ids] [--agent claude|codex|copilot] [--max 3] [--dry-run]` and `unblock stop [ids]`.
|
|
265
|
+
|
|
266
|
+
**Cloud agents from the `/gh` viewer.** On a GitHub board, Start also offers **Copilot (cloud)**, which starts Copilot's cloud agent through GitHub's Agent tasks API on your Copilot plan and opens a pull request. Where the repository runs `anthropics/claude-code-action`, it also offers **Claude on GitHub**: an `@claude` comment on the task's issue, or a new issue if the task has none, run by that workflow on the repository's own Anthropic key. Both ask first. Signing in is enough when you've accepted the app's current permissions; a pasted [fine-grained token](https://github.com/settings/personal-access-tokens/new) needs *Agent tasks: Read and write* for Copilot, or *Issues: Read and write* for Claude. The card follows Copilot's task state (checked every 30 seconds, including *needs you*) and either agent's pull request; what you started is remembered in your browser. From the command line or the local viewer, `unblock start <id> --agent copilot` does the same with `GITHUB_TOKEN` or `gh auth login`.
|
|
267
|
+
|
|
268
|
+
**Beside the chat.** `unblock init --agents` also adds an `unblock` entry to `.claude/launch.json`. In Claude Code's desktop app, the browser pane can show it: the live board next to the conversation, where `point` highlights tasks with the agent's note and **Start agents** sits. Each project gets a port of its own, and the viewer refuses to move off it (`--exact-port`), so a pane never shows another project's board. Agents in hosts that don't draw the board in the chat are told it's there.
|
|
269
|
+
|
|
270
|
+
**In the chat: moments, not the board.** Finishing or changing a task's status says what became ready and what it did to the plan (`▸ Unblock ▰▰▰▰▰▰▰▰▰▱ 88% → 90% · 58 of 66 tasks done`, Unblock's one line for where the plan stands, the same in the status line and the plugin's band), two lines an agent can pass on. Where the chat draws MCP Apps, `point` shows a short strip of the board around those tasks with the agent's note, always in the Remaining view (the tasks pointed at stay drawn even when finished), and **Open the full board** expands it with your usual view. Agents are told to point at three moments: starting a task, finishing one, and being stuck on you.
|
|
271
|
+
|
|
272
|
+
**In your status bar (the terminal).** `unblock init --agents` also sets Claude Code's status line to `unblock statusline`, which the terminal CLI shows below the prompt. The desktop app doesn't show custom status lines; there, the side pane does this job. The line reads: `▸ Unblock ▰▰▰▰▰▰▰▰▰▱ 91% · 5 ready · 1 needs you · on auth-api`. That's progress, what's ready, how many agents are waiting on you, and the task this session holds. It's always in view and costs the agent nothing. If the project already has a status line, it's left alone.
|
|
273
|
+
|
|
274
|
+
**What each agent is doing** shows on its card: the holder while it's *working*, a pulsing *needs you* when it waits on a person (a permission prompt, a question, or a finished turn), *quiet* after 45 minutes without a word, and *PR #12* once an open pull request says `done: <id>`. Tasks that need you also head the rail. `unblock init --agents` adds Claude Code hooks to `.claude/settings.json` that call `unblock heartbeat --hook` on session start, each prompt, notifications, the end of a turn and the end of the session. Each heartbeat records the state and session on the claims that session holds, and renews their lease. Other agents can report the same way with `unblock heartbeat --state needs-you`. The local viewer also reads Claude Code's background sessions (`claude agents --json`) and the repository's open pull requests (with `gh`, or the GitHub API and a token); the `/gh` viewer reads the pull requests from GitHub. The task panel says who holds the task, when it was last heard from, and gives the command to resume or attach to its session.
|
|
275
|
+
|
|
276
|
+
Where the board is decides what's offered: the local viewer opens the project folder, the `/gh` viewer offers the cloud session and the terminal (in your clone of the repository), and the board inside a chat asks the agent you're talking to. Tasks someone has claimed show the holder on the card, and the panel says until when.
|
|
277
|
+
|
|
278
|
+
### History
|
|
279
|
+
|
|
280
|
+
`unblock log` lists what each commit did to the plan: the date, progress and its change, and what it finished, reopened, added or removed (`--json` for the series). In the app, **Show history** in the board panel draws progress over time and the same list, from the local viewer's git history or, for `/gh/` boards, from GitHub (one API request, then the file at each commit). **Compare with now** on any commit opens the compare view against that version.
|
|
281
|
+
|
|
282
|
+
### A picture of progress
|
|
283
|
+
|
|
284
|
+
**Share a picture of progress** in the board panel (or in the command menu) downloads a 1200×630 card: the title, a ring with how much is done and in progress, the four counts, and what's in progress or next. It's the size link previews and slides expect, as PNG or SVG. `unblock card` writes the same picture from the command line (`--dark` for the dark version, `--out` for the file name).
|
|
285
|
+
|
|
286
|
+
### Since you last looked
|
|
287
|
+
|
|
288
|
+
Nobody writes status updates. `unblock digest --since 7d` (or `12h`, `2w`, a git ref) says what finished, what became ready, what's new and what's blocked, from the board file's git history; `--markdown` for posting it, and the MCP server has it as a `digest` tool. The app remembers the board as you last saw it, per file or repo, in your browser: when you come back and it has changed, a card says what moved, and **See the changes** opens the compare view against what you saw.
|
|
289
|
+
|
|
290
|
+
### What a change does to the plan
|
|
291
|
+
|
|
292
|
+
`unblock diff` compares two versions of the board and reports what the change means, not just which lines moved:
|
|
293
|
+
|
|
294
|
+
- **Effects:** tasks completed, now ready to start, now blocked or reopened, how each task's % unblocked moves, progress before and after, and the critical path.
|
|
295
|
+
- **Edits:** tasks added, removed, renamed or moved, links added and removed, status, estimate, assignee, tags and checklist changes.
|
|
296
|
+
|
|
297
|
+
**In the app:** `https://unblock.esh.sh/gh/<owner>/<repo>/compare/<base>...<head>` (the same shape as GitHub's compare pages; `..` compares with the base branch's tip) shows the head board with the changes marked: new tasks outlined, chips for done, now ready, now blocked, reopened and edited, rings showing the % before and after, and a bar with counts you can click to point at those tasks. The list of every change replaces the task list. Locally, `unblock view --diff main` (or `--diff main...` for since the branch split off) does the same against the file on disk, and keeps updating as it changes.
|
|
298
|
+
|
|
299
|
+
With no arguments it compares `HEAD` with the file on disk, which is handy before committing. `diff main feature` compares two refs, and `diff main...feature` compares a branch with where it split off (what a pull request contains). Two `.json` files work too. Add `--json` for agents, or `--markdown` for the pull request comment.
|
|
300
|
+
|
|
301
|
+
### Merging the board
|
|
302
|
+
|
|
303
|
+
Branches that change the board merge like code, and `unblock init --git` makes that dependable: it sets git to merge `.github/unblock.json` task by task (a line in `.gitattributes`, plus this clone's git config). Two branches that add tasks, or change different tasks or different fields of one task, then always merge; links merge as sets. Only real conflicts stop the merge: the same field changed differently on both sides, a task deleted on one side and changed on the other, or a result with a dependency loop. Then git falls back to its usual conflict markers, and the driver lists exactly what clashed. Other clones run `unblock init --git` once; until then git merges the file line by line, as before. GitHub's own merge button always merges line by line.
|
|
304
|
+
|
|
305
|
+
### Plan changes on pull requests
|
|
306
|
+
|
|
307
|
+
`unblock init --ci` adds `.github/workflows/unblock.yml`. On every pull request it comments with what merging it does to the plan: a one-line summary (for example "44% → 51% done · completes 2 · unblocks 1 · adds 4"), a picture of the affected tasks drawn like the app (cards, rings, what's new, done or now ready), the lists of what changed, and a link that opens the board at the branch with those tasks highlighted.
|
|
308
|
+
|
|
309
|
+
- It compares the merge result GitHub prepares with the base branch, so only this pull request's changes count.
|
|
310
|
+
- It keeps one comment up to date as you push, and adds an **Unblock plan** check beside it (neutral when something is worth a look, failed only for an invalid board), stays quiet on pull requests that don't touch the board, and fails the check if the pull request would leave the board invalid.
|
|
311
|
+
- **Done on merge.** Write `done: <id>` (or `closes:`, `finishes:`, `completes:`, several ids with commas) in a commit message or the pull request's description. The comment already shows those tasks done, and when the change reaches the default branch, a second job marks them done in the board and commits that. It's the only job with write access to the repository. Anything that isn't an open task on the board, such as `closes: #12`, is left alone.
|
|
312
|
+
- Under **Worth a look** it flags what the pull request causes: a task marked done while something it waits on isn't, and new tasks with no description (an agent starting them has only the title). These are notes, never a failed check. `unblock lint` checks the whole board locally, including tasks stuck in doing for a week or more (from the file's git history), and the compare view shows the same notes.
|
|
313
|
+
- It needs nothing but the built-in token (`pull-requests: write`). Pull requests from forks get a read-only token, so for those the diff is only in the run's summary.
|
|
314
|
+
- **The picture** is an SVG served from `unblock.esh.sh/d/<id>.svg`, with a dark version for dark themes. The run sends a small snapshot (only the changed tasks and their neighbours: titles, states and percentages) and proves which repository it comes from with GitHub's OIDC token (`id-token: write`), so there's nothing to configure. Public repositories get it by default. Private repositories get a Mermaid graph instead, unless you set `UNBLOCK_IMAGES: "on"`, since the snapshot's titles are then viewable by anyone with the link. `"off"` never sends anything. `unblock diff main...feature --svg plan.svg` draws the same picture locally.
|
|
315
|
+
- **It runs one release of unblock, checked against its sha256.** The workflow downloads `unblock.esh.sh/v/<version>/unblock.py` and refuses to run it if its sha256 isn't the one written into the workflow, so a change on unblock.esh.sh can't change what runs in your repository. `unblock init --ci` pins your CLI's own version when it's a release (installed from PyPI, say), and otherwise the newest release; run it again to move to the newest, and it changes only the download steps, keeping your other edits. Before the first release exists it downloads the latest `unblock.py` on each run, and says so. You can also commit `unblock.py` to the repository and run `python3 unblock.py ci` instead, as this repository does.
|
|
316
|
+
|
|
317
|
+
### The plan in your README
|
|
318
|
+
|
|
319
|
+
`unblock readme --add` puts a **Plan** section at the end of `README.md`: a progress badge (`.github/unblock-badge.svg`, "plan | 54% done · 3 ready") linking to the live board, a line of counts, and a Mermaid graph of the plan, which GitHub draws in place. Big plans show their groups folded so the graph stays readable, with links between tasks in different groups drawn between the groups. The section sits between `<!-- unblock:plan -->` and `<!-- /unblock:plan -->`, each on a line of its own, so you can move it anywhere.
|
|
320
|
+
|
|
321
|
+
With `unblock init --ci`, the job that runs after a merge refreshes that section and the badge whenever the plan changed, in the same commit that marks `done:` tasks done. Nothing is sent anywhere: the badge is a file in the repository. `unblock readme` refreshes it by hand (`--check` exits 1 when it was stale), and `unblock mermaid` prints the graph on its own.
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
## MCP server
|
|
325
|
+
|
|
326
|
+
`unblock mcp` runs the CLI as an [MCP](https://modelcontextprotocol.io) server over stdio, so any MCP client can read and change the board with tools instead of shell commands.
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
claude mcp add unblock -- python3 ~/.local/bin/unblock mcp # Claude Code, this project
|
|
330
|
+
claude mcp add --scope user unblock -- python3 ~/.local/bin/unblock mcp # every project
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
For apps configured with JSON (Claude Desktop and others):
|
|
334
|
+
|
|
335
|
+
```json
|
|
336
|
+
{ "mcpServers": { "unblock": { "command": "python3", "args": ["/Users/you/.local/bin/unblock", "mcp"] } } }
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Tools: `show_board`, `point`, `next_tasks`, `claim_task`, `get_task`, `get_board`, `diff`, `digest`, `update_task`, `add_task`, `link_tasks`, `unlink_tasks`, `remove_task`, `create_board` and `open_viewer` (starts `unblock view` in the background). `unblock mcp --read-only` offers only the tools that read and show: nothing can change the board, and the board in the chat is view-only. Every tool takes an optional `board`: the project folder or the board file. The server looks in the folders the client shares (MCP roots) and in its working directory; once a board is used it stays the default. Add `-f path/to/.github/unblock.json` before `mcp` to pin one board.
|
|
340
|
+
|
|
341
|
+
**The board in the chat.** `show_board` and `point` come with an [MCP App](https://modelcontextprotocol.io/extensions/apps/overview): in clients that support MCP Apps (Claude Desktop, VS Code GitHub Copilot, Goose and [others](https://modelcontextprotocol.io/extensions/client-matrix)), the interactive board appears right in the conversation, with the pointed tasks ringed and the agent's note beside them. It's the full app, so you can fold groups, switch to the force view, go full screen and edit. Edits save to the file through the server, and changes the agent makes show up within a few seconds. Clicking a task tells the agent which one you mean ("do this one"), and **Work on this in the chat** in a task's menu starts on it. Clients without MCP Apps get the same information as text.
|
|
342
|
+
|
|
343
|
+
`report` returns, for every task: `state` (`ready`, `doing`, `blocked`, `done`), `unblockedPct`, `progressPct`, `upstream` counts, `doNext`, `waitingOn` and `unblocksOpen`, plus the board summary, the ordered `next` list and the `criticalPath`. The app shows the same report under **JSON → Computed report**; both implementations compute identical numbers.
|
|
344
|
+
|
|
345
|
+
Boards start with `"$schema": "https://unblock.esh.sh/schema.json"`, so editors such as VS Code check and autocomplete them. Editing `board.json` by hand or with other tools is fine too. Keep ids stable, and check your change with `python3 unblock.py validate`.
|
|
346
|
+
|
|
347
|
+
Inside the page, `window.unblock` offers `getBoard()`, `getReport()`, `load(board)`, `setStatus(id, status)`, `addTask({...})`, `link(task, prereq)`, `select(id)` and `point(ids, {note})` for browser automation.
|
|
348
|
+
|
|
349
|
+
## Tests
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
python3 -m unittest discover -s tests
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Standard library only, a little over a minute, and nothing talks to GitHub: the suites use scratch git repositories, a fake GitHub API and a fake OIDC issuer (which needs the `openssl` command). They cover the CLI and the model, `unblock diff` and the pictures, `unblock ci`, the picture service, the MCP server, the local viewer and the sign-in relay. `test_app_diff.py` also runs the app's own diff code (cut out of `index.html`, in JavaScriptCore on macOS or Node elsewhere) and checks it matches `unblock.py`'s exactly, on every pair of versions of this repository's roadmap and on randomly edited boards. `.github/workflows/tests.yml` runs them on every push and pull request.
|
|
356
|
+
|
|
357
|
+
`python3 tests/mcp_host.py [project]` is a small MCP Apps host for trying the board-in-the-chat by hand: it runs `unblock mcp`, shows the app in a sandboxed iframe with a strict CSP, and logs every message the app sends.
|
|
358
|
+
|
|
359
|
+
## Packaging
|
|
360
|
+
|
|
361
|
+
`python3 packaging/build.py` builds `dist/unblock_cli-<version>-py3-none-any.whl` and an npm package in `dist/npm/`, with the standard library only. Both ship `unblock.py` and `index.html` (so `unblock view` and the MCP App work offline) and install an `unblock` command; the npm one runs it with the machine's `python3`. The package name is `unblock-cli`, since `unblock` is taken on PyPI and npm, so once published it's `uvx --from unblock-cli unblock` or `npx unblock-cli`. Nothing is published yet. Publishing goes through `.github/workflows/publish.yml` with PyPI trusted publishing (no stored token): it runs when a `v<version>` tag is pushed, refuses without a `LICENSE` file or when the tag doesn't match `__version__`, builds and installs the wheel, then uploads it. The wheel and the npm package carry the license and its SPDX id (MIT). `tests/test_packaging.py` installs the wheel into a fresh virtual environment and runs it.
|
|
362
|
+
|
|
363
|
+
**A release is a tag.** Pushing `v<version>` publishes the wheel, and `python3 packaging/site.py <dir>` builds the site's files with every tagged release at `v/<version>/unblock.py` and their sha256s in `v/releases.json`, which is what `unblock init --ci` pins to. Deploying the site replaces it whole, so the releases are rebuilt from the tags each time.
|
|
364
|
+
|
|
365
|
+
## Hosting the picture service
|
|
366
|
+
|
|
367
|
+
`snap/snap.py` (standard library only) stores pull request snapshots and draws them as SVG with `unblock.py`'s renderer, so it never serves SVG that someone else wrote. Uploads must carry a GitHub Actions OIDC token for `https://unblock.esh.sh`, checked against GitHub's published keys, and are limited per repository per day. Pictures never change, so they're cached forever by GitHub's image proxy and the CDN. `snap/deploy.sh` runs it as a container next to the site; Traefik sends `/d/*` and `/api/snapshots` to it.
|
|
368
|
+
|
|
369
|
+
## Hosting the sign-in relay
|
|
370
|
+
|
|
371
|
+
GitHub doesn't let a web page complete "Sign in with GitHub" on its own: the code exchange needs the app's client secret. `relay/relay.py` does that part and nothing else. It keeps no user data; it stores only the GitHub App's own credentials and a signing key.
|
|
372
|
+
|
|
373
|
+
1. `relay/deploy.sh` copies the relay to the server and starts it. Traefik sends `/auth/*` to it; everything else is still the static site.
|
|
374
|
+
2. The first time, it prints a one-time setup link. Opening it creates the GitHub App from a manifest: it can read repository contents and, when a person asks, write contents, pull requests, issues and agent tasks (for saving edits and starting agents). No webhooks. You click **Create GitHub App** on GitHub, and GitHub sends the credentials straight to the relay.
|
|
375
|
+
3. Install the app on the repositories you want to view, or let the viewer send you there when you first open a private board.
|
|
376
|
+
|
|
377
|
+
## License
|
|
378
|
+
|
|
379
|
+
MIT: see [LICENSE](LICENSE). Use it, change it, ship it, host your own copy.
|
|
380
|
+
|
|
381
|
+
The name "Unblock" and its logo belong to Sqvist AB and aren't covered by the license. A fork or a hosted copy is welcome, but it should go by a name of its own, so people can tell it apart from the hosted service at unblock.esh.sh.
|
|
382
|
+
|
|
383
|
+
## Plan
|
|
384
|
+
|
|
385
|
+
<!-- unblock:plan -->
|
|
386
|
+
|
|
387
|
+
[](https://unblock.esh.sh/gh/sqvist-ab/unblock)
|
|
388
|
+
|
|
389
|
+
89 of 100 tasks done, 6 ready to start.
|
|
390
|
+
|
|
391
|
+
```mermaid
|
|
392
|
+
flowchart LR
|
|
393
|
+
n0[["Core board<br/>100% done"]]
|
|
394
|
+
n1[["GitHub viewer<br/>100% done"]]
|
|
395
|
+
n2[["Agents first<br/>71% done"]]
|
|
396
|
+
n3[["Merge-friendly format<br/>100% done"]]
|
|
397
|
+
n4[["Shows up on GitHub<br/>100% done"]]
|
|
398
|
+
n5[["Momentum<br/>100% done"]]
|
|
399
|
+
n6[["Launch<br/>40% done"]]
|
|
400
|
+
n7[["See plans kept elsewhere<br/>100% done"]]
|
|
401
|
+
n8[["Agents from the board<br/>100% done"]]
|
|
402
|
+
n9[["Where the board lives<br/>100% done"]]
|
|
403
|
+
n10[["Before going public<br/>100% done"]]
|
|
404
|
+
n11[["In the conversation<br/>100% done"]]
|
|
405
|
+
n12[["Quality of life<br/>100% done"]]
|
|
406
|
+
n4 --> n1
|
|
407
|
+
n6 --> n2
|
|
408
|
+
n1 --> n3
|
|
409
|
+
n0 --> n4
|
|
410
|
+
n1 --> n4
|
|
411
|
+
n4 --> n5
|
|
412
|
+
n2 --> n6
|
|
413
|
+
n3 --> n6
|
|
414
|
+
n1 --> n7
|
|
415
|
+
classDef done fill:#DAF1E7,stroke:#0F8A62,color:#0A6A4B
|
|
416
|
+
classDef ready fill:#FDE8DE,stroke:#E84E12,color:#B03A0A
|
|
417
|
+
classDef doing fill:#E1E9FD,stroke:#2563EB,color:#1C4DBD
|
|
418
|
+
classDef blocked fill:#E7EBEF,stroke:#96A0AD,color:#586473
|
|
419
|
+
class n0 done
|
|
420
|
+
class n1 done
|
|
421
|
+
class n2 doing
|
|
422
|
+
class n3 done
|
|
423
|
+
class n4 done
|
|
424
|
+
class n5 done
|
|
425
|
+
class n6 doing
|
|
426
|
+
class n7 done
|
|
427
|
+
class n8 done
|
|
428
|
+
class n9 done
|
|
429
|
+
class n10 done
|
|
430
|
+
class n11 done
|
|
431
|
+
class n12 done
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
<sub>Kept up to date by [Unblock](https://unblock.esh.sh). [Open the live board](https://unblock.esh.sh/gh/sqvist-ab/unblock).</sub>
|
|
435
|
+
|
|
436
|
+
<!-- /unblock:plan -->
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
unblock_cli/__init__.py,sha256=a9LRm1Ck6JCxnO8_GW0bhJo9U2z1anbJdF0p-08TKMw,302744
|
|
2
|
+
unblock_cli/__main__.py,sha256=K6urUhz9vfSGXuwgaNA4kmUJf0GnO3L30gPDlScqYI0,48
|
|
3
|
+
unblock_cli/index.html,sha256=uzdKDXO5JYb8UnCaD8srgNATbxZrm2jMsK7bQc9hbTA,483968
|
|
4
|
+
unblock_cli-0.1.0.dist-info/METADATA,sha256=Npa7_znvd0j9icOF8--iMvN3c1pyOWHXNx-n_Cn1COA,49671
|
|
5
|
+
unblock_cli-0.1.0.dist-info/WHEEL,sha256=MtqajaHUu1y4_-4KPfr0a7FC-j_E3KGUCIoEAoNJR34,84
|
|
6
|
+
unblock_cli-0.1.0.dist-info/entry_points.txt,sha256=14Hte4P2tUqPzHW4hthBSO1eX6oXvaboCLvjozOLfqY,45
|
|
7
|
+
unblock_cli-0.1.0.dist-info/licenses/LICENSE,sha256=z0tFn5cYJziTe8f3y3uuXhnIAsCWnnwSYi0mDLy3NpQ,1066
|
|
8
|
+
unblock_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sqvist AB
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|