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/.claude/skills/k0-ultracode/SKILL.md +3 -3
- package/CHANGELOG.md +95 -1
- package/LICENSE +83 -18
- package/README.md +259 -45
- package/bin/k0-board.js +48 -1
- package/package.json +2 -2
- package/platform/darwin/terminal.js +6 -7
- package/platform/shared/tmux.js +2 -2
- package/platform/win32/terminal.js +2 -2
- package/server/cloud.js +355 -0
- package/server/db.js +375 -58
- package/server/index.js +92 -2
- package/server/launcher.js +8 -10
- package/server/mirror.js +21 -0
- package/server/sync.js +383 -0
- package/server/update.js +3 -2
- package/web/account.js +265 -0
- package/web/base.css +93 -0
- package/web/board.js +1 -1
- package/web/files.js +1 -1
- package/web/index.html +1 -0
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
|
[](https://github.com/alessio-ragni/k0-board/actions/workflows/ci.yml)
|
|
25
25
|
[](https://www.npmjs.com/package/k0-board)
|
|
26
|
-
[](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) · [
|
|
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
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
`
|
|
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
|
|
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 —
|
|
361
|
-
|
|
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
|
|
603
|
-
shared between epics and stories, and it is
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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. **
|
|
878
|
-
paragraph: a model of its own would mean a key to keep,
|
|
879
|
-
messages going out over a wire
|
|
880
|
-
|
|
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
|
|
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
|
|
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
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
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
|
|
1451
|
-
|
|
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
|
|
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
|
|
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.
|
|
1872
|
-
|
|
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
|
-
**
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
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
|
-
/**
|
|
21
|
-
|
|
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
|
|
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
|
*/
|
package/platform/shared/tmux.js
CHANGED
|
@@ -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
|
|
40
|
-
const COVERAGE = 0.
|
|
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
|
|
30
|
-
const COVERAGE = 0.
|
|
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 = `
|