@cuongtran001/kanna 1.66.0 → 1.68.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.
Files changed (82) hide show
  1. package/README.md +69 -529
  2. package/dist/client/assets/{BoardsPage-D31FaEEf.js → BoardsPage-B7ELOprx.js} +1 -1
  3. package/dist/client/assets/{BoardsRoutePage-CMakUFoG.js → BoardsRoutePage-C2IIJ2gE.js} +1 -1
  4. package/dist/client/assets/{ChatPage-CJ03Ftnh.js → ChatPage-JNQSO8JS.js} +25 -25
  5. package/dist/client/assets/{CronJobRow-CGS9yyPD.js → CronJobRow-Bm-rgCut.js} +1 -1
  6. package/dist/client/assets/{CronJobsPage-Dt74G7Jg.js → CronJobsPage-DIyy1I6S.js} +1 -1
  7. package/dist/client/assets/{FilePreviewSheet-DwGUa4RQ.js → FilePreviewSheet-DW17i61R.js} +1 -1
  8. package/dist/client/assets/{FlowSurface-M0IVqz3_.js → FlowSurface-Ci_DU31t.js} +1 -1
  9. package/dist/client/assets/{GenUIBlock-ClxxVzVx.js → GenUIBlock-NxfHy-M-.js} +2 -2
  10. package/dist/client/assets/{KannaSocketProvider-DvGKXQaL.js → KannaSocketProvider-sMDsfeD8.js} +1 -1
  11. package/dist/client/assets/{LocalProjectsPage-BhuE-ZFH.js → LocalProjectsPage-x2TKJhR_.js} +1 -1
  12. package/dist/client/assets/SettingsPage-EwyjbN1B.js +9 -0
  13. package/dist/client/assets/{StackBoardsRoutePage-CjGnPOZV.js → StackBoardsRoutePage-DoNMNMWu.js} +1 -1
  14. package/dist/client/assets/{ToolCallMessage-CE6M9Snv.js → ToolCallMessage-D5EQmHMc.js} +2 -2
  15. package/dist/client/assets/{VChartSurface-DrJrtbfo.js → VChartSurface-DePif5KB.js} +1 -1
  16. package/dist/client/assets/{WorkflowsPage-DPlFUIlp.js → WorkflowsPage-CAeXbGX-.js} +1 -1
  17. package/dist/client/assets/{WorkflowsSection-DBI_-g4k.js → WorkflowsSection-DZgaorNI.js} +1 -1
  18. package/dist/client/assets/{chart-renderer-Dsvifv2S.js → chart-renderer-BidnUcku.js} +1 -1
  19. package/dist/client/assets/{chatSounds-BXLkxmTk.js → chatSounds-RaoVyHro.js} +1 -1
  20. package/dist/client/assets/chevron-left-DiYVFQ-b.js +1 -0
  21. package/dist/client/assets/{clipboard.adapter-CNlTjwvW.js → clipboard.adapter-Bru5Dd09.js} +1 -1
  22. package/dist/client/assets/{columnStyle-DSoD_MHh.js → columnStyle-B9_33loX.js} +1 -1
  23. package/dist/client/assets/copyFeedbackStore-CRZtJg63.js +1 -0
  24. package/dist/client/assets/{createScopedStore-C53mSZz7.js → createScopedStore-RyJsCnYw.js} +1 -1
  25. package/dist/client/assets/{dist-BoqRTts9.js → dist-wxLwRaXU.js} +1 -1
  26. package/dist/client/assets/{ellipsis-45QtxVih.js → ellipsis-ZBAJ0CUB.js} +1 -1
  27. package/dist/client/assets/{flow-kinds-DHpUe9Cp.js → flow-kinds-CoeC8H7K.js} +1 -1
  28. package/dist/client/assets/{folder-DruQ8_0V.js → folder-CGggBLMI.js} +1 -1
  29. package/dist/client/assets/{formatDuration-CEhz1BlD.js → formatDuration-3n1BKehq.js} +1 -1
  30. package/dist/client/assets/{genui-BQ6wCzAf.js → genui-BcOst0zM.js} +1 -1
  31. package/dist/client/assets/{globe-CXmc6hht.js → globe-CBkFFuZO.js} +1 -1
  32. package/dist/client/assets/{host-C1OHWx7L.js → host--QhnGtJL.js} +1 -1
  33. package/dist/client/assets/{index-D6ymT4lH.js → index-BrruRg1Z.js} +3 -3
  34. package/dist/client/assets/index-D8o_YX_z.css +1 -0
  35. package/dist/client/assets/{info-BnLGGQxB.js → info-DNotXw4U.js} +1 -1
  36. package/dist/client/assets/{input-5NMhvELM.js → input-DN-wCafJ.js} +1 -1
  37. package/dist/client/assets/{lexicalToReact-BXLHOiaS.js → lexicalToReact-6yHqBclr.js} +3 -3
  38. package/dist/client/assets/{log-BMpe3lw-.js → log-D_3UCXAU.js} +1 -1
  39. package/dist/client/assets/{monitor-q3eoCx7V.js → monitor-Du7U0ywV.js} +1 -1
  40. package/dist/client/assets/{pendingActionsStore-D91Xy3Q0.js → pendingActionsStore-BXtQ_f7y.js} +1 -1
  41. package/dist/client/assets/{play-BcMltSpn.js → play-EtSzD4P1.js} +1 -1
  42. package/dist/client/assets/{plus-D1bORwKT.js → plus-CwgNbCIY.js} +1 -1
  43. package/dist/client/assets/{popover-B0-UTvQH.js → popover-Dr1G9kvL.js} +1 -1
  44. package/dist/client/assets/{pushClient-azXJMyCS.js → pushClient-cZX8cRVK.js} +1 -1
  45. package/dist/client/assets/runDetached-D41_1-Bs.js +1 -0
  46. package/dist/client/assets/{segmented-control-CpSpmtF8.js → segmented-control-C_6NA7K6.js} +1 -1
  47. package/dist/client/assets/{select-CaNlTSfr.js → select-UrSTK6MF.js} +1 -1
  48. package/dist/client/assets/{settings-header-button-CwzBDZw5.js → settings-header-button-D0WggErO.js} +1 -1
  49. package/dist/client/assets/{sidebarPendingActions-rvOoGKjN.js → sidebarPendingActions-e6zpVjF4.js} +1 -1
  50. package/dist/client/assets/{square-pen-zNNjlljm.js → square-pen-D3XQCcso.js} +1 -1
  51. package/dist/client/assets/{state-mark-c9jXgPOm.js → state-mark-Brx5EQWB.js} +1 -1
  52. package/dist/client/assets/{status-pill-BjyjxzWG.js → status-pill-CKCRwrnp.js} +1 -1
  53. package/dist/client/assets/{terminal-CE--FdY9.js → terminal-MXZ2nH2C.js} +1 -1
  54. package/dist/client/assets/{timer.adapter-CHM1rThy.js → timer.adapter-BjNLYZ-F.js} +1 -1
  55. package/dist/client/assets/{tools-Dn1WRzVk.js → tools-CMoFNV10.js} +1 -1
  56. package/dist/client/assets/{triangle-alert-AK9DKrnh.js → triangle-alert-Dm_D6vNy.js} +1 -1
  57. package/dist/client/assets/{useAppGlobalState-IYry06dM.js → useAppGlobalState-Dr5gGunk.js} +1 -1
  58. package/dist/client/assets/{useKannaState-Bm78hI5J.js → useKannaState-Dj0r2TIZ.js} +1 -1
  59. package/dist/client/assets/{useTheme-grZwCapr.js → useTheme-yKMme2ZZ.js} +1 -1
  60. package/dist/client/index.html +34 -34
  61. package/package.json +4 -2
  62. package/src/server/beacon-connection.test.ts +56 -3
  63. package/src/server/beacon-connection.ts +55 -10
  64. package/src/server/beacon-pairing.ts +1 -2
  65. package/src/server/beacon-registry.test.ts +46 -15
  66. package/src/server/beacon-registry.ts +29 -4
  67. package/src/server/beacon-services.test.ts +58 -0
  68. package/src/server/beacon-services.ts +8 -2
  69. package/src/server/kanna-mcp-beacon.test.ts +2 -0
  70. package/src/server/ws-router-utils.ts +1 -0
  71. package/src/shared/beacon-conformance.test.ts +65 -0
  72. package/src/shared/beacon-pair-link.test.ts +32 -0
  73. package/src/shared/beacon-pair-link.ts +60 -0
  74. package/src/shared/beacon-protocol.test.ts +31 -0
  75. package/src/shared/beacon-protocol.ts +64 -3
  76. package/src/shared/beacon-scope.test.ts +25 -0
  77. package/src/shared/beacon-scope.ts +26 -1
  78. package/dist/client/assets/SettingsPage-C6nacIW7.js +0 -9
  79. package/dist/client/assets/chevron-left-CO-Hpw58.js +0 -1
  80. package/dist/client/assets/editor-icons-Dla5b-gH.js +0 -1
  81. package/dist/client/assets/index-D1WB5Sx6.css +0 -1
  82. package/dist/client/assets/runDetached-CDRpTZF4.js +0 -1
