jjhub 0.1.15 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jjhub",
3
- "version": "0.1.15",
3
+ "version": "0.3.0",
4
4
  "description": "jjhub — GitHub CLI with Jujutsu-native superpowers: stable change IDs, stacks, platform-wide undo, and conflicts that wait, layered over plain GitHub repos by a JJHub server.",
5
5
  "bin": {
6
6
  "jjhub": "./dist/cli/jjhub.mjs"
@@ -1,9 +1,11 @@
1
1
  ---
2
2
  name: jjhub-cli
3
- description: Drive a running JJHub instance from the command line with the `jjhub` CLI — create and land changes, manage stacks and bookmarks, resolve conflicts, and undo/redo via the operation log. Use when scripting or automating repository operations against a JJHub server instead of the web UI.
3
+ description: Drive a running JJHub instance (a Jujutsu-native change/stack/operation overlay on GitHub) from the command line with the `jjhub` CLI, or as an agent over MCP, ACP, A2A, or WebMCP — create and land changes, manage stacks and bookmarks, resolve conflicts, coordinate local jj workspaces, and undo/redo via the operation log. Use when scripting or automating repository operations against a JJHub server, or wiring an agent client (Claude Code, Zed, a Claude/ChatGPT connector, an A2A peer) up to one.
4
4
  ---
5
5
 
6
- # jjhub — the JJHub CLI
6
+ License: MIT (see the public mirror erisera-code/jjhub-skills, LICENSE).
7
+
8
+ # jjhub — the JJHub CLI and agent surfaces
7
9
 
8
10
  `jjhub` is a GitHub CLI (`gh`)-like wrapper with Jujutsu-native superpowers. It is a thin
9
11
  client over JJHub's HTTP API: every command reads or mutates state on a running JJHub
@@ -11,21 +13,47 @@ server, the same server backing the web UI. Repo context is inferred from the cu
11
13
  directory's git `origin` remote (override with `--repo`), and any top-level command that
12
14
  isn't one of `jjhub`'s own nouns is passed straight through to the real `gh` binary.
13
15
 
16
+ The CLI is one of five interchangeable surfaces over the same server state and the same
17
+ op log — pick whichever fits the client:
18
+
19
+ | Surface | What it is | Entry point |
20
+ |---|---|---|
21
+ | CLI | `jjhub`, a `gh`-alike | `npx jjhub@latest <command>`, or `jjhub` once installed |
22
+ | MCP | 133 structured tools | remote: `<server>/mcp` (OAuth 2.1, RFC 9728 auto-discovery); local: `npx jjhub@latest mcp serve` (stdio) or `--http <port>` |
23
+ | ACP | Agent Client Protocol v1, slash-command style | `npx jjhub@latest acp serve` (stdio) — Zed's agent panel and friends |
24
+ | A2A | Agent2Agent v1.0, one skill per registry command | agent card `<server>/.well-known/agent-card.json`; JSON-RPC `POST <server>/a2a`; REST `<server>/a2a/v1` |
25
+ | WebMCP | 133 tools, progressively loaded | in-browser only, from an open JJHub tab — no separate connection |
26
+
27
+ This file documents the CLI in depth (it is the most complete reference for every verb,
28
+ since ACP/A2A commands are the same verbs under the same names) and points at each other
29
+ surface's own guide for its protocol-specific mechanics. See "Install this skill" below
30
+ for how to wire any of these into an agent client.
31
+
14
32
  ## Setup
15
33
 
16
34
  See the [top-level README](../../README.md) for full project setup (`npm install`,
17
35
  tests, requirements). This section covers just the CLI.
18
36
 
19
- 1. **Start the JJHub server** (if not already running): `npm start` from the repo root.
20
- Defaults to `http://localhost:3000`; set `PORT` to change it, `JJHUB_DB` to point at a
21
- different SQLite file.
37
+ 0. **No install needed**: `npx jjhub@latest <command>` fetches and runs the CLI on demand.
38
+ `npm i -g jjhub` gives a standalone `jjhub` binary if you'd rather not re-fetch every
39
+ invocation.
40
+ 1. **Start the JJHub server** (if not already running, and you're not pointing at a
41
+ deployment): `npm start` from the repo root. Defaults to `http://localhost:3000`; set
42
+ `PORT` to change it, `JJHUB_DB` to point at a different SQLite file.
22
43
  2. **Point the CLI at it**: `export JJHUB_URL=http://localhost:3000` (this is the default,
23
- so only needed if the server is elsewhere). If the server was started with
24
- `JJHUB_API_KEY` set, also `export JJHUB_API_KEY=<the same key>` — every request will
25
- otherwise get a `401 unauthorized`. (`jjhub auth login` persists these to a config file
26
- instead of needing them exported every shell — see "Server connection" below.)
27
- 3. **Invoke commands**: `npm run jjhub -- <command> [args]` in development, or `jjhub <command>`
28
- directly if installed as a binary (`bin/jjhub.mjs`).
44
+ so only needed if the server is elsewhere). Against a real deployment, sign in first —
45
+ `jjhub auth login --url https://<your-jjhub>` (GitHub device flow) saves a bearer token
46
+ to `~/.config/jjhub/config.json`, and every subsequent command picks it up with no env
47
+ vars needed. For a local dev server started with `JJHUB_API_KEY` set, instead
48
+ `export JJHUB_API_KEY=<the same key>` — every request otherwise gets `401 unauthorized`.
49
+ Precedence when both a token and a key are present: `JJHUB_TOKEN` (bearer, e.g. from
50
+ `auth login`) beats `JJHUB_API_KEY` (operator key, sent as `x-api-key`) beats the saved
51
+ config file. Exporting a login token as `JJHUB_API_KEY` does NOT work — the server
52
+ rejects it (401). `JJHUB_CONFIG_DIR` overrides `~/.config/jjhub` for the CLI and every
53
+ agent surface below, if you need an isolated config (a scratch credential, a test
54
+ harness, a second identity).
55
+ 3. **Invoke commands**: `npx jjhub@latest <command> [args]` or `jjhub <command>` once
56
+ installed; from inside this repo in development, `npm run jjhub -- <command> [args]`.
29
57
  4. **Initialize the overlay** for a repo before using any other command:
