k0-board 0.5.0 → 0.7.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/README.md CHANGED
@@ -23,9 +23,9 @@ several over its life, one at a time, and it keeps all of them.
23
23
 
24
24
  [![Tests](https://github.com/alessio-ragni/k0-board/actions/workflows/ci.yml/badge.svg)](https://github.com/alessio-ragni/k0-board/actions/workflows/ci.yml)
25
25
  [![npm](https://img.shields.io/npm/v/k0-board)](https://www.npmjs.com/package/k0-board)
26
- [![License: MIT](https://img.shields.io/badge/License-MIT-informational)](LICENSE)
26
+ [![License: Sustainable Use](https://img.shields.io/badge/License-Sustainable%20Use-informational)](LICENSE)
27
27
 
28
- **Contents** · [Install](#install) · [How it is used](#how-it-is-used) ·
28
+ **Contents** · [Install](#install) · [k0 Cloud](#k0-cloud) · [How it is used](#how-it-is-used) ·
29
29
  [The backlog](#the-backlog) · [The ChangeLog](#the-changelog) · [What's New](#whats-new) ·
30
30
  [The files of a session](#the-files-of-a-session) · [What it costs](#what-it-costs) ·
31
31
  [Filling the board with what you have already done](#filling-the-board-with-what-you-have-already-done) ·
@@ -33,7 +33,7 @@ several over its life, one at a time, and it keeps all of them.
33
33
  [Shift+Enter starts a new line](#shiftenter-starts-a-new-line) ·
34
34
  [Pasting into the terminal](#pasting-into-the-terminal) ·
35
35
  [How it is put together](#how-it-is-put-together) · [Security](#security) ·
36
- [What is not here yet](#what-is-not-here-yet) · [Contributing](#contributing) · [Licence](#licence)
36
+ [What is not here yet](#what-is-not-here-yet) · [Contributing](#contributing) · [License](#license)
37
37
 
38
38
  ---
39
39
 
@@ -71,7 +71,9 @@ that and went out from a laptop.)
71
71
  | `k0-board start` | run the server in the foreground, installing nothing |
72
72
  | `k0-board restart` | restart the service |
73
73
  | `k0-board status` | is it running? exits 0 if it is, 1 if it is not |
74
- | `k0-board doctor` | what this machine can and cannot do, and why |
74
+ | `k0-board doctor` | what this machine can and cannot do, and why — and where k0 Cloud stands |
75
+ | `k0-board login` | sign in to [k0 Cloud](#k0-cloud) with GitHub or Google: opens the board, where the buttons are |
76
+ | `k0-board logout` | sign out; the board stays on this machine and stops syncing |
75
77
 
76
78
  `--yes` does not ask. `--from-source` points the service at the directory you are in instead of
77
79
  copying it, which is what you want while working on k0 itself. `--no-open` finishes without
@@ -149,13 +151,140 @@ That last one deserves a sentence of its own: a process with the Accessibility p
149
151
  send keystrokes to any application, and here it is granted to your `node`, not to a signed k0
150
152
  app. Skipping it breaks nothing — the prompt gets typed and the session starts by itself.
151
153
 
152
- k0 makes **exactly one network request**, and you can switch it off. Once a day it asks the public
153
- npm registry what the newest version of `k0-board` is, so the board can mention that there is one.
154
- The package name is in the address and nothing else is in the request: not the version you are
155
- running, not your repositories, not your commits, not your board, not an identifier of any kind.
156
- Nothing is downloaded and nothing is run — k0 cannot update itself. Set `"updateCheck": false` in `~/.k0/config.json` and and no
157
- socket is opened, ever. Everything else stays as it was: no fonts fetched, no telemetry, no
158
- `git fetch`.
154
+ k0 needs **an account**, and it talks to **exactly two places**. You sign in with GitHub or Google,
155
+ and your board is kept in sync with **k0 Cloud** — k0's own database, on Supabase, in the EU — so
156
+ it survives a lost laptop and can be read from a browser. The board you work on is still the copy
157
+ on this machine: without a connection k0 keeps working, and catches up when the connection is back.
158
+
159
+ The second place is the public npm registry. Once a day k0 asks it what the newest version of
160
+ `k0-board` is, so the board can mention that there is one. The package name is in the address and
161
+ nothing else is in the request: not the version you are running, not your repositories, not your
162
+ commits, not your board, not an identifier of any kind. Nothing is downloaded and nothing is run —
163
+ k0 cannot update itself. Set `"updateCheck": false` in `~/.k0/config.json` and that socket is never
164
+ opened. Everything else stays as it was: no fonts fetched, no telemetry, no analytics, no
165
+ `git fetch`, and the models still run here, through your own Claude Code.
166
+
167
+ ---
168
+
169
+ ## k0 Cloud
170
+
171
+ Your board lives on your machine and keeps working with no network at all. k0 Cloud holds a copy
172
+ of it, so it survives a lost laptop, reaches your other machines, and — coming with the site — can
173
+ be read from a browser at `k0-board.com/app/`.
174
+
175
+ Who owns which piece of it, where the keys live and how somebody is let in: [`docs/cloud.md`](docs/cloud.md).
176
+
177
+ ### Signing in
178
+
179
+ The first time you open the board it shows **Connect your account**, with a button for GitHub and
180
+ one for Google. Those are the only two ways in; there is no password of k0's own. The browser goes
181
+ to the provider, you say yes there, and it comes back to the board on this machine —
182
+ `http://k0.localhost:4319/auth/callback` — which finishes the sign-in by itself. k0 never sees a
183
+ password, and the one-time code that comes back is worth nothing on its own: it only works
184
+ together with a secret this machine made before sending the browser off (PKCE).
185
+
186
+ What keeps you signed in is written to `~/.k0/cloud.json`, readable by you and nobody else.
187
+
188
+ `k0-board login` from a terminal does the same thing: it opens the board, which is where the
189
+ buttons are, or says who is already signed in.
190
+
191
+ The screen only stands in front of the board when there is a way past it. If the cloud has no
192
+ sign-in switched on yet, the board simply opens: blocking it for something you cannot do anything
193
+ about would be k0's failure passed off as yours. Offline on the very first start, the screen says
194
+ the cloud cannot be reached instead of offering buttons that would not work.
195
+
196
+ ### Early access
197
+
198
+ Signing in is not the same as being let in. k0 is in early access: an account opens once it has
199
+ been approved, by hand. Until then the board says **You are on the list**, nothing is sent
200
+ anywhere, and the board goes on working exactly as before. It notices the approval by itself — no
201
+ restart, no second sign-in. *Use another account* on that screen signs out and starts again.
202
+
203
+ (Approving accounts is done from an admin page that is coming with the site.)
204
+
205
+ ### What goes up, and what never leaves the machine
206
+
207
+ Every few seconds, while you are signed in and let in, k0 sends what changed and fetches what your
208
+ other machines changed: epics, stories, tasks, decisions, rounds, checklists, dependencies, the
209
+ Log, and the sessions a story has had. A repository is named in the cloud by its `origin` remote,
210
+ cleaned of any password and of the difference between `git@` and `https://`, so the same
211
+ repository checked out on two machines is one column; a repository with no remote stays a name
212
+ known only to this machine.
213
+
214
+ What never leaves: which terminal window a session is in, the folder it is working in, and the
215
+ commit it started at. Nothing from your repositories either — not files, not diffs, not commits.
216
+
217
+ Sessions go up but do not come down. A session is a terminal on the machine that opened it; the
218
+ other machine has nothing to attach it to, so it would only be a ghost there. The cloud keeps
219
+ their status so the web board can show it.
220
+
221
+ When the same thing was changed in two places, a change made here and not yet sent wins over one
222
+ arriving from the cloud — it is what the person at this machine just did — and in the cloud a later
223
+ change wins over an older one that arrives late, so a laptop coming back after a day away does not
224
+ put yesterday's version over this morning's.
225
+
226
+ ### Offline, and keys with a `~`
227
+
228
+ A key is one number for your whole account: `K150` is the same story whichever repository it is in
229
+ and whichever machine is looking at it. So the number comes from the cloud, which is the only place
230
+ two machines can agree on what comes next. k0 asks for it the moment you create a story or an
231
+ epic, and waits at most two and a half seconds.
232
+
233
+ When the cloud does not answer, the story is made anyway, with a **provisional key**: `K~3`. It
234
+ works everywhere a key does. As soon as the cloud is back the sync gives it its real number, oldest
235
+ first, and renames its file in `.k0/`. Nothing is reserved in advance, so the numbers still run in
236
+ the order the stories were made.
237
+
238
+ ### Moving a board you already had
239
+
240
+ A board that existed before you signed in was numbered on this machine — per repository, if it is
241
+ older than k0 Cloud — and the cloud's counter starts somewhere else. Using the cloud's numbers
242
+ straight away would give your next story a `K1` the board already has. So the first time an
243
+ account is linked on a machine whose board has stories, **nothing is sent and no number is taken
244
+ from the cloud** until the board has been moved.
245
+
246
+ The board offers it: **Move your board to k0 Cloud**. *Show me the new numbers* lists every story
247
+ and epic, repository by repository, with its old key beside its new one, and says how many pieces
248
+ of text — a plan, a prompt, a decision — mention a key and will be updated with it. Nothing has
249
+ changed yet at that point. *Move it* then:
250
+
251
+ 1. copies the whole database to `~/.k0/backups/k0-before-cloud-<time>.db`, in one consistent file;
252
+ 2. renumbers every story and epic once, in the order they were made, starting from the cloud's
253
+ next number;
254
+ 3. rewrites the keys written inside the text — plans, prompts, bodies, decisions, rounds, the Log,
255
+ the checklist — within the same repository, since that is the only place a bare `K12` ever
256
+ meant anything;
257
+ 4. renames the files in `.k0/`;
258
+ 5. writes `~/.k0/backups/k0-before-cloud-<time>.keys.json`, every old key beside its new one;
259
+ 6. moves the cloud's counter past the board, and sends the whole board up.
260
+
261
+ When it is done the board says how many it moved, from which number, and where the two files are,
262
+ and stays on that page until you close it.
263
+
264
+ *Not now* means not now: the board keeps its old numbers and keeps working, and the offer comes
265
+ back the next time the board is opened.
266
+
267
+ ### The account chip
268
+
269
+ At the end of the top bar, once you are signed in: your picture, and a dot. **Green**, everything
270
+ is in the cloud. **Amber**, changes are waiting to go up — or a story still has a provisional key.
271
+ **Grey**, the cloud cannot be reached and the board is carrying on by itself. Click it for who is
272
+ signed in, **Sync now**, and **Sign out**.
273
+
274
+ ### Signing out
275
+
276
+ **Sign out** in that menu, or `k0-board logout`. The board stays on this machine exactly as it is;
277
+ it stops syncing, keys are counted on this machine again, and nothing is queued for later.
278
+
279
+ `k0-board doctor` has a *k0 Cloud* section: who is signed in, whether they have been let in,
280
+ whether the board still has to be moved, whether the cloud answers, and how many changes are
281
+ waiting.
282
+
283
+ ### Switching it off
284
+
285
+ `K0_CLOUD=off` in the server's environment switches the cloud off entirely: no sign-in screen, no
286
+ chip, no socket. It is meant for working on k0 itself and for the tests. Without an account, keys
287
+ are still one counter for the whole board rather than one per repository, kept on this machine.
159
288
 
160
289
  ---
161
290
 
@@ -217,8 +346,8 @@ socket is opened, ever. Everything else stays as it was: no fonts fetched, no te
217
346
  work and check keep a task per step — so without it they would be asking for a tool that is not
218
347
  there.
219
348
 
220
- The window is born **centred on the screen you are using**, at 86% of the free space: the same
221
- margin on all four sides, never under a menu bar or a dock. Open more than one and they stack
349
+ The window is born **centred on the screen you are using**, at 90% of the free space, in every mode:
350
+ the same margin on all four sides, never under a menu bar or a dock. Open more than one and they stack
222
351
  in the middle — double click brings the one underneath back.
223
352
 
224
353
  **If the screen changes** — you plug a monitor in or out, or change resolution — the windows put
@@ -357,8 +486,8 @@ that asked for it, close every session, restart k0 itself — the server stays u
357
486
  stays green. This is the whole reason it exists: a dev server started inside a session belongs to
358
487
  that session, and dies with it.
359
488
 
360
- **k0 never asks the server whether it is up.** Nothing here opens a socket — the one request k0
361
- makes goes to npm, and it is about versions, not about you. "Up" means the process is alive *and*
489
+ **k0 never asks the server whether it is up.** Nothing here opens a socket to it — k0 talks to its
490
+ own cloud and to npm, and to nothing it finds running on your machine. "Up" means the process is alive *and*
362
491
  the operating system says it is holding a TCP port
363
492
  open. That is stronger evidence than a reply would be, and it is where the port in the tooltip
364
493
  comes from — k0 never guesses a port, it reads the one your server actually chose.
@@ -599,12 +728,14 @@ the whole thing.
599
728
 
600
729
  ### The key, and where it sits
601
730
 
602
- Every epic, story and task gets a **key**: `K` plus a number, `K42`. It is counted per repository,
603
- shared between epics and stories, and it is **never reused and never reassigned** — not when the
604
- story moves under another epic, not when you delete it, not when the repository is renamed. Two
605
- repositories can both have a `K42`, and that is intended: a key means something inside its
606
- repository and nowhere else. Move a story to another repository and it is given a key from *that*
607
- repository, rather than carrying a number into a place that already has one.
731
+ Every epic, story and task gets a **key**: `K` plus a number, `K42`. It is **one number for your
732
+ whole account** — every repository, every machine — shared between epics and stories, and it is
733
+ **never reused and never reassigned**: not when the story moves under another epic, not when you
734
+ delete it, not when the repository is renamed, and not when the story moves to another repository,
735
+ since there is no other `K42` there for it to collide with. The number comes from k0 Cloud's
736
+ counter; a story made while the cloud cannot be reached gets a provisional key, `K~3`, until the
737
+ sync gives it its real one — see [Offline, and keys with a `~`](#offline-and-keys-with-a-). Without
738
+ an account the same single counter is kept on this machine.
608
739
 
609
740
  Beside it there is a **position**: `1.12.1`. The first epic, its twelfth story, that story's first
610
741
  task. The position is worked out fresh every time it is shown and never stored, so it cannot
@@ -874,10 +1005,10 @@ not believe the paragraph above it.
874
1005
 
875
1006
  ### Who writes it
876
1007
 
877
- k0 does not. **Nothing about your work leaves this machine**, and that is worth more than a better
878
- paragraph: a model of its own would mean a key to keep, an account to configure, and your commit
879
- messages going out over a wire. The one request k0 makes is to the npm registry, asking after its
880
- own version; it carries no commits, no repository names and no identifier.
1008
+ k0 does not. **No model of k0's own reads your work**, and that is worth more than a better
1009
+ paragraph: a model of its own would mean a key to keep, a second account to configure, and your
1010
+ commit messages going out over a wire to somebody's model. The facts are gathered on this machine
1011
+ and written up here; what k0 Cloud keeps is your board, not your commits or your diffs.
881
1012
 
882
1013
  It does not need one. If you are running k0 you already have Claude Code installed, signed in and
883
1014
  paid for — that is the whole point of the board — so the model is already here. k0 works out the
@@ -920,7 +1051,7 @@ they were written. While the prose is being written they are what there is to re
920
1051
  are what you check it against.
921
1052
 
922
1053
  It is the same machinery as the ChangeLog, and it has the same shape: nothing is stored, no model
923
- of k0's own, no key and no account — the Claude Code you already pay for does the writing, on this
1054
+ of k0's own and no key of its own — the Claude Code you already pay for does the writing, on this
924
1055
  machine. Nothing is downloaded either. k0 cannot update itself; the most it can do is say a newer
925
1056
  version exists, and leave `npx k0-board@latest` to you.
926
1057
 
@@ -1390,7 +1521,7 @@ one again does nothing — you leave a mode, you do not switch it off.
1390
1521
  | two **z** | **Sleep** | sleeps as it normally would | goes off | normal |
1391
1522
  | a **palm tree** | **Away** | does not sleep, not even with the lid closed | may sleep and lock | normal |
1392
1523
  | a **nerd face** with glasses | **Nerd** | does not sleep | **stays on** | normal |
1393
- | a **car** | **Driving** | does not sleep | **stays on** | **large**: the text here and in the terminals, and the terminals fill the screen |
1524
+ | a **car** | **Driving** | does not sleep | **stays on** | **large**: the text here and in the terminals |
1394
1525
 
1395
1526
  The four icons do not describe the machine: they describe **where you are**. Asleep, away, at the
1396
1527
  desk, watching from across the room. That is where the order comes from, and it is why the second
@@ -1438,17 +1569,22 @@ they reorder by urgency, reddest to the left. On a screen you glance at, two or
1438
1569
  visible at a time and the most urgent one has to come to you. Full screen you have them all in
1439
1570
  front of you already, and a board that reshuffles itself while you work only loses your place.
1440
1571
 
1441
- **The windows grow with the text.** In Driving a terminal takes the whole free screen; leave
1442
- Driving and it goes back to the usual 86% centred, with the text back at the profile's size. It is
1443
- one gesture and not two, and it lands on the terminals **already open**, not only on the ones you
1444
- open next.
1572
+ **The windows keep their margin.** In every mode, Driving included, a terminal takes 90% of the
1573
+ free screen, centred, with the same strip left free on all four sides — that strip is where you
1574
+ click to reach whatever is behind it. Driving changes the text, not the share: switch it on and the
1575
+ terminals get 22 point text and are put back at 90% centred; switch it off and the text goes back
1576
+ to the profile's size. It is one gesture and not two, and it lands on the terminals **already
1577
+ open**, not only on the ones you open next.
1578
+
1579
+ For a while Driving took the whole free screen instead, which left nothing at the edge to click on;
1580
+ the margin is worth more than the last tenth of the screen.
1445
1581
 
1446
1582
  It used to be the opposite, and the reason is worth keeping in mind: Terminal.app holds rows and
1447
1583
  columns when the font changes and resizes the window to match — going from 12 to 22 turns a 700×500
1448
1584
  window into 1378×856, measured — so k0 read each window's own bounds first and put them back, and
1449
1585
  nothing moved. What that left behind was a window holding 22 point text in a box measured for 12.
1450
- Half a gesture is worse than none, so the size of the text and the size of the window now travel
1451
- together, and the price is said plainly: **a window you had dragged onto another screen comes back
1586
+ Half a gesture is worse than none, so the text is set first and the window put back at its share
1587
+ right after, and the price is said plainly: **a window you had dragged onto another screen comes back
1452
1588
  to the middle of the main one.**
1453
1589
 
1454
1590
  **All of them, every time.** k0 asks Terminal once for the windows it has open and changes those
@@ -1618,8 +1754,13 @@ server/
1618
1754
  worktree.js a session working copy of its own: open it from the branch you are on, merge
1619
1755
  it back, destroy it. Never a push, never a pull request, never a conflict
1620
1756
  resolved for you, and it never runs tests or a build in there
1621
- update.js the one request k0 makes — what npm says the newest version is — and the
1622
- version jump that puts the mark next to k0 in the top bar
1757
+ update.js one of the two places k0 reaches outside — what npm says the newest version
1758
+ is — and the version jump that puts the mark next to k0 in the top bar
1759
+ cloud.js the other place k0 reaches outside, and the only door to k0 Cloud: signing
1760
+ in, the next K number, rows up and rows down. Plain fetch, no client library
1761
+ sync.js moves rows between the board and the cloud every five seconds, and the
1762
+ one-time move of a board that predates the account. Carries rows; decides
1763
+ nothing about what they mean
1623
1764
  paths.js where the board, the logs, the settings and the cache live
1624
1765
  settings.js the few things you can change, and the file that is the list of them —
1625
1766
  ~/.k0/config.json, written out with everything in it and re-read when it changes
@@ -1667,6 +1808,8 @@ web/ the four pages (html, css, js served exactly as they are)
1667
1808
  whatsnew.html what changed in k0 between the version you had and this one — whatsnew.js,
1668
1809
  whatsnew.css. Reached from the mark next to k0, and never opened by itself
1669
1810
  base.css colours, fonts and scale: the house variables, shared by all of them
1811
+ account.js the account on the board: the chip at the end of the top bar, and the screen
1812
+ in front of the board until you are signed in, let in and moved
1670
1813
  awake.js the one place that repeats anything: a job that runs only while its tab is in
1671
1814
  front of you, and takes a fresh reading the moment you come back to it. A page
1672
1815
  nobody can see must not be asking the server anything — asking is also how the
@@ -1708,6 +1851,13 @@ web/ the four pages (html, css, js served exactly as they are)
1708
1851
  commit-push-deploy/
1709
1852
  the road out: the documents, the tests, the commit, the push, and the tag that
1710
1853
  publishes. Also for whoever works on k0, and also not in the package
1854
+ supabase/ k0 Cloud's own side: migrations/ is its database (accounts, workspaces, the
1855
+ rows of every board, the counter), config.toml its sign-in settings. The secrets
1856
+ are in ~/.config/k0-cloud/.env, never in the repository
1857
+ site/ k0-board.com: static pages, no build, deployed as they are. index.html is the
1858
+ home page with the early-access form (waitlist.js), privacy, terms and 404
1859
+ beside it, one site.css; app/ is the read-only web board and the page where
1860
+ accounts are approved
1711
1861
  docs/
1712
1862
  database.md the shape of the database as it is now, table by table, column by column
1713
1863
  method.md why a decision has to be an object, what a round of questions is for, and
@@ -1717,6 +1867,40 @@ test/ npm test — Node's own runner, no framework. See docs/testing.
1717
1867
  harness.mjs check(label, got, want), and the sections the labels are grouped under
1718
1868
  ```
1719
1869
 
1870
+
1871
+ Two small pieces live outside the app because they run in the cloud, not on your machine:
1872
+ `supabase/functions/notify` sends the emails k0 Cloud sends (an early-access request, an account
1873
+ waiting to be let in), and `infra/mail-forward` forwards mail sent to the site's contact address.
1874
+ Neither ships in the npm package. Where each one runs and how it is put online: `docs/cloud.md`.
1875
+ ### How the board reaches the cloud
1876
+
1877
+ The board's rules live on the machine, in SQLite, and they stay there: states, decisions, rounds,
1878
+ dependencies. The cloud does not model the board table by table. It keeps **one document per
1879
+ row**, keyed by a `uid` the machine gave that row, so a change to k0's own tables needs no change
1880
+ in the cloud. What the cloud does own is what a single machine cannot: who you are, whether you
1881
+ have been let in, which workspace your rows belong to — a personal one today, built so a team is a
1882
+ second kind of workspace and nothing else moves — and the one counter that numbers your stories.
1883
+
1884
+ **Changes are noticed by triggers, not by the code that makes them.** Every synced table has
1885
+ triggers that write a line into an `outbox` table on insert, update and delete. About forty places
1886
+ in `db.js` write to those tables, and a list of "remember to tell the cloud" calls would be forty
1887
+ chances to forget one; a trigger cannot be forgotten. Two switches decide whether the triggers
1888
+ write anything: one that is off until an account is linked, so a board with no cloud does not grow
1889
+ a queue for ever, and one that is on while rows that came *from* the cloud are being written, so
1890
+ they are not sent straight back.
1891
+
1892
+ A round, every five seconds, does three things in order: gives provisional keys their real
1893
+ numbers, oldest first; sends the outbox up as documents, with references to other rows written as
1894
+ `<column>_uid` because an integer id means nothing in another database; and fetches what other
1895
+ machines wrote since the last round, in the order that puts an epic before its stories and a story
1896
+ before its decisions. A row whose owner has not arrived yet is left for a later round rather than
1897
+ written pointing at nothing. A failed round never throws at the board: it says so in the chip and
1898
+ tries again.
1899
+
1900
+ In the cloud every row is fenced off by the database's own row-level rules: only a member of the
1901
+ workspace, with an approved account, can read or write it. The address and the key in `cloud.js`
1902
+ are public on purpose — the key identifies the project and grants nothing by itself.
1903
+
1720
1904
  ### What it reads, and what it never does
1721
1905
 
1722
1906
  k0 reads your repositories. It writes into one in three places, and all three are named here
@@ -1762,8 +1946,8 @@ Windows.
1762
1946
 
1763
1947
  The ChangeLog and the What's New page run Claude Code a second way: `claude -p` on the skill that
1764
1948
  writes them, with the facts on standard input and no terminal at all. Those are the only times k0
1765
- starts a model, and they still open no socket of k0's own — the request is Claude Code's, made
1766
- with your own account, from your own machine. Both pages can be writing at once without either
1949
+ starts a model, and the model runs on Claude Code's own connection — the request is Claude Code's,
1950
+ made with your own Claude account, from your own machine, and it goes nowhere near k0 Cloud. Both pages can be writing at once without either
1767
1951
  cancelling the other. Those runs cannot come back as stories: `sessions.js` drops `claude -p` when it looks for
1768
1952
  sessions to import, which is also how it tells a hundred and forty real sessions from eight
1769
1953
  hundred automated ones.
@@ -1840,6 +2024,25 @@ Field notes the code takes for granted.
1840
2024
  XML, and with a `1b` byte inside, that XML is not even valid. And `killall cfprefsd` twice, not
1841
2025
  once: **before** writing, so the preferences daemon flushes what it still holds from the Terminal
1842
2026
  that just quit, and **after**, or nobody sees the change.
2027
+ - **The cloud's numbers cannot be used before the board has moved.** An old board was numbered on
2028
+ the machine, and the cloud's counter starts at 1: the first story numbered by the cloud would
2029
+ collide with a `K1` already on the board. So a linked machine with stories records nothing and
2030
+ asks for no number until the move has run — and the move pushes the cloud's counter past the
2031
+ whole board before the first sync.
2032
+ - **There is no way to look at the cloud's next number without using it.** A number asked for is a
2033
+ number handed out. So the move's preview takes one, and the move then starts from exactly that
2034
+ number; the gap it would otherwise leave never appears.
2035
+ - **`VACUUM INTO`, not a copy of `k0.db`.** With the write-ahead log on, the file alone is not the
2036
+ board — the last changes may still be in `-wal`. The copy taken before the move is one consistent
2037
+ file with all of it folded in.
2038
+ - **Keys inside text are rewritten in one pass, with the whole map.** One key after another would
2039
+ turn a `K3` that became `K12` into whatever `K12` became.
2040
+ - **Supabase's new `sb_secret_…` keys cannot be sent as a Bearer token.** The admin API — creating
2041
+ a throwaway account, approving one from a script — needs the `service_role` JWT instead. Both
2042
+ live in `~/.config/k0-cloud/.env`.
2043
+ - **`supabase config push` fails on the storage part with CLI 2.106**, but the auth part — the
2044
+ redirect addresses, the providers — has already been applied by then. Check the dashboard rather
2045
+ than trusting the exit code.
1843
2046
 
1844
2047
  ### Where the repository list comes from
1845
2048
 
@@ -1868,14 +2071,17 @@ deleting**: the stories stay in the database, and if the directory comes back so
1868
2071
 
1869
2072
  The server listens on loopback only, and refuses requests that arrive with a `Host` or `Origin`
1870
2073
  it does not recognise — which is what stops a web page you happen to have open from talking to it.
1871
- HTML files from your repositories are shown in a sandbox with no permissions. There is no account
1872
- and no token: anything already running as you on this machine can reach the API, and that is worth
2074
+ HTML files from your repositories are shown in a sandbox with no permissions. The local API has no
2075
+ login of its own: anything already running as you on this machine can reach it, and that is worth
1873
2076
  knowing rather than glossing over.
1874
2077
 
1875
- **One request leaves this machine, and it is about k0 rather than about you.** Once a day k0 asks
1876
- the public npm registry what the newest version of `k0-board` is, so the board can say there is
1877
- one. The package name is in the address and nothing else — nothing about you, your repositories,
1878
- your commits or your board — nothing is downloaded, and `"updateCheck": false` in `~/.k0/config.json` switches it off entirely.
2078
+ **k0 talks to two places, and to nothing else.** The first is k0 Cloud, where your board is synced
2079
+ once you have signed in with GitHub or Google. The token that keeps you signed in sits in `~/.k0/`,
2080
+ readable by you alone, and in the cloud every workspace's rows are fenced off from every other's by
2081
+ the database itself. The second is the public npm registry, once a day, asking what the newest
2082
+ version of `k0-board` is. The package name is in the address and nothing else — nothing about you,
2083
+ your repositories, your commits or your board — nothing is downloaded, and `"updateCheck": false`
2084
+ in `~/.k0/config.json` switches it off entirely. No telemetry, no analytics, no fonts from anywhere.
1879
2085
 
1880
2086
  The full picture, and how to report a problem privately, is in [SECURITY.md](SECURITY.md).
1881
2087
 
@@ -1898,6 +2104,14 @@ Yes please — especially if you are on Linux or Windows, where nobody has tried
1898
2104
  [CONTRIBUTING.md](CONTRIBUTING.md); the fastest useful thing you can send is the output of
1899
2105
  `k0-board doctor`.
1900
2106
 
1901
- ## Licence
2107
+ ## License
2108
+
2109
+ k0 is source-available under the [Sustainable Use License](LICENSE) © Alessio Ragni ·
2110
+ [alessioragni.com](https://alessioragni.com). Every line of it is public, and you may use it, change
2111
+ it, and run it for yourself or inside your company, for your own work; you may also pass it on,
2112
+ free of charge, for non-commercial purposes. What it does not allow is offering k0 to other people
2113
+ as a paid or hosted service of your own. It is not an open source license in the OSI sense, and k0
2114
+ does not call itself one.
1902
2115
 
1903
- [MIT](LICENSE) © Alessio Ragni · [alessioragni.com](https://alessioragni.com)
2116
+ Versions up to and including 0.5.0 were released under the MIT License, and they stay that way: a
2117
+ copy of one of those keeps every right MIT gave it.
package/bin/k0-board.js CHANGED
@@ -471,6 +471,8 @@ async function doctor() {
471
471
  else if (state.running) warn('the service is running but the server is not answering')
472
472
  else warn('not running — `k0-board install` or `k0-board start`')
473
473
 
474
+ await cloudReport()
475
+
474
476
  step('What this machine can do')
475
477
  const rows = [
476
478
  ['open terminals', capabilities.terminal.pasteWithoutSending || capabilities.terminal.readScreen],
@@ -514,6 +516,49 @@ async function doctor() {
514
516
  }
515
517
  }
516
518
 
519
+ // ── k0 Cloud ─────────────────────────────────────────────────────────────────
520
+ /**
521
+ * Signing in happens in the browser, on the board: that is where GitHub or Google send you back
522
+ * to. So this opens the board, which shows the way in when nobody is signed in yet, and says
523
+ * where things stand when somebody is.
524
+ */
525
+ async function login() {
526
+ if (!(await alive())) {
527
+ fail('k0 is not running — `k0-board install` or `k0-board start` first')
528
+ process.exit(1)
529
+ }
530
+ const s = await api('/api/cloud')
531
+ if (!s.enabled) return warn('k0 Cloud is switched off on this machine (K0_CLOUD=off)')
532
+ if (s.linked && s.approved) return ok(`signed in as ${s.email || s.name}`)
533
+ if (s.linked) return note(`signed in as ${s.email || s.name}, waiting to be let in — the board notices by itself`)
534
+ const url = await openBoard()
535
+ say(`Sign in on the board: ${url}`)
536
+ }
537
+
538
+ async function logout() {
539
+ if (!(await alive())) {
540
+ fail('k0 is not running')
541
+ process.exit(1)
542
+ }
543
+ await api('/api/cloud/logout', {})
544
+ ok('signed out — the board stays on this machine, and stops syncing')
545
+ }
546
+
547
+ async function cloudReport() {
548
+ step('k0 Cloud')
549
+ if (!(await alive())) return warn('the server is not running, so there is nothing to ask')
550
+ const s = await api('/api/cloud').catch(() => null)
551
+ if (!s) return warn('the server did not say')
552
+ if (!s.enabled) return warn('switched off on this machine (K0_CLOUD=off)')
553
+ if (!s.linked) return warn('nobody is signed in — `k0-board login`')
554
+ ok(`signed in as ${s.email || s.name}`)
555
+ ;(s.approved ? ok : warn)(s.approved ? 'let in' : 'waiting to be let in')
556
+ if (s.needs_move) warn('the board on this machine has not been moved to the account yet — the board offers it')
557
+ ;(s.online === false ? warn : ok)(s.online === false ? 'k0 Cloud cannot be reached right now' : 'k0 Cloud answers')
558
+ if (s.pending) note(`${s.pending} change${s.pending === 1 ? '' : 's'} waiting to go up`)
559
+ if (s.sync_error) warn(s.sync_error)
560
+ }
561
+
517
562
  // ── the rest ─────────────────────────────────────────────────────────────────
518
563
  async function restart() {
519
564
  await service.restart()
@@ -547,6 +592,8 @@ ${bold('k0-board')} — a board of sticky notes that drives your Claude Code ses
547
592
  ${bold('k0-board restart')} restart the service
548
593
  ${bold('k0-board status')} is it running?
549
594
  ${bold('k0-board doctor')} what this machine can and cannot do, and why
595
+ ${bold('k0-board login')} sign in to k0 Cloud with GitHub or Google (on the board)
596
+ ${bold('k0-board logout')} sign out; the board stays on this machine
550
597
 
551
598
  Options
552
599
  ${bold('--yes')} do not ask, assume yes
@@ -556,7 +603,7 @@ Options
556
603
  `)
557
604
  }
558
605
 
559
- const commands = { install, uninstall, start, restart, status, doctor, 'shift-enter': shiftEnter }
606
+ const commands = { install, uninstall, start, restart, status, doctor, login, logout, 'shift-enter': shiftEnter }
560
607
 
561
608
  if (flag('help') || flag('h') || command === 'help') {
562
609
  usage()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "k0-board",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "A board of sticky notes that drives your Claude Code sessions",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -16,7 +16,7 @@
16
16
  "type": "git",
17
17
  "url": "git+https://github.com/alessio-ragni/k0-board.git"
18
18
  },
19
- "license": "MIT",
19
+ "license": "SEE LICENSE IN LICENSE",
20
20
  "author": "Alessio Ragni (https://alessioragni.com)",
21
21
  "type": "module",
22
22
  "bin": {
@@ -17,8 +17,11 @@ const PBPASTE = () => which('pbpaste', ['/usr/bin/pbpaste'])
17
17
  /** Escape for an AppleScript string literal. */
18
18
  const asq = (s) => String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"')
19
19
 
20
- /** How much of the free screen area a window takes unless somebody asks for more. */
21
- const COVERAGE = 0.86
20
+ /**
21
+ * How much of the free screen area a window takes, in every mode: the rest is margin, and the
22
+ * margin is where you click to reach whatever is behind it.
23
+ */
24
+ const COVERAGE = 0.9
22
25
 
23
26
  /**
24
27
  * How long a pass over the open windows may take. The five seconds `run` gives every other
@@ -28,9 +31,6 @@ const COVERAGE = 0.86
28
31
  */
29
32
  const PASS_TIMEOUT = 20000
30
33
 
31
- /** In driving mode the terminal has to be readable from across the room. */
32
- export const DRIVING_FONT_SIZE = 22
33
-
34
34
  /**
35
35
  * The profile's font size, asked once and then remembered. It is what we go back to when
36
36
  * driving mode is switched off; hard-coding a 12 here would be right today and wrong the
@@ -51,8 +51,7 @@ export async function defaultFontSize() {
51
51
  * same for all of them: otherwise the first time a monitor is unplugged every window would
52
52
  * change size for no reason.
53
53
  *
54
- * `coverage` is how much of the free area the window takes: the usual share, or all of it when
55
- * the mode wants windows readable from across the room.
54
+ * `coverage` is how much of the free area the window takes; left out, it is the usual share.
56
55
  *
57
56
  * Leaves `x1`, `y1`, `w` and `h` behind, which is what `bounds` needs.
58
57
  */
@@ -36,8 +36,8 @@ const EMULATORS = [
36
36
  { bin: 'xterm', args: (cmd) => ['-e', ...cmd] },
37
37
  ]
38
38
 
39
- /** How much of the work area a window takes unless the mode asks for more. */
40
- const COVERAGE = 0.86
39
+ /** How much of the work area a window takes, in every mode: the rest is margin. */
40
+ const COVERAGE = 0.9
41
41
 
42
42
  /**
43
43
  * Which emulator to open. `K0_TERMINAL` wins, because no list of ours will ever cover every
@@ -26,8 +26,8 @@ import { ps, psJson, psq } from './powershell.js'
26
26
  const WT = () => which('wt')
27
27
  const POWERSHELL_BIN = () => which('pwsh') || which('powershell')
28
28
 
29
- /** How much of the work area a window takes unless the mode asks for more. */
30
- const COVERAGE = 0.86
29
+ /** How much of the work area a window takes, in every mode: the rest is margin. */
30
+ const COVERAGE = 0.9
31
31
 
32
32
  /** The user32 declarations every window operation here needs, added once per PowerShell run. */
33
33
  const USER32 = `