package/README.md CHANGED
@@ -5,11 +5,7 @@
5
5
  <h1 align="center">Kanna</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>A beautiful web UI for the Claude Code & Codex CLIs</strong>
9
- </p>
10
-
11
- <p align="center">
12
- <em>OAuth token pooling, multi-provider chat (Claude + Codex + OpenRouter), subagent orchestration, custom MCP servers, a workflow status panel, durable tool-approval protocol, in-app self-update, and more.</em>
8
+ <strong>A web UI for the Claude Code and Codex agents, built for long sessions across many projects.</strong>
13
9
  </p>
14
10
 
15
11
  <p align="center">
@@ -20,7 +16,9 @@
20
16
  </p>
21
17
 
22
18
  <p align="center">
23
- 📖 <strong>Docs:</strong> <a href="https://kanna-wiki.lowbit.link">kanna-wiki.lowbit.link</a>
19
+ <a href="https://kanna-wiki.lowbit.link"><strong>Documentation</strong></a> ·
20
+ <a href="https://kanna-wiki.lowbit.link/getting-started/install/">Install guide</a> ·
21
+ <a href="CHANGELOG.md">Changelog</a>
24
22
  </p>
25
23
 
26
24
  <br />
@@ -33,530 +31,109 @@
33
31
  </picture>
34
32
  </p>
35
33
 
36
- <br />
37
-
38
- ## About Kanna
34
+ ## What Kanna is
39
35
 