30
58
  `jjhub repo init [owner/name]` (infers `owner/name` from the cwd's git origin if omitted).
31
59
 
@@ -44,6 +72,9 @@ two interchangeable clients over the same state, not separate systems.
44
72
  ```
45
73
  repo init [owner/name] [--github-token pat]
46
74
  create the JJHub overlay for this repo
75
+ repo create owner/name --public|--private [--description text] [--default-branch name]
76
+ create a NEW repo on GitHub (via the GitHub App) then
77
+ overlay it — --public/--private is required, no default
47
78
  repo list list repositories you can access
48
79
  (permissions come live from GitHub)
49
80
  repo archive | unarchive retire/reactivate (archived = read-only, sync skipped)
@@ -51,7 +82,11 @@ repo delete hard-delete incl. op log (ops credential
51
82
 
52
83
  user list list known users (identity is GitHub-federated)
53
84
 
54
- change new <title> [--parent CHG-x] [--stack id]
85
+ change new <title> [--parent CHG-x] [--stack id] [--route same-repo|unprojected|fork:<owner/name>]
86
+ --route (issue #277): same-repo is the default; unprojected
87
+ creates the change with no GitHub branch/PR ever; fork:<owner/name>
88
+ is recorded but refused at projection time until real fork-PR
89
+ projection ships
55
90
  change list
56
91
  change show <changeId>
57
92
  change describe <changeId> [--title t] [--description d]
@@ -59,49 +94,134 @@ change amend <changeId>
59
94
  change squash <changeId>
60
95
  change abandon <changeId>
61
96
  change restack <changeId> rebase a needs-restack change onto its rewritten ancestor
62
- change commit <changeId> [<file...>] [-m msg] [--delete <path>]...
97
+ change request-review <changeId> move a draft change into review and mark its PR ready for review
98
+ change return-to-draft <changeId> return an in-review change and its PR to draft
99
+ change context <changeId> compact orientation snapshot: status, diff stat, landability,
100
+ checks, conflicts, review health, and derived nextActions in one call
101
+ change commit <changeId> [<file...>] [-m msg] [--delete <path>]... [--expect-head <sha>]
63
102
  commit working-tree files onto the change's branch;
64
- --delete removes a path in the same commit (repeatable)
103
+ --delete removes a path in the same commit (repeatable);
104
+ --expect-head refuses (409) if the change moved since <sha>
105
+ change propose <title> [<file...>] [--parent CHG-x] [--stack id] [-m msg]
106
+ [--delete <path>]... [--expect-base <sha>]
107
+ issue #374: create + commit in one call, printing the diff
108
+ summary and PR link; --expect-base checks --parent's current
109
+ commit; on partial failure the error names the created change
110
+ id (still listed/abandon-able)
65
111
  change diff <changeId> per-file diffs of what the change contains
66
112
  change files <changeId> list locally-stored files (contentMode "local" repos only)
67
- change land <changeId> [--force] waits for real CI triggered by its own ready flip,
68
- re-syncs a behind branch, then merges; --force bypasses
113
+ change land <changeId> [--force] [--expect-head <sha>]
114
+ waits for real CI triggered by its own ready flip,
115
+ re-syncs a behind branch, then merges; --force bypasses;
116
+ --expect-head refuses (409) if the change moved since <sha>
117
+ change enqueue <changeId> add a review-ready pull request to GitHub's merge queue;
118
+ GitHub owns the merge from here, JJHub marks the change
119
+ landed once GitHub reports it merged
120
+ change dequeue <changeId> withdraw a JJHub-queued pull request back to explicit review
121
+ change queue-status <changeId> print the merge-queue receipt (position/state/merge-group
122
+ sha/checks/removed reason) plus whether GitHub auto-merge
123
+ is armed on the pull request
69
124
  change split <changeId> <newTitle>
70
125
  change checks <changeId> list GitHub check runs for a change
71
126
  change actions <changeId> show bounded GitHub Actions workflow and job evidence
72
127
  change actions rerun <changeId> <runId>
73
128
  ask GitHub to re-run a workflow run shown by "change actions"
129
+ change actions cancel <changeId> <runId>
130
+ ask GitHub to cancel a workflow run shown by "change actions"
74
131
  change deployments <changeId> show bounded GitHub deployment evidence
75
132
  change security <changeId> show bounded GitHub code-scanning evidence
76
133
  change issues <changeId> show linked GitHub issue context for a change
77
134
  change reviewers <changeId> reviewer-routing audit trail: every request/remove that
78
135
  actually succeeded against GitHub, who did it, and when
136
+ change recommend-reviewers <changeId> advisory-only reviewer suggestions from CODEOWNERS, prior
137
+ review history, and recent file authorship — never requests
138
+ a reviewer; use "change reviewers" to see actual requests
139
+ change review-health <changeId> live GitHub review-collaboration summary: state, requested
140
+ reviewers, stale/dismissed approvals, unresolved threads,
141
+ CODEOWNERS evidence, and GitHub's effective merge requirement
142
+ change landability <changeId> show the effective GitHub landability policy report — every
143
+ gate's state and resolution, plus the overall verdict
79
144
  change projects <changeId> show bounded GitHub Projects v2 evidence for linked issues
145
+ change handoff <changeId> durable task-handoff log (issue #373): findings, validation
146
+ evidence, outstanding work, or a verified commit SHA another
147
+ agent (or the same one later) can pick up without reconstructing
148
+ a chat transcript; works against a landed or abandoned change too
149
+ change handoff <changeId> --add <kind> -m <body>
150
+ append one entry (never updates/deletes an existing one);
151
+ kind: note | status | question | decision | blocker | plan
152
+ change deps <changeId> list a change's declared read-only external dependencies
153
+ (issue #277); with no flag, lists current state
154
+ change deps <changeId> --set kind:owner/repo#ref [--set ...]
155
+ replace-all declaration of external dependency edges
156
+ (kind is issue|pull_request|release|bookmark); never itself
157
+ reads GitHub and never gates landing (advisory only)
158
+ change deps <changeId> --refresh best-effort live GitHub read of every declared dependency's
159
+ current state (open/closed, released/draft, exists/missing)
160
+ change evolution <changeId> auditable logical-change timeline (issue #275): kind, related
161
+ change ids, before/after evidence, remote recovery limits
162
+ change recover <changeId> --keep left|right|both
163
+ resolve a recorded divergence (issue #275) between two live
164
+ changeIds that both claim the same underlying jj change id;
165
+ left/right abandons the other side, both marks it resolved
166
+ without abandoning either; refuses (409) if either is landed
80
167
 
81
168
  stack create <title> [--id x] [--changes CHG-1,CHG-2]
82
169
  stack list
83
170
  stack land <stackId> [--force] same check-run gate as change land
171
+ stack enqueue <stackId> queue a LINEAR stack root->tip via change enqueue's own path;
172
+ refuses (409) before any GitHub call if the stack isn't linear
173
+ (a member with more than one parent, an unprojected member, or
174
+ a base that doesn't chain onto the previous member); prints each
175
+ member's own outcome (queued/skipped/failed) rather than an
176
+ all-or-nothing result
84
177
  stack delete <stackId> disband — members become loose changes (undoable)
85
178
  stack reorder <stackId> <changeId,changeId,...>
86
179
  stack checks <stackId> every member's GitHub check runs, one batched round trip
87
180
  stack issues <stackId> linked issue and external-blocker summary for a stack
181
+ stack review-health <stackId> per-member review-health summary, one batched round trip
182
+ stack landability <stackId> per-member effective GitHub landability policy report plus
183
+ the overall verdict, one batched round trip; a member blocked
184
+ behind an earlier, not-yet-landable member carries its own
185
+ "stack-order" requirement
186
+
187
+ project rules list list this repo's Projects v2 lifecycle->field mapping rules
188
+ project rules set --project <projectNodeId> --field <fieldNodeId> --option <optionId>
189
+ --when <draft|in_review|queued|landed|abandoned|blocked> [--id <ruleId>] [--disabled]
190
+ project rules delete <ruleId>
191
+ project preview <changeId> preview what "project apply" would do right now, without writing
192
+ project apply <changeId> evaluate and write matching rules now; automatic dispatch already
193
+ runs this on land
88
194
 
89
195
  bookmark list
90
196
  bookmark delete <name> [--force] --force required for the default (trunk) bookmark
91
197
  bookmark set <name> <tracked|view> [--target CHG-x]
92
198
 
199
+ workspace list live local jj workspace agents reporting into this repo
200
+ (see "jjhub daemon"), with their last-seen topology observation
201
+ workspace status agents, which is primary, and any active lease holder
202
+ workspace primary <workspaceId> mark a workspace as this repository's primary
203
+ workspace route [<workspaceId>|clear] show/set/clear the suggested local-write target — a SUGGESTION
204
+ only, never applied implicitly
205
+ workspace connect [aliasOrPath] start a local daemon for a workspace (= "daemon start")
206
+ workspace disconnect [aliasOrPath] stop a local daemon for a workspace (= "daemon stop")
207
+
93
208
  view save <name> <revsetExpression> save a named jj revset expression for later reuse (storage
94
209
  only — evaluate it with "jjhub revset")
95
210
  view list list saved views in this repo
96
211
  view delete <name>
97
212
 
98
213
  op log "@" marks the current head operation
214
+ op diff <fromOpId> <toOpId> structural diff between two operations' immutable snapshots
215
+ (which changes/stacks/bookmarks/conflicts were created,
216
+ modified, or removed); works for adjacent or far-apart ops
99
217
  undo [--force] refused when the head op is a "land" (real merge can't be unwound); --force reverts local state anyway
100
218
  redo
101
219
 
102
- conflict list
220
+ conflict list ACTIONS column: the states each conflict can still take;
221
+ "historical" = its change already landed, nothing to resolve
103
222
  conflict resolve <conflictId> <state>
104
223
  state: unresolved | left | right | edited | preserve_unresolved
224
+ ("superseded" is set by JJHub itself when the change lands; it cannot be chosen)
105
225
 
106
226
  sync push overlay state to GitHub
107
227
 
@@ -115,13 +235,30 @@ auth sessions list your live sessions; auth revoke <id>
115
235
  auth logout forget the saved connection/token
116
236
  auth status who the saved credential authenticates as
117
237
 
118
- mcp serve MCP server over stdio (86 tools) for agent clients
238
+ ai models --provider <p> [--api-key <key>] [--base-url <url>]
239
+ live model list for one AI provider (issue #472); the key
240
+ is used for this one request only and never saved. Without
241
+ --api-key, falls back to your already-saved AI connection's
242
+ key for that same provider, if any. Always succeeds — a
243
+ live-lookup failure shows source: static_fallback with a
244
+ detail message rather than an error, since a free-text model
245
+ id always works when configuring AI, listed or not
246
+ ai status where AI is coming from for you: your own saved connection,
247
+ the server operator's default, or not configured at all
248
+
249
+ mcp serve MCP server over stdio (133 tools) for agent clients
119
250
  mcp serve --http <port> the same tools over Streamable HTTP (POST /)
120
- acp serve Agent Client Protocol agent over stdio (Zed's agent
121
- panel, ...) — every operation as a slash command
251
+ acp serve Agent Client Protocol v1 agent over stdio (Zed's agent
252
+ panel, ...) — modes (read-only/ask/auto), a working-
253
+ repository + dry-run config option, persistent sessions,
254
+ plans and diffs — every operation as a slash command
122
255
  agents-md print an AGENTS.md section teaching coding agents to
123
256
  prefer jjhub over raw git/gh (jjhub agents-md >> AGENTS.md)
124
257
 
258
+ telemetry flows [--since <ISO8601>] [--surface webmcp|cli|browser|mcp|acp|a2a|api] [--limit N]
259
+ ongoing telemetry for the read -> patch -> verify -> land -> disconnect -> recover flow
260
+ across every client surface — per-step p50/p95 duration and error rate
261
+
125
262
  daemon <localRepoPath> [--once] [--interval 5] [--engine auto|subprocess|isomorphic] [--two-way]
126
263
  sync a local jj working copy into JJHub (inbound; --two-way mirrors JJHub edits back)
127
264
  daemon start [alias|localRepoPath] [--interval 5] [--engine ...] [--two-way]
@@ -134,6 +271,12 @@ topology <localRepoPath> [--engine subprocess]
134
271
  revset <localRepoPath> <expression> [--engine subprocess]
135
272
  evaluate the installed jj binary's revset in exactly the named workspace;
136
273
  this is local, read-only discovery rather than a server-side approximation
274
+ revset <expression> --workspace <workspaceId> [--repo owner/name] [--limit N]
275
+ REMOTE form (issue #274): relays the query through the JJHub server to
276
+ that exact workspace's own "jjhub daemon" and polls for the result — use
277
+ when you're not on the machine with the workspace checked out. Slower
278
+ (bound by the daemon's sync interval) and needs that daemon healthy;
279
+ same result shape/error taxonomy as the local form above
137
280
 
138
281
  content preview <localRepoPath> <split|absorb|squash|duplicate|restore>
139
282
  --source <jj-change-id> [--destination <jj-change-id>] [--path <relative-path>]...
@@ -142,6 +285,14 @@ content apply <localRepoPath> <split|absorb|squash|duplicate|restore>
142
285
  local-only jj content curation; checked against jj's immutable policy and
143
286
  never implemented as a browser patch transform; records a durable receipt
144
287
  on the JJHub server (best-effort — see "content receipts" below)
288
+ content preview|apply --workspace <workspaceId> <split|absorb|squash|duplicate|restore>
289
+ --op <json> | (--source <jj-change-id> [--destination <jj-change-id>]
290
+ [--path <relative-path>]... [--title <title>]) [--expected-operation <id>]
291
+ REMOTE form (issue #276): relays the preview/apply through the JJHub server to
292
+ that exact workspace's own "jjhub daemon" and polls for the result — use
293
+ when you're not on the machine with the workspace checked out. jj's own
294
+ immutable-commits check and JJHub's landed-change guard both apply; a
295
+ successful apply also appends a durable content_op_receipts entry
145
296
  content receipts [--repo owner/name|repoId]
146
297
  list this repo's content-op receipt trail: every content apply invocation,
147
298
  successful or rejected, with its kind, revisions, and outcome
@@ -150,10 +301,65 @@ content receipts [--repo owner/name|repoId]
150
301
  Anything else (`pr`, `issue`, ...) is passed through to `gh` — e.g. `jjhub pr view 7`. `jjhub auth`
151
302
  is jjhub's own command (it manages the *JJHub server* connection); use `gh auth` for GitHub auth.
152
303
 
304
+ ### The other agent surfaces: ACP and A2A
305
+
306
+ `jjhub acp serve` and the server's A2A endpoint are not CLI subcommands (A2A has no CLI
307
+ entry point at all — it's always part of the running server), but they run the exact same
308
+ verbs as the table above, through a shared command registry
309
+ (`src/agents/commands.ts`, 139 commands: every operation, the hand-written extras `whoami`,
310
+ `land_change`, `land_stack`, `get_land_job`, `delete_bookmark`, `sync_github`,
311
+ `raise_conflict`, plus registry-only composites `stack_status`, `get_conflict`,
312
+ `get_landability`, `land_when_ready`, `watch_change`, `watch_stack`, `get_skill`, `ask`) — so
313
+ anything documented above as a CLI command is also an ACP slash command and an A2A skill
314
+ under the same snake_case name.
315
+
316
+ **`ask` / `ask_jjhub` (issue #586)** — free text mapped onto ONE command from this same
317
+ registry (deterministic `/command args` parse first, else the caller's own AI connection,
318
+ resolved SERVER-SIDE via `POST /ai/interpret-command` so the process calling it needs no local
319
+ AI env of its own): `jjhub ask "<free text>"`, the A2A skill `ask`, and the MCP tool
320
+ `ask_jjhub` all wrap the exact same interpretation. Prefer it when a caller (an editor agent
321
+ speaking MCP, in particular) only has free text to work with and can't first call
322
+ `list_repositories`/`list_changes`/etc. itself to build a structured call — e.g. an end user
323
+ typed "land CHG-3 in the payments repo" into a chat and the agent should map that onto
324
+ `land_change` without hand-rolling its own NL parsing. Prefer the DIRECT command/tool instead
325
+ whenever the caller already knows exactly which verb and arguments it wants — `ask` adds an
326
+ extra round trip (interpret, then execute) and, for a command needing confirmation (e.g.
327
+ force-land), never bypasses it: it returns the resolved invocation unexecuted and says to call
328
+ the command directly, EXCEPT `ask_jjhub` itself over MCP, which carries the identical
329
+ SEP-2322/elicitation confirmation seam `land_change`/`land_stack`/`delete_bookmark` use, so a
330
+ force-land asked for in free text still asks before it runs. Without an AI provider configured
331
+ for the caller, only a direct `/command args` prompt resolves; the response says so
332
+ (`interpreted: false`, a `reason`) rather than failing.
333
+
334
+ - **ACP** (`jjhub acp serve`, stdio, Agent Client Protocol v1): sessions have modes
335
+ (`read-only` refuses mutations; `ask`, the default, confirms force-land/trunk-deletion/
336
+ force-undo through `session/request_permission` or `elicitation/create`; `auto`
337
+ auto-approves everything else), a `repository` config option so `/get_change CHG-3` works
338
+ without repeating the repository id, a `dry_run` config option, and persistent sessions
339
+ (`session/load`/`list`/`resume`/`close`/`delete`, stored in
340
+ `~/.config/jjhub/acp-sessions.json` — `JJHUB_ACP_SESSIONS_PATH` overrides the file,
341
+ `JJHUB_CONFIG_DIR` moves the whole config dir). A dev server with no `JJHUB_API_KEY` needs
342
+ `JJHUB_ACP_ALLOW_ANONYMOUS=1` on the agent process. Full Zed setup and everything the
343
+ session exposes: [`docs/guides/COMMON-TASKS.md`](../../docs/guides/COMMON-TASKS.md#drive-jjhub-from-an-editor-agent-panel-acp-eg-zed).
344
+ - **A2A** (spec v1.0 only — the v0.3 compatibility layer was removed; no v0.3 clients):
345
+ agent card at `/.well-known/agent-card.json` (optionally signed via
346
+ `JJHUB_A2A_CARD_SIGNING_JWK`, verifiable against `/.well-known/a2a-jwks.json`), JSON-RPC at
347
+ `POST /a2a`, REST at `/a2a/v1` — one task store shared by both bindings, so a task started
348
+ on one can be fetched/listed/cancelled on the other. Call a command as a text part
349
+ (`/list_changes <repositoryId> status=in_review`) or a data part
350
+ (`{"command": "commit_files", "args": {...}}`); confirmable mutations pause the task in
351
+ `input-required` (a `confirm` message resumes it), plans stream as `working` updates, and
352
+ `CancelTask` can interrupt an in-flight skill at its next safe checkpoint. Full detail and
353
+ the `npm run a2a:exercise` / `npm run acp:exercise` client harnesses (including the shared
354
+ `--mutate --stress N --land` scratch-repository stress scenario):
355
+ [`docs/guides/COMMON-TASKS.md`](../../docs/guides/COMMON-TASKS.md#drive-jjhub-agent-to-agent-a2a).
356
+
153
357
  ### Server connection: flags, env vars, or a config file
154
358
 
155
- Precedence is `--flag` > env var (`JJHUB_URL`/`JJHUB_API_KEY`) > `~/.config/jjhub/config.json`
156
- (written by `jjhub auth login`). Run `jjhub auth login --url http://localhost:3000 --api-key <key>`
359
+ Precedence is `--url` > `JJHUB_URL` > `~/.config/jjhub/config.json` for the server, and
360
+ `JJHUB_TOKEN` (bearer token from `jjhub auth login`) > `JJHUB_API_KEY` (operator key, sent as
361
+ `x-api-key`) > the saved config (token, then apiKey) for the credential; the config file is
362
+ written by `jjhub auth login`. Run `jjhub auth login --url http://localhost:3000 --api-key <key>`
157
363
  once and every subsequent command in that shell (any shell, any day) picks it up with no env
158
364
  vars needed. `jjhub auth status` confirms what's currently in effect.
159
365
 
@@ -185,7 +391,14 @@ admin verification (optional mode)" section.
185
391
  For exact, always-current syntax straight from the CLI binary, run `jjhub help` — this file is
186
392
  an expanded, example-rich companion to that output, not a replacement for it.
187
393
 
188
- ### Install into your repo (teach YOUR coding agents to prefer jjhub)
394
+ ## Install this skill
395
+
396
+ Three independent ways to pick this skill up — use whichever fits the agent you're
397
+ configuring. `GET /.well-known/skills/` on any running JJHub server (including the live
398
+ instance) returns all three as an `installation` object, e.g.
399
+ `curl https://jjhub.erisera.com/.well-known/skills/ | jq .installation`.
400
+
401
+ ### (a) Teach a repo's coding agents to prefer jjhub (AGENTS.md)
189
402
 
190
403
  [`agents-snippet.md`](./agents-snippet.md) is a short, imperative AGENTS.md
191
404
  section — "version control in this repo goes through JJHub; prefer `jjhub
@@ -195,11 +408,82 @@ AGENTS.md so any AGENTS.md-aware coding agent (Claude Code, Copilot, Cursor,
195
408
  Codex, ...) picks up the workflow automatically:
196
409
 
197
410
  ```bash
198
- jjhub agents-md >> AGENTS.md # from the CLI
199
- # or fetch it from a running server:
200
- curl https://<your-jjhub>/.well-known/skills/jjhub-cli/agents-snippet.md >> AGENTS.md
411
+ npx jjhub@latest agents-md >> AGENTS.md
412
+ ```
413
+
414
+ ### (b) Fetch this skill directly (`skills.sh`, or a plain curl/`.well-known` fetch)
415
+
416
+ The skill content itself (this file plus `agents-snippet.md`) is mirrored, read-only, to the
417
+ public repo [`erisera-code/jjhub-skills`](https://github.com/erisera-code/jjhub-skills) on
418
+ every change (`.github/workflows/publish-skills.yml`, built by
419
+ `scripts/build-skills-dist.ts` — `npm run build:skills` reproduces it locally). Install from
420
+ there with the [skills.sh](https://skills.sh) installer:
421
+
422
+ ```bash
423
+ npx skills add erisera-code/jjhub-skills
424
+ ```
425
+
426
+ Or mirror it by hand into a local `skills/` directory — the same shape either the live
427
+ server's `GET /.well-known/skills/` or the mirror's own `index.json` returns:
428
+
429
+ ```json
430
+ {
431
+ "skills": [
432
+ { "name": "jjhub-cli", "url": "/.well-known/skills/jjhub-cli/SKILL.md", "agentsSnippet": "/.well-known/skills/jjhub-cli/agents-snippet.md" }
433
+ ],
434
+ "installation": {
435
+ "skillsCli": "npx skills add erisera-code/jjhub-skills",
436
+ "claudeCodeMarketplace": "/plugin marketplace add erisera-code/jjhub-skills",
437
+ "agentsMd": "npx jjhub@latest agents-md >> AGENTS.md",
438
+ "wellKnown": "https://jjhub.erisera.com/.well-known/skills/",
439
+ "repository": "https://github.com/erisera-code/jjhub-skills"
440
+ }
441
+ }
442
+ ```
443
+
444
+ ```bash
445
+ mkdir -p skills/jjhub-cli
446
+ curl -s https://jjhub.erisera.com/.well-known/skills/jjhub-cli/SKILL.md -o skills/jjhub-cli/SKILL.md
447
+ curl -s https://jjhub.erisera.com/.well-known/skills/jjhub-cli/agents-snippet.md -o skills/jjhub-cli/agents-snippet.md
448
+ ```
449
+
450
+ `erisera-code/jjhub-skills` also carries a `.claude-plugin/marketplace.json`, so Claude Code
451
+ can add it directly as a plugin marketplace:
452
+
453
+ ```
454
+ /plugin marketplace add erisera-code/jjhub-skills
455
+ /plugin install jjhub-cli@jjhub-skills
201
456
  ```
202
457
 
458
+ ### (c) Wire up an MCP/ACP client, or an A2A peer
459
+
460
+ - **Claude Code** (MCP): `claude mcp add jjhub --http https://jjhub.erisera.com/mcp`
461
+ against a deployment (OAuth 2.1 discovered automatically via RFC 9728 — the CLI walks you
462
+ through sign-in), or point at a local stdio server:
463
+ ```json
464
+ { "mcpServers": { "jjhub": { "command": "npx", "args": ["jjhub@latest", "mcp", "serve"] } } }
465
+ ```
466
+ - **Zed** (ACP), `~/.config/zed/settings.json`:
467
+ ```json
468
+ {
469
+ "agent_servers": {
470
+ "jjhub": {
471
+ "command": "npx",
472
+ "args": ["jjhub@latest", "acp", "serve"],
473
+ "env": { "JJHUB_URL": "https://jjhub.erisera.com", "JJHUB_TOKEN": "<token from jjhub auth login>" }
474
+ }
475
+ }
476
+ }
477
+ ```
478
+ (Running `jjhub auth login` on the same machine first means the `env` block can be
479
+ omitted entirely — the agent picks up the saved credential from `~/.config/jjhub`.)
480
+ - **A Claude.ai or ChatGPT connector** (remote MCP): add a custom connector pointing at
481
+ `https://<your-jjhub>/mcp` — both discover the OAuth 2.1 flow automatically (RFC 9728
482
+ protected-resource metadata); no manual token entry.
483
+ - **An A2A peer**: fetch `https://<your-jjhub>/.well-known/agent-card.json` for the skill
484
+ list and the two binding URLs (JSON-RPC `/a2a`, REST `/a2a/v1`); see "The other agent
485
+ surfaces: ACP and A2A" above for the auth model and confirmation flow.
486
+
203
487
  ## Example workflows
204
488
 
205
489
  ### Create and land a change
@@ -214,6 +498,25 @@ npm run jjhub -- stack land "Fix bundle"
214
498
 
215
499
  A single change not part of a stack lands directly: `npm run jjhub -- change land CHG-1`.
216
500
 
501
+ ### Use GitHub's merge queue instead of a direct land
502
+
503
+ When the target branch requires it, add a review-ready change to GitHub's
504
+ merge queue rather than merging it directly — GitHub owns the merge from
505
+ there, and JJHub only marks the change landed once GitHub reports it merged:
506
+
507
+ ```bash
508
+ npm run jjhub -- change request-review CHG-1
509
+ npm run jjhub -- change enqueue CHG-1
510
+ npm run jjhub -- change queue-status CHG-1 # position/state/merge-group evidence,
511
+ # removal reason if GitHub removed it,
512
+ # and whether GitHub auto-merge is armed
513
+ npm run jjhub -- change dequeue CHG-1 # withdraw back to explicit review
514
+ ```
515
+
516
+ A change JJHub knows is queued cannot be rewritten (`describe`/`amend`/
517
+ `restack`/...) or landed directly until it is dequeued — `change land`
518
+ refuses with a message naming the queue.
519
+
217
520
  ### Sync a local jj working copy into JJHub
218
521
 
219
522
  ```bash