40
- Kanna (`@cuongtran001/kanna`) is a clean web UI for the Claude Code and Codex CLIs, built for heavier day-to-day use, multi-account billing, and self-hosting.
41
-
42
- **Headline features:**
43
-
44
- - **OAuth token pool** — register multiple Claude OAuth tokens; Kanna rotates across them per chat with automatic fallover on rate-limit and an explicit disabled state.
45
- - **Multi-provider chat** — switch between Claude, Codex (OpenAI), and OpenRouter from the composer with per-provider model + reasoning-effort controls and Codex fast mode. OpenRouter populates its model picker live from the public catalog (tool-capable models).
46
- - **Subagent orchestration** — first-class subagent CRUD, `@agent/` mentions, parallel runs, live activity labels, MCP progress notifications, and `mcp__kanna__delegate_subagent` so the main agent itself can delegate — including **keep-alive multi-turn** sessions (`send_subagent_message` / `close_subagent`) and **background** runs that report back as a fresh turn.
47
- - **Custom MCP servers** — register your own `stdio` / `http` / `sse` / `ws` MCP servers from Settings (with OAuth 2.1 for network transports); they merge into every Claude session and their tools surface as `mcp__<name>__<tool>`.
48
- - **Workflow status panel** — read-only per-chat panel surfacing Claude Code's native Workflow tool runs (live status, drill-in progress, token totals) via disk-watch.
49
- - **Agent self-scheduled wake** — `mcp__kanna__schedule_wakeup` lets the agent re-enter the chat to harvest long-running background work, with a runaway-loop cap.
50
- - **Durable tool-approval protocol** (`KANNA_MCP_TOOL_CALLBACKS=1`) — pending `AskUserQuestion` / `ExitPlanMode` / built-in shims survive server restart and replay to the client on reconnect.
51
- - **Cloudflare `expose_port` MCP tool** — agent-callable port exposure with always-ask or auto-expose modes, replacing bash-output sniffing.
52
- - **In-app self-update** — one-click pull/rebuild/reload with a host-agnostic supervisor (works under pm2, systemd, docker, plain shell) or direct pm2 reload; install any prior release straight from the changelog UI.
53
- - **Git worktree isolation** per chat, **bulk import** of existing `~/.claude/projects/` sessions, **proactive context compaction**, **per-turn token cost**, **local skills & slash commands** in the composer `/` picker, **password gate** for HTTP/WS/API, **PWA / mobile layout**, **mermaid rendering** in transcripts, **standalone HTML transcript export**, and **customizable keybindings**.
54
-
55
- See the full inventory in [Features](#features) below.
36
+ Kanna runs on your machine and puts a browser UI in front of the coding agents you already use: the Claude Agent SDK, the Codex app-server, and OpenRouter models. Every chat belongs to a project, every event is saved to disk, and the UI works on a desktop, a phone, or through a tunnel.
56
37
 
57
38
  ## Quickstart
58
39
 
59
40
  ```bash
41
+ curl -fsSL https://bun.sh/install | bash # if you don't have Bun 1.3.11+
60
42
  bun install -g @cuongtran001/kanna
43
+ kanna # opens http://localhost:3210
61
44
  ```
62
45
 
63
- If Bun isn't installed, install it first:
46
+ Add a Claude OAuth token, a Codex login, or an OpenRouter key in **Settings → Providers**, open a project folder, and start a chat. The [install guide](https://kanna-wiki.lowbit.link/getting-started/install/) covers requirements and flags.
64
47
 
65
- ```bash
66
- curl -fsSL https://bun.sh/install | bash
67
- ```
48
+ ## How it fits together
68
49
 
69
- Then run from any project directory:
70
-
71
- ```bash
72
- kanna
73
- ```
50
+ <p align="center">
51
+ <picture>
52
+ <source media="(prefers-color-scheme: dark)" srcset="assets/diagrams/kanna-architecture-dark.svg" />
53
+ <img src="assets/diagrams/kanna-architecture.svg" alt="Architecture diagram: the Kanna web app talks to a Bun server over WebSocket; its agent coordinator records every event, runs turns on the Claude Agent SDK or the Codex app-server, and those agents edit your project worktree and call your MCP servers and Kanna's own tools." width="900" />
54
+ </picture>
55
+ </p>
74
56
 
75
- That's it. Kanna opens in your browser at [`localhost:3210`](http://localhost:3210).
57
+ One Bun process owns everything. The browser sends commands over a WebSocket, the agent coordinator runs each turn on a provider, and every change lands in an append-only event log under `~/.kanna/data`. Refresh the page or restart the server and the chat comes back exactly as it was.
76
58
 
77
59
  ## Features
78
60
 
79
- **Providers & models**
80
-
81
- - **Multi-provider support** — switch between Claude, Codex (OpenAI), and OpenRouter from the chat input, with per-provider model selection, reasoning-effort controls, and Codex fast mode
82
- - **OpenRouter** — set an OpenRouter API key in Settings; the model picker populates live from OpenRouter's catalog (tool-capable models), routed through its Anthropic-compatible endpoint
83
- - **OAuth token pool** — register multiple Claude OAuth tokens; Kanna rotates across them per chat
84
- - **Custom MCP servers** — register `stdio` / `http` / `sse` / `ws` MCP servers from Settings (OAuth 2.1 for network transports); merged into every Claude session
85
-
86
- **Chat & transcript**
87
-
88
- - **Rich transcript rendering** — hydrated tool calls, collapsible tool groups, plan-mode dialogs, and interactive prompts with full result display
89
- - **Inline diff viewer** — file and commit diffs rendered directly in the transcript
90
- - **Embedded terminal** — per-project xterm terminal in a resizable side panel (macOS/Linux)
91
- - **File & image uploads** — drag-and-drop attachments into the composer
92
- - **Slash commands & @-mentions** — in-composer pickers for slash commands (including local Claude Code skills/commands), file mentions, and subagents
93
- - **Plan mode** — review and approve agent plans before execution
94
- - **Subagent orchestration** — run and track parallel subagents within a turn, plus keep-alive multi-turn sessions and non-blocking background runs that report back as a fresh turn
95
- - **Workflow status panel** — read-only per-chat view of Claude Code's native Workflow tool runs with live status and drill-in progress
96
- - **Agent self-scheduled wake** — the agent can re-enter the chat to harvest long-running background work (`schedule_wakeup`), with a runaway-loop cap
97
- - **Background tasks** — long-running tasks tracked out-of-band with a status indicator
98
- - **Per-turn token cost** — token usage and estimated USD cost shown inline per turn
99
- - **Auto-continue** — optionally continue a turn automatically when the agent stops short
100
- - **Proactive compaction** — context-window meter with automatic transcript compaction before limits are hit
101
-
102
- **Projects & sessions**
103
-
104
- - **Project-first sidebar** — chats grouped under projects, with live status indicators (idle, running, waiting, failed)
105
- - **Drag-and-drop project ordering** — reorder project groups in the sidebar with persistent ordering
106
- - **Local project discovery** — auto-discovers projects from both Claude and Codex local history
107
- - **Bulk import Claude Code sessions** — one-click import of existing `~/.claude/projects/` sessions with full transcript and seamless resume via the Claude Agent SDK
108
- - **Git worktree isolation** — run a chat in an isolated worktree without disturbing your working tree
109
- - **Session resumption** — resume agent sessions with full context preservation
110
- - **Auto-generated titles** — chat titles generated in the background via Claude Haiku
111
- - **Quick responses** — lightweight structured queries (e.g. title generation) via Haiku with automatic Codex fallback
112
-
113
- **Persistence & realtime**
114
-
115
- - **Persistent local history** — refresh-safe routes backed by append-only JSONL event logs and compacted snapshots
116
- - **WebSocket-driven** — real-time subscription model with reactive state broadcasting
117
- - **Standalone transcript export** — export a chat as a self-contained HTML viewer
118
-
119
- **Access & notifications**
120
-
121
- - **Password protection** — optional launch password gating the app, WebSocket, and API routes
122
- - **Public share link** — `--share` creates a temporary `trycloudflare.com` URL with a terminal QR code
123
- - **Cloudflare tunnel via `expose_port` tool** — opt-in; the agent proactively calls the Kanna `expose_port` MCP tool with a port. In `always-ask` mode Kanna shows an inline "expose via Cloudflare" card for you to accept; in `auto-expose` mode `cloudflared tunnel --url` spawns immediately. Both modes are gated by the Cloudflare Tunnel setting
124
- - **Web push & sound notifications** — browser push and sound alerts when a chat needs attention
125
- - **Customizable keybindings** — user-editable keyboard shortcuts
126
- - **In-app self-update** — one-click update that pulls, rebuilds, and hot-reloads (host-agnostic supervisor or pm2)
127
- - **Mobile-friendly** — responsive layout, installable as a standalone PWA
128
-
129
- ## Architecture
130
-
131
- ```mermaid
132
- flowchart LR
133
- Browser["Browser<br/>React + Zustand"]
134
-
135
- subgraph Server["Bun Server (src/server/**)"]
136
- direction TB
137
- WS["WSRouter<br/>subscriptions + commands"]
138
- Auth["Auth gate"]
139
- Agent["AgentCoordinator<br/>multi-provider turns"]
140
- ES["EventStore<br/>append-only JSONL + snapshots"]
141
- RM["ReadModels<br/>derived views"]
142
- Diff["DiffStore"]
143
- Term["TerminalManager"]
144
- Up["Uploads"]
145
- Disc["Discovery"]
146
- Push["Push"]
147
- Tun["Share / Tunnel"]
148
- Upd["UpdateManager"]
149
-
150
- subgraph Adapters["*.adapter.ts (IO seal exempt)"]
151
- direction LR
152
- FsA["fs / chokidar"]
153
- DbA["bun:sqlite / pg"]
154
- SpA["Bun.spawn / child_process"]
155
- HtA["node:http / fetch"]
156
- PtyA["Bun.Terminal (PTY)"]
157
- end
158
-
159
- WS --> Agent
160
- WS --> ES
161
- WS --> RM
162
- Agent --> ES
163
- Agent -.spawn.-> SpA
164
- ES -.fs.-> FsA
165
- Diff -.fs+spawn.-> SpA
166
- Diff -.fs.-> FsA
167
- Term -.pty.-> PtyA
168
- Up -.fs.-> FsA
169
- Disc -.fs.-> FsA
170
- Tun -.spawn+http.-> SpA
171
- Tun -.http.-> HtA
172
- Upd -.spawn.-> SpA
173
- end
174
-
175
- subgraph Shared["src/shared/** (pure)"]
176
- Proto["protocol types"]
177
- Types["domain types"]
178
- end
179
-
180
- subgraph External["External processes"]
181
- CC["Claude Agent SDK / claude CLI"]
182
- CX["Codex App Server"]
183
- FS["Local FS<br/>~/.kanna/data/, project dirs"]
184
- end
185
-
186
- Browser <-->|WebSocket| WS
187
- Browser -.types.-> Shared
188
- Server -.types.-> Shared
189
-
190
- SpA --> CC
191
- SpA --> CX
192
- PtyA --> CC
193
- FsA --> FS
194
- ```
195
-
196
- **Layer rules (lint-enforced, see [CLAUDE.md](./CLAUDE.md#side-effect-lint-ports-and-adapters-seal)):**
197
-
198
- - `src/shared/**` + `src/client/**` — pure. ESLint `no-restricted-imports` errors on `node:fs`, `bun:sqlite`, `node:child_process`, `node:http`, `Bun.spawn`, `Bun.file`, `Bun.serve`, …
199
- - `src/server/**` production — also sealed at `error`. Side-effect call sites only allowed inside files matching `**/*.adapter.ts` (or the legacy `src/server/adapters/**` dir).
200
- - Mixed-concern modules extract their IO into a sibling `*-io.adapter.ts` and import through it.
201
-
202
- **Key patterns:** Event sourcing for all state mutations. CQRS with separate write (event log) and read (derived snapshots) paths. Reactive broadcasting — subscribers get pushed fresh snapshots on every state change. Multi-provider agent coordination with tool gating for user-approval flows. Provider-agnostic transcript hydration for unified rendering.
203
-
204
- ### Workflow: adding code that touches IO
205
-
206
- ```mermaid
207
- flowchart TD
208
- Start(["You need fs / spawn / http / DB / Bun globals"]) --> Layer{"Which layer?"}
209
- Layer -->|src/shared or src/client| Reject["ESLint errors at CI"]
210
- Reject --> Move["Move the module to src/server/**<br/>or inject through a typed parameter"]
211
- Move --> Server
212
- Layer -->|src/server| Server{"File responsibility?"}
213
- Server -->|leaf IO wrapper| RenameAdapter["Name it foo.adapter.ts<br/>(exempt from seal)"]
214
- Server -->|mixed domain + IO| SiblingAdapter["Extract calls into foo-io.adapter.ts<br/>keep domain logic in foo.ts<br/>import helpers from the adapter"]
215
- Server -->|domain only| Port["Take a typed port parameter<br/>provided by caller's adapter"]
216
- RenameAdapter --> Lint["bun run lint"]
217
- SiblingAdapter --> Lint
218
- Port --> Lint
219
- Lint --> CI(["CI: lint + tests + build"])
220
- ```
61
+ **Chat with any agent**
221
62
 
222
- For the longer story (90 → 0 burndown, ratchet pipeline retired in PR #303) see the **Side-Effect Lint** section of `CLAUDE.md`.
63
+ - **Claude, Codex and OpenRouter** in one composer, with per-chat model, reasoning effort and context-window controls. [Providers & models](https://kanna-wiki.lowbit.link/features/providers-models/)
64
+ - **OAuth token pool**: register several Claude tokens and Kanna rotates between them, failing over when one hits a rate limit. [OAuth pool](https://kanna-wiki.lowbit.link/getting-started/oauth-pool-setup/)
65
+ - **Live output**: on Claude chats, thinking, text and tool input stream in as the model writes them.
66
+ - **Rich transcript**: grouped tool calls, inline diffs, plan-mode approval, mermaid diagrams checked before you see them, and interactive charts and reports the agent composes ([generative UI](https://kanna-wiki.lowbit.link/features/generative-ui/)).
67
+ - **Slash commands everywhere**: `/clear`, `/compact`, `/cron`, plus your local Claude Code skills, on every provider. [Slash commands](https://kanna-wiki.lowbit.link/features/slash-commands/)
223
68
 
224
- ## Requirements
69
+ **Let it work while you're away**
225
70
 
226
- - [Bun](https://bun.sh) v1.3.11+
227
- - A working [Claude Code](https://docs.anthropic.com/en/docs/claude-code) environment
228
- - _(Optional)_ [Codex CLI](https://github.com/openai/codex) for Codex provider support
71
+ <p align="center">
72
+ <picture>
73
+ <source media="(prefers-color-scheme: dark)" srcset="assets/diagrams/kanna-turn-queue-dark.svg" />
74
+ <img src="assets/diagrams/kanna-turn-queue.svg" alt="Fan-in diagram: a typed message, a cron job, a loop wake, a background subagent and a board card all enter one durable per-chat queue, which starts a turn only when the chat is idle and writes its output to the transcript." width="900" />
75
+ </picture>
76
+ </p>
229
77
 
230
- Embedded terminal support uses Bun's native PTY APIs and currently works on macOS/Linux.
78
+ - **Subagents**: named agents with their own prompts and tools. The main agent delegates, runs them in parallel or in the background, and gets the result back as a new turn. [Subagents](https://kanna-wiki.lowbit.link/guides/user/subagents/)
79
+ - **Autonomous loops**: give a goal and a verify command; Kanna works through a durable task list until the check passes and stops on its own. [Loops](https://kanna-wiki.lowbit.link/features/loops/)
80
+ - **Cron jobs**: `/cron` schedules a recurring instruction, down to the second, in the same chat or a fresh one. [Cron jobs](https://kanna-wiki.lowbit.link/features/cron-jobs/)
81
+ - **Durable queue**: messages, wakes and scheduled runs wait in a per-chat queue that survives a server restart.
82
+ - **Background tasks and workflows**: dev servers, long builds and Claude Code workflows show live status, and Kanna keeps their session alive while they run.
231
83
 
232
- ## Install
84
+ **Organize the work**
233
85
 
234
- Install Kanna globally:
86
+ <p align="center">
87
+ <picture>
88
+ <source media="(prefers-color-scheme: dark)" srcset="assets/diagrams/kanna-board-card-dark.svg" />
89
+ <img src="assets/diagrams/kanna-board-card.svg" alt="State diagram of a board card: Kanna moves it to In progress on Start work and creates its worktree and chat, the agent advances it one column when the work is verified, and only you move it to Done, which asks whether to merge, discard or keep the worktree." width="900" />
90
+ </picture>
91
+ </p>
235
92
 
236
- ```bash
237
- bun install -g @cuongtran001/kanna
238
- ```
93
+ - **Projects and session tabs**: chats grouped by project with live status, a split-pane workspace, a project quick switcher, and one-click import of existing Claude Code sessions. [Projects & sessions](https://kanna-wiki.lowbit.link/features/projects-sessions/)
94
+ - **Kanban boards**: *Start work* on a card creates a branch, a git worktree and a chat, so several agents can work at once without touching each other's files. Boards sync with GitHub issues. [Boards](https://kanna-wiki.lowbit.link/features/boards/)
95
+ - **Multi-repo stacks**: group several repositories so one chat can read and edit across all of them. [Stacks](https://kanna-wiki.lowbit.link/features/multi-repo-stacks/)
239
96
 
240
- If Bun isn't installed, install it first:
97
+ **Extend and connect**
241
98
 
242
- ```bash
243
- curl -fsSL https://bun.sh/install | bash
244
- ```
99
+ - **Your MCP servers** (`stdio`, `http`, `sse`, `ws`, with OAuth 2.1) are available in every chat. [Advanced](https://kanna-wiki.lowbit.link/features/advanced/)
100
+ - **Kanna plugins** add sidebar pages, panels and `/` commands, with a server half running in its own process. [Plugins](https://kanna-wiki.lowbit.link/features/plugins/)
101
+ - **Beacons** pair another machine you own, so the agent can read files or run commands there within limits you set. [Beacons](https://kanna-wiki.lowbit.link/guides/user/beacons/)
102
+ - **Package updates** for installed skills, Claude Code plugins and Codex plugins, checked and applied from Settings. [Package auto-update](https://kanna-wiki.lowbit.link/features/package-auto-update/)
245
103
 
246
- Or clone and build from source:
104
+ **Run it anywhere**
247
105
 
248
- ```bash
249
- git clone https://github.com/cuongtranba/kanna.git
250
- cd kanna
251
- bun install
252
- bun run build
253
- ```
106
+ - **Password gate**, LAN or Tailscale binding, and Cloudflare tunnels (`--share`, `--cloudflared`), plus an agent tool that exposes a dev server on request. [Self-host](https://kanna-wiki.lowbit.link/guides/ops/self-host/)
107
+ - **Read-only share links** that render a chat exactly as you see it. [Session share](https://kanna-wiki.lowbit.link/sharing/session-share/)
108
+ - **Resumable uploads** for multi-gigabyte files, even behind a tunnel's request-size limit.
109
+ - **Web push, PWA install, phone layout**, customizable keybindings, and **in-app self-update** under pm2, systemd, Docker or a plain shell. [Self-update](https://kanna-wiki.lowbit.link/guides/ops/self-update/)
254
110
 
255
111
  ## Usage
256
112
 
257
113
  ```bash
258
- kanna # start with defaults (localhost only)
259
- kanna --port 4000 # custom port
260
- kanna --strict-port # fail instead of trying another port
261
- kanna --no-open # don't open browser
262
- kanna --password <secret> # require a password before loading the app
263
- kanna --share # create a public quick tunnel + terminal QR
264
- kanna --cloudflared <token> # run a named Cloudflare tunnel from a token
265
- ```
266
-
267
- Default URL: `http://localhost:3210`
268
-
269
- ### Network access (Tailscale / LAN)
270
-
271
- By default Kanna binds to `127.0.0.1` (localhost only). Use `--host` to bind a specific interface, or `--remote` as a shorthand for `0.0.0.0`:
272
-
273
- ```bash
274
- kanna --remote # bind all interfaces — browser opens localhost:3210
275
- kanna --host dev-box # bind to a specific hostname — browser opens http://dev-box:3210
276
- kanna --host 192.168.1.x # bind to a specific LAN IP
277
- kanna --host 100.64.x.x # bind to a specific Tailscale IP
278
- ```
279
-
280
- When `--host <hostname>` is given, the browser opens `http://<hostname>:3210` automatically. Other machines on your network can connect to the same URL:
281
-
282
- ### Password protection
283
-
284
- Use `--password` to require a launch password before the app or websocket can connect:
285
-
286
- ```bash
287
- kanna --password my-secret
288
- bun run dev --password my-secret
289
- ```
290
-
291
- Kanna verifies the password once, then sets a browser-session cookie. The password itself is not stored in the browser.
292
- When password protection is enabled, the backend requires authentication for API routes and `/ws`. The SPA shell still loads, `/health` remains public for restart detection, and the same in-app password screen is used in both dev and production.
293
-
294
- ### Public share link
295
-
296
- Use `--share` to create a temporary public `trycloudflare.com` URL and print a terminal QR code:
297
-
298
- ```bash
299
- kanna --share
300
- kanna --share --port 4000
301
- kanna --cloudflared <token>
302
- ```
303
-
304
- `--share` is incompatible with `--host` and `--remote`. It does not open a browser automatically.
305
-
306
- Without a token, it prints:
307
-
308
- ```text
309
- QR Code:
310
- ...
311
-
312
- Public URL:
313
- https://<random>.trycloudflare.com
314
-
315
- Local URL:
316
- http://localhost:3210
114
+ kanna # localhost only, opens a browser
115
+ kanna --port 4000 # custom port
116
+ kanna --remote # bind 0.0.0.0 (LAN, Tailscale)
117
+ kanna --password <secret> # require a password for the app, WebSocket and API
118
+ kanna --share # temporary public trycloudflare.com URL + terminal QR
119
+ kanna --cloudflared <token> # run a named Cloudflare tunnel
120
+ kanna plugin ls # manage Kanna plugins
317
121
  ```
318
122
 
319
- With `--cloudflared <token>`, Kanna runs `cloudflared tunnel run --token <token> --url <local-url>`.
320
- If Kanna can detect the public hostname from cloudflared output, it prints the same QR/public/local block.
321
- If not, it keeps the tunnel running, warns that no public hostname was detected, and prints the local URL so you can use the hostname already configured for that tunnel in Cloudflare.
322
-
323
- ### Auto-expose detected ports
324
-
325
- When the agent runs a Bash command in a chat (`bun run dev`, `go run`, `uvicorn`, etc.), Kanna can detect any listening port from the command's stdout and offer to expose it through a Cloudflare quick tunnel without leaving the chat.
326
-
327
- Enable from **Settings → Cloudflare Tunnel**:
328
-
329
- - **Toggle** — opt-in (off by default)
330
- - **Mode** — `Always ask` (one card per detected port; click Expose to spawn) or `Auto-expose` (spawn immediately on detection)
331
- - **`cloudflared` path** — defaults to `cloudflared` on `$PATH`
332
-
333
- Each detected port shows up inline in the transcript. Click **Expose**, watch the spinner until cloudflared returns the `*.trycloudflare.com` URL, then click **Stop** when done. Tunnels are also stopped automatically when the chat closes or the server restarts.
334
-
335
- Requires the `cloudflared` binary installed locally — `brew install cloudflared` on macOS, or see [Cloudflare's downloads](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/).
123
+ `kanna --help` lists every flag. State lives in `~/.kanna`; back it up to keep your chat history. Deployment recipes for [pm2](https://kanna-wiki.lowbit.link/guides/ops/pm2/), [systemd](https://kanna-wiki.lowbit.link/guides/ops/systemd/), [Docker](https://kanna-wiki.lowbit.link/guides/ops/docker/) and a [Cloudflare tunnel](https://kanna-wiki.lowbit.link/guides/ops/cloudflare-tunnel/) are in the wiki.
336
124
 
337
125
  ## Development
338
126
 
339
127
  ```bash
128
+ git clone https://github.com/cuongtranba/kanna.git && cd kanna
340
129
  bun install
341
- bun run setup:hooks # wire the gitleaks pre-commit hook (one-time per clone/worktree)
342
- bun run dev
343
- ```
344
-
345
- The same `--remote` and `--host` flags can be used with `bun run dev` for remote development.
346
- `--share` is also supported in dev mode and exposes the Vite client URL publicly:
347
-
348
- ```bash
349
- bun run dev --share
350
- bun run dev --cloudflared <token>
351
- bun run dev --port 3333 --share
352
- ```
353
-
354
- In dev, `--port` sets the Vite client port and the backend runs on `port + 1`, so `bun run dev --port 3333 --share` publishes `http://localhost:3333`.
355
- `--share` remains incompatible with `--host` and `--remote`.
356
- Use `bun run dev --port 4000` to run the Vite client on `4000` and the backend on `4001`.
357
-
358
- Or run client and server separately:
359
-
360
- ```bash
361
- bun run dev:client # http://localhost:5174
362
- bun run dev:server # http://localhost:5175
363
- ```
364
-
365
- ## Scripts
366
-
367
- | Command | Description |
368
- | -------------------- | ------------------------------------ |
369
- | `bun run build` | Build client + standalone export viewer |
370
- | `bun run check` | Typecheck, lint, and build |
371
- | `bun run lint` | ESLint over `src/` (zero-warning gate) |
372
- | `bun run dev` | Run client + server together |
373
- | `bun run dev:client` | Vite dev server only (`:5174`) |
374
- | `bun run dev:server` | Bun backend only (`:5175`) |
375
- | `bun run start` | Start production server |
376
- | `bun test` | Run the test suite |
377
- | `bun run setup:hooks` | Wire the gitleaks pre-commit hook (once per clone/worktree) |
378
- | `bun run scan:secrets` | Full working-tree secret scan on demand |
379
-
380
- ## Project Structure
381
-
382
- Abridged — the actual tree has more modules, each with co-located `*.test.ts`:
383
-
384
- ```
385
- src/
386
- ├── client/ React UI layer
387
- │ ├── app/ App router, pages, central state hook, socket client
388
- │ ├── components/ chat-ui, messages, settings, ui primitives, modals
389
- │ ├── hooks/ mobile/standalone detection, theme, mention/slash suggestions
390
- │ ├── stores/ Zustand stores (chat input, preferences, terminal, tasks…)
391
- │ └── lib/ formatters, path utils, transcript parsing, keybindings
392
- ├── server/ Bun backend
393
- │ ├── cli.ts · cli-runtime.ts CLI entry, flag parsing, supervisor
394
- │ ├── server.ts HTTP/WS server + static serving
395
- │ ├── auth.ts password gate for HTTP/WS/API
396
- │ ├── ws-router.ts WebSocket routing & subscriptions
397
- │ ├── agent.ts AgentCoordinator (multi-provider turns)
398
- │ ├── codex-app-server.ts Codex App Server JSON-RPC client
399
- │ ├── oauth-pool/ Claude OAuth token rotation
400
- │ ├── provider-catalog.ts provider/model/effort normalization
401
- │ ├── openrouter-models.ts live OpenRouter catalog (tool-capable models)
402
- │ ├── quick-response.ts structured queries w/ provider fallback
403
- │ ├── event-store.ts JSONL persistence, replay & compaction
404
- │ ├── read-models.ts derived view models
405
- │ ├── events.ts event type definitions
406
- │ ├── discovery.ts auto-discover Claude/Codex projects
407
- │ ├── local-catalog.ts local Claude skills/slash-command discovery
408
- │ ├── claude-session-importer.ts bulk import existing sessions
409
- │ ├── diff-store.ts per-chat diff hydration
410
- │ ├── terminal-manager.ts embedded-terminal PTY sessions
411
- │ ├── uploads.ts attachment intake
412
- │ ├── subagent-orchestrator.ts parallel + keep-alive + background subagent runs
413
- │ ├── workflow-registry.ts workflow status panel (disk-watch read-model)
414
- │ ├── background-tasks.ts out-of-band task tracking
415
- │ ├── worktree-store.ts git worktree isolation
416
- │ ├── push/ web-push notifications
417
- │ ├── share.ts · cloudflare-tunnel/ trycloudflare / expose_port tunnels
418
- │ ├── update-manager.ts · update-strategy.ts self-update
419
- │ ├── kanna-mcp.ts Kanna MCP tools (built-in shims)
420
- │ ├── mcp-validator.ts · mcp-oauth.adapter.ts custom MCP connect-test + OAuth
421
- │ └── keybindings.ts persisted keybindings
422
- └── shared/ Shared between client & server
423
- ├── types.ts core domain types, provider catalog, transcript entries
424
- ├── tools.ts tool-call normalization & hydration
425
- ├── protocol.ts WebSocket wire envelopes
426
- ├── ports.ts default ports & dev-mode offsets
427
- ├── share.ts share/tunnel shared types
428
- ├── token-pricing.ts per-turn token cost (USD)
429
- └── branding.ts app name & data-directory paths
430
- ```
431
-
432
- ## Data Storage
433
-
434
- All state is stored locally at `~/.kanna/data/`:
435
-
436
- | File | Purpose |
437
- | ---------------- | ----------------------------------------- |
438
- | `projects.jsonl` | Project open/remove events |
439
- | `chats.jsonl` | Chat create/rename/delete events |
440
- | `messages.jsonl` | Transcript message entries |
441
- | `turns.jsonl` | Agent turn start/finish/cancel events |
442
- | `snapshot.json` | Compacted state snapshot for fast startup |
443
-
444
- Event logs are append-only JSONL. On startup, Kanna replays the log tail after the last snapshot, then compacts if the logs exceed 2 MB.
445
-
446
- ## Self-hosting on macOS (pm2 + Cloudflare tunnel)
447
-
448
- Run Kanna as a background service on macOS under [pm2](https://pm2.keymetrics.io/), exposed through a named Cloudflare tunnel. The in-app **Update** button then pulls the latest commit, rebuilds, and hot-reloads the pm2 process — no terminal round-trip needed.
449
-
450
- ### 1. Link the repo as the global install
451
-
452
- `bun link` makes the global `kanna` binary resolve to your checkout:
453
-
454
- ```bash
455
- cd ~/path/to/kanna
456
- bun install
457
- bun run build
458
- bun link # registers @cuongtran001/kanna → repo
459
- ```
460
-
461
- After this, `~/.bun/install/global/node_modules/@cuongtran001/kanna` is a symlink to your repo.
462
-
463
- ### 2. Create a named Cloudflare tunnel
464
-
465
- In the [Cloudflare Zero Trust dashboard](https://one.dash.cloudflare.com/) → **Networks → Tunnels → Create tunnel** (type: **Cloudflared**):
466
-
467
- 1. Name the tunnel (e.g. `kanna`) and copy the **connector token** Cloudflare shows you. You will paste it as `KANNA_CLOUDFLARED_TOKEN` in the next step.
468
- 2. Add a **public hostname** route: pick your subdomain (e.g. `kanna.example.com`) and point service to `HTTP` → `localhost:5174` (or whatever `--port` you plan to run). Kanna binds `127.0.0.1` automatically when `--cloudflared` is set, so the tunnel is the only ingress.
469
- 3. Save. The hostname's TLS is terminated at Cloudflare's edge.
470
-
471
- ### 3. Write `scripts/pm2.env` (untracked secrets)
472
-
473
- `scripts/deploy.sh` reads this file and passes the values to kanna as `--cloudflared <TOKEN> --password <PW>`. Without it, deploy launches kanna with no token and no password — kanna will then run as plain HTTP on localhost, **`trustProxy` will not auto-enable**, and every `/auth/login` POST through the tunnel will return **403** because the CSRF origin check compares the browser's `https://` Origin against the server's `http://` `req.url`.
474
-
475
- Create `scripts/pm2.env` (gitignored) with at least:
476
-
477
- ```env
478
- KANNA_CLOUDFLARED_TOKEN=<paste the connector token from step 2>
479
- KANNA_PASSWORD=<a long random password>
480
- # Optional: pass through to spawned Claude Code agents
481
- # CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...
482
- ```
483
-
484
- Generate a strong password with `openssl rand -base64 24`.
485
-
486
- ### 4. (Migrating from launchd) Unload the old agent
487
-
488
- If you previously ran Kanna under launchd, unload it once so pm2 can take over:
489
-
490
- ```bash
491
- launchctl bootout gui/$(id -u)/io.silentium.kanna || true
492
- ```
493
-
494
- ### 5. First deploy
495
-
496
- `scripts/deploy.sh` installs pm2 if missing, renders `scripts/pm2.config.cjs` from the template (via `envsubst` from `brew install gettext`), and starts the pm2 process:
497
-
498
- ```bash
499
- ./scripts/deploy.sh
500
- pm2 list # kanna should be "online"
501
- pm2 logs kanna --lines 50
130
+ bun run setup:hooks # gitleaks + commit-message hooks, once per clone
131
+ bun run dev # Vite client on :5174, server on :5175
132
+ bun run check # typecheck, lint, build
133
+ bun run test # never bare `bun test`
502
134
  ```
503
135
 
504
- `pm2 save` persists the running process list. To resurrect after a reboot, run `pm2 startup` once (pm2 prints the exact command) and then `pm2 save` again.
505
-
506
- The pm2 config sets `KANNA_RELOADER=pm2` and `KANNA_REPO_DIR=<repo>` so the in-app Update button triggers the pm2 reload pipeline (see next section). Override the pm2 process name with `KANNA_PM2_PROCESS_NAME` before running `./scripts/deploy.sh` if you need to run multiple instances.
507
-
508
- ### 6. Redeploy / update
509
-
510
- Two ways to ship a new build:
511
-
512
- **a. From the UI (fastest).** Click **Update** in the running app. The server runs `git pull --ff-only` → conditional `bun install` → `bun run build` → `pm2.reload` internally, and the UI reconnects to the fresh build. If any step fails, the UI shows a red banner with the stderr tail and the old build keeps serving.
513
-
514
- **b. From the terminal.** Useful for non-Kanna deploys (e.g., pm2 config edits) or when the UI is unreachable:
515
-
516
- ```bash
517
- git pull
518
- ./scripts/deploy.sh
519
- ```
520
-
521
- ### 7. Troubleshooting: 403 on login
522
-
523
- If the login screen rejects the correct password with **403** behind a Cloudflare (or any HTTPS-terminating) tunnel, the server is running without `trustProxy` enabled. The CSRF origin check then compares the browser's `https://kanna.example.com` `Origin` against the local `http://127.0.0.1:<port>` `req.url` and rejects them as mismatched. Two ways to enable it:
524
-
525
- - **Recommended.** Pass `--cloudflared <TOKEN>` (or `--share`) on the kanna command line. Both flags auto-enable `trustProxy` and bind to `127.0.0.1`. With `scripts/pm2.env` populated, `scripts/deploy.sh` does this for you — verify with `pm2 logs kanna --lines 20` that the startup line includes `--cloudflared`.
526
- - **Running cloudflared separately?** Use `--cloudflared` on kanna anyway and let kanna spawn the tunnel; the standalone `cloudflared` daemon does not set `trustProxy` for you. (There is no standalone `--trust-proxy` CLI flag today.)
527
-
528
- Other things to check if the 403 persists:
529
-
530
- - Cloudflare tunnel **public hostname** points to `http://localhost:<KANNA_PORT>`, not `https://` — kanna terminates plain HTTP locally.
531
- - The public hostname's **TLS mode** is `Full` or `Flexible` (Cloudflare → Origin is HTTP), not `Full (strict)` against a self-signed origin.
532
- - No `Access` policy in front of the hostname is stripping or rewriting the `Origin` header.
533
-
534
- ### 8. Update strategies
535
-
536
- The update mechanism is abstracted behind `UpdateChecker` + `UpdateReloader` interfaces in `src/server/update-strategy.ts`, selected at startup by `KANNA_RELOADER`:
537
-
538
- | `KANNA_RELOADER` | Check | Reload | Notes |
539
- |---|---|---|---|
540
- | unset / `supervisor` | npm registry for `@cuongtran001/kanna` | `<pm> install -g @cuongtran001/kanna@latest`, exit 76, supervisor respawns | Default. End-user path. `<pm>` auto-detected: `bun`/`npm`/`pnpm`/`yarn`. Override via `KANNA_UPDATE_COMMAND`. |
541
- | `pm2` | `git fetch` + `HEAD` vs `origin/main` | `git pull --ff-only` → cond. `bun install` → `bun run build` → `pm2 reload` | Dev/self-host path. Requires `KANNA_REPO_DIR`. |
542
-
543
- **Host-agnostic supervisor mode.** When `KANNA_RELOADER` is unset (default), the in-app Update button works under any process host (pm2, systemd, docker, screen, plain shell) — the internal supervisor catches the child's exit-76 and respawns. The package manager used to install the new version is auto-detected from the running binary path:
544
-
545
- - `~/.bun/bin/kanna` → `bun install -g`
546
- - `~/.local/share/pnpm/kanna` (or any `pnpm/` path) → `pnpm add -g`
547
- - `~/.yarn/bin/kanna` (or any `.yarn/` path) → `yarn global add`
548
- - anything else (e.g. `/usr/local/bin/kanna`, `~/.npm-global/bin/kanna`) → `npm install -g`
549
-
550
- If the detected manager is not on `PATH`, kanna falls back through `bun → npm → pnpm → yarn`. To override the install command entirely — useful for custom installers, monorepo wrappers, docker pulls, ansible, etc. — set `KANNA_UPDATE_COMMAND`. Placeholders `{package}` and `{version}` are substituted; the result is executed via `sh -c`.
551
-
552
- ```bash
553
- # Force npm regardless of detection
554
- KANNA_UPDATE_COMMAND="npm install -g {package}@{version}" pm2 start kanna
555
- # Custom: chain pre-install hook
556
- KANNA_UPDATE_COMMAND="my-deploy-hook && npm install -g {package}@{version}" kanna
557
- ```
558
-
559
- To add another reload mechanism (e.g., docker, systemd) at the strategy layer, implement `UpdateChecker` + `UpdateReloader` and branch inside `createUpdateStrategy`; no changes to `UpdateManager`, `server.ts`, or any client code are needed.
136
+ Start with [`CLAUDE.md`](CLAUDE.md) and the [contributing guide](https://kanna-wiki.lowbit.link/guides/contributing/overview/). The server is event-sourced, IO is sealed behind `*.adapter.ts` files, and the lint gates enforce both; the [architecture page](https://kanna-wiki.lowbit.link/guides/contributing/architecture/) explains the shape. Releases are cut by release-please; see [Releasing](https://kanna-wiki.lowbit.link/guides/contributing/releasing/).
560
137
 
561
138
  ## Star History
562
139
 
@@ -568,43 +145,6 @@ To add another reload mechanism (e.g., docker, systemd) at the strategy layer, i
568
145
  </picture>
569
146
  </a>
570
147
 
571
- ## Releasing
572
-
573
- Releases are automated. Merging to `main` runs
574
- [release-please](.github/workflows/release-please.yml), which opens a release PR;
575
- merging that PR tags the version, creates the GitHub Release, and publishes
576
- `@cuongtran001/kanna` to npm with a provenance attestation.
577
-
578
- ### Recovering a failed publish
579
-
580
- If the `publish` job fails, the version is tagged on GitHub but missing from npm.
581
- **Re-running the workflow will not fix it** — release-please reports
582
- `release_created: false` once the release exists, so `publish` is skipped.
583
- Publish the existing tag instead:
584
-
585
- ```bash
586
- gh workflow run release-please.yml -f tag=v1.32.0
587
- ```
588
-
589
- This rebuilds and republishes from the tag through CI, so the package keeps its
590
- provenance signature. A local `npm publish` would not.
591
-
592
- ### npm token expiry
593
-
594
- `NPM_TOKEN` is an npm granular access token, and npm caps those at **90 days**.
595
- When it expires, `npm publish` fails with a misleading `E404 Not Found` rather
596
- than an auth error — npm masks a permission failure as a missing package. Rotate
597
- the token at [npmjs.com](https://www.npmjs.com/settings/cuongtran001/tokens)
598
- (scope: all packages, permission: read/write), then:
599
-
600
- ```bash
601
- gh secret set NPM_TOKEN
602
- ```
603
-
604
- ## Contributing
605
-
606
- Contributions are welcome! Feel free to open PRs
607
-
608
148
  ## License
609
149
 
610
150
  [MIT](LICENSE)