k0-board 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/k0-changelog/SKILL.md +96 -0
- package/CHANGELOG.md +185 -1
- package/README.md +289 -20
- package/bin/k0-board.js +51 -3
- package/package.json +6 -3
- package/platform/contract.js +30 -0
- package/platform/darwin/index.js +18 -3
- package/platform/darwin/servers.js +16 -0
- package/platform/index.js +9 -1
- package/platform/linux/index.js +18 -1
- package/platform/linux/servers.js +41 -0
- package/platform/shared/listeners.js +121 -0
- package/platform/shared/servers.js +111 -0
- package/platform/win32/index.js +15 -1
- package/platform/win32/servers.js +73 -0
- package/server/changelog.js +242 -0
- package/server/db.js +69 -0
- package/server/git.js +135 -0
- package/server/guard.js +31 -0
- package/server/idle.js +56 -0
- package/server/index.js +177 -36
- package/server/launcher.js +5 -1
- package/server/machine.js +9 -0
- package/server/paths.js +5 -4
- package/server/servers.js +481 -0
- package/server/settings.js +121 -0
- package/server/watcher.js +8 -1
- package/server/writer.js +128 -0
- package/web/board.css +137 -7
- package/web/board.js +285 -10
- package/web/changelog.css +185 -0
- package/web/changelog.html +43 -0
- package/web/changelog.js +298 -0
- package/web/index.html +8 -2
- package/web/recency.js +86 -0
- package/web/view.js +3 -1
package/README.md
CHANGED
|
@@ -40,7 +40,8 @@ npx k0-board
|
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
That is the whole thing. It tells you everything it is about to change, asks, and only then does
|
|
43
|
-
it
|
|
43
|
+
it — and when it is finished it opens the board in your browser, so there is no address to write
|
|
44
|
+
down anywhere. `k0-board uninstall` undoes all of it.
|
|
44
45
|
|
|
45
46
|
k0 is published on npm as [`k0-board`](https://www.npmjs.com/package/k0-board): one package, no
|
|
46
47
|
dependencies, nothing to compile. `npx` fetches it, runs the installer once and leaves nothing
|
|
@@ -69,8 +70,9 @@ that and went out from a laptop.)
|
|
|
69
70
|
| `k0-board doctor` | what this machine can and cannot do, and why |
|
|
70
71
|
|
|
71
72
|
`--yes` does not ask. `--from-source` points the service at the directory you are in instead of
|
|
72
|
-
copying it, which is what you want while working on k0 itself. `--no
|
|
73
|
-
|
|
73
|
+
copying it, which is what you want while working on k0 itself. `--no-open` finishes without
|
|
74
|
+
opening a browser, which is what you want over SSH. `--no-<name>` skips one optional change —
|
|
75
|
+
`--no-lid-sleep`, `--no-shift-enter`. `K0_PORT` moves the board off 4319.
|
|
74
76
|
|
|
75
77
|
From a clone, if you would rather look first:
|
|
76
78
|
|
|
@@ -83,7 +85,10 @@ node bin/k0-board.js start
|
|
|
83
85
|
That runs the server in the foreground and opens the board. Nothing is installed and nothing
|
|
84
86
|
outside the directory is touched.
|
|
85
87
|
|
|
86
|
-
The board lives at <http://k0.localhost:4319
|
|
88
|
+
The board lives at <http://k0.localhost:4319>, which is the address both `install` and `start`
|
|
89
|
+
open for you. If that name does not resolve on your machine — which happens on Windows, because
|
|
90
|
+
only browsers are obliged to know that `.localhost` means this computer — k0 opens
|
|
91
|
+
<http://127.0.0.1:4319> instead, and says so.
|
|
87
92
|
|
|
88
93
|
### Requirements
|
|
89
94
|
|
|
@@ -128,7 +133,7 @@ Nothing on this list happens before you have seen it and said yes. Everything on
|
|
|
128
133
|
| What | Where | Why |
|
|
129
134
|
|---|---|---|
|
|
130
135
|
| A copy of k0 | `~/.k0/app` | so the service does not point at a temporary npx cache |
|
|
131
|
-
| Your board, logs and cache | `~/.k0/` | `%LOCALAPPDATA%\k0` on Windows |
|
|
136
|
+
| Your board, settings, logs and cache | `~/.k0/` | `%LOCALAPPDATA%\k0` on Windows |
|
|
132
137
|
| A service that starts at login | LaunchAgents · systemd user unit · Task Scheduler | so k0 is simply there |
|
|
133
138
|
| **macOS only** — one administrator rule | `/etc/sudoers.d/k0-pmset` | to keep the Mac awake with the lid closed. Asks for your password once, and grants exactly two command lines |
|
|
134
139
|
| **macOS only** — one key binding | your Terminal profile (backed up first) | so Shift+Enter starts a new line |
|
|
@@ -195,8 +200,10 @@ k0 makes **no network requests**. Not one, not even for a font.
|
|
|
195
200
|
- **double click** — brings that session's terminal back to the front, even if you had minimised
|
|
196
201
|
it. It is what stops you losing track of which window is which.
|
|
197
202
|
|
|
198
|
-
- **Resume** — appears once
|
|
199
|
-
where it was.
|
|
203
|
+
- **Resume** — appears once the terminal has been closed, by you or by k0 after a long silence: it
|
|
204
|
+
reopens the same conversation exactly where it was. The card's prompt is **not** put back in —
|
|
205
|
+
that conversation answered it hours ago, and typing it in again would be at best something stale
|
|
206
|
+
in the way.
|
|
200
207
|
|
|
201
208
|
- **Done** — closes the work **and its terminal**, with no questions: undo it with **Reopen**, and
|
|
202
209
|
pick the conversation up again with **Resume**. It is not on backlog cards: they never started.
|
|
@@ -205,6 +212,16 @@ k0 makes **no network requests**. Not one, not even for a font.
|
|
|
205
212
|
still inside it makes macOS put up the "terminate running processes" dialog, and the window does
|
|
206
213
|
not close at all.
|
|
207
214
|
|
|
215
|
+
- **Close** — a link at the end of the row, quieter than the buttons because it is the one you
|
|
216
|
+
reach for least: it closes the terminal and **not the work**. The session is stopped, its window
|
|
217
|
+
goes, and the machine gets the memory back; the card stays exactly where it is, with the colour
|
|
218
|
+
it had — the one asking you something stays red, the one holding a plan up stays amber, the one
|
|
219
|
+
at your turn stays yellow — and **Resume** takes it from there. It is what a dozen windows open
|
|
220
|
+
and a computer running out of air are answered with.
|
|
221
|
+
|
|
222
|
+
It is **not there while the session is working**: stopping one mid-thought is not memory saved,
|
|
223
|
+
it is work lost. Wait for it to come back to you, and then close it.
|
|
224
|
+
|
|
208
225
|
The only thing that asks for confirmation is throwing a card away.
|
|
209
226
|
|
|
210
227
|
Cards reorder themselves inside a column: first the ones waiting for you, then the ones grinding
|
|
@@ -218,17 +235,53 @@ right, left, up, down, and diagonally in the corners — faster the closer you a
|
|
|
218
235
|
where the board ends. The thin shadow along one side says there is still something over there.
|
|
219
236
|
Holding a mouse button keeps the board still, so a click near the edge lands.
|
|
220
237
|
|
|
221
|
-
Bottom right there is the **zoom
|
|
238
|
+
Bottom right there is the **zoom**: + and − move it ten per cent at a time — 90%, 80%, 70% and so
|
|
239
|
+
on, between 30% and 200% — and the percentage between them, clicked, goes back to 100%. The button
|
|
222
240
|
next to it shrinks just enough to fit the whole board on the screen. On a trackpad, two fingers
|
|
223
|
-
move the view and a pinch zooms
|
|
241
|
+
move the view and a pinch zooms to anything in between; the next click of + or − picks the nearest
|
|
242
|
+
step from there. Where you were is still there after a reload.
|
|
224
243
|
|
|
225
244
|
The pills at the top are both legend and filter: click one and that status leaves the board. They
|
|
226
245
|
stay as you left them.
|
|
227
246
|
|
|
228
|
-
**The
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
247
|
+
**The board keeps itself down to what you are working on.** A repository you have not touched
|
|
248
|
+
since the day before your last piece of work folds away by itself: its column leaves the board
|
|
249
|
+
and its name appears in `Old`, over on the right. There is nothing to switch on and nothing to
|
|
250
|
+
tidy up — with a dozen repositories open, you arrive in the morning and the three or four you are
|
|
251
|
+
actually on are the ones standing there, side by side.
|
|
252
|
+
|
|
253
|
+
The day is counted back from **your last piece of work, not from the clock**, and that is what
|
|
254
|
+
makes it survive a weekend: on Monday the freshest thing on the board is Friday evening, so what
|
|
255
|
+
you find is Friday — not an empty board on the one morning you most need to see where you left
|
|
256
|
+
off. The moment you start something on Monday the line moves with you.
|
|
257
|
+
|
|
258
|
+
Two things never fold. **A column with something going on in it** — working, planning, asking you
|
|
259
|
+
something, holding a plan up for approval — stays where it is however old it is: hiding the one
|
|
260
|
+
that is waiting for you is the opposite of what the board is for. A terminal simply left open at
|
|
261
|
+
**Your turn** is not one of those: it is the resting state of every window you have not closed,
|
|
262
|
+
and a session you finished with three weeks ago would otherwise hold its column at full width for
|
|
263
|
+
ever. It folds like anything else — and folding closes nothing, so the terminal is still open and
|
|
264
|
+
the card is still exactly where you left it. And **nothing folds while you are looking at it**: within one visit the set of columns can only grow, and the board tidies itself
|
|
265
|
+
on the next reload, so a column never disappears out from under your hands.
|
|
266
|
+
|
|
267
|
+
**The last two columns on the right are `Old` and `Others`**, and they are the same list twice
|
|
268
|
+
over: a `+`, a name and the git mark, in alphabetical order. `Old` is the repositories that have
|
|
269
|
+
work on the board, just not lately; `Others` is the ones that have no column because they have no
|
|
270
|
+
cards at all. **Click a row in `Old` and the column comes back** at full width for the rest of the
|
|
271
|
+
visit — with everything that was in it, which is why the row itself says nothing about how much
|
|
272
|
+
that is or how long it has been there. Both columns are also there for the same two things:
|
|
273
|
+
getting into the files of a directory you are not working on, and giving birth to the first card
|
|
274
|
+
there with the `+`.
|
|
275
|
+
|
|
276
|
+
Next to a column's `+` there is one more button, which **puts that column away now** instead of
|
|
277
|
+
waiting for it to go quiet on its own — for when you are finished with a repository for the day
|
|
278
|
+
and would rather not look at it. On a column with something going on in it the button goes faint
|
|
279
|
+
and refuses, and says why if you hover it — it does not disappear, because a column runs and goes
|
|
280
|
+
back to your turn every few seconds and it would spend the session blinking.
|
|
281
|
+
Unlike the automatic fold, this one is remembered across reloads, until you fetch the column back
|
|
282
|
+
or something in it wakes up. It **closes nothing**: no session is stopped and no terminal is
|
|
283
|
+
shut. It is the board that gets smaller, not the work — what closes a session is `Close` or `Done`
|
|
284
|
+
on a card.
|
|
232
285
|
|
|
233
286
|
### The git mark
|
|
234
287
|
|
|
@@ -248,6 +301,93 @@ k0 **never runs `git fetch`**. It only looks at what your git already knows abou
|
|
|
248
301
|
itself; a push made by somebody else does not show until you fetch. On a repository with no
|
|
249
302
|
remote, pushing is not mentioned at all.
|
|
250
303
|
|
|
304
|
+
### The globe: is the site up
|
|
305
|
+
|
|
306
|
+
Last on a column's heading, after the git mark, a small **globe** says whether that repository's
|
|
307
|
+
dev server is running. **Grey** is off, **green** is up, and it **breathes** while it is coming
|
|
308
|
+
up. **Red** means it was started and did not come up — hover it for the reason and the log.
|
|
309
|
+
|
|
310
|
+
**One click switches it on or off. Two restart it.** And when it is green the repository's
|
|
311
|
+
**name is underlined with a green dashed line** and becomes a link: click it and the site opens
|
|
312
|
+
in another tab, on the port it is really listening on. The line is the same green as the globe
|
|
313
|
+
and stays for as long as the server does, so a repository that is up is one you can tell apart
|
|
314
|
+
at a glance, from across the board, without going near it. Hover the globe to read the rest — the port, the command it was
|
|
315
|
+
started with, and where its log is.
|
|
316
|
+
|
|
317
|
+
A repository gets a globe when its `package.json` has a **`dev`** script, or failing that a
|
|
318
|
+
**`serve`** one. Nothing to configure and nothing to fill in. `start` is deliberately not on that
|
|
319
|
+
list: by convention it runs what has been *built*, which is not what this is for.
|
|
320
|
+
|
|
321
|
+
Three things are worth knowing, because they are the point.
|
|
322
|
+
|
|
323
|
+
**The server outlives the session.** k0 starts it detached and owned by nobody. Close the session
|
|
324
|
+
that asked for it, close every session, restart k0 itself — the server stays up and the globe
|
|
325
|
+
stays green. This is the whole reason it exists: a dev server started inside a session belongs to
|
|
326
|
+
that session, and dies with it.
|
|
327
|
+
|
|
328
|
+
**k0 never asks the server whether it is up.** It makes no network requests, here as everywhere
|
|
329
|
+
else. "Up" means the process is alive *and* the operating system says it is holding a TCP port
|
|
330
|
+
open. That is stronger evidence than a reply would be, and it is where the port in the tooltip
|
|
331
|
+
comes from — k0 never guesses a port, it reads the one your server actually chose.
|
|
332
|
+
|
|
333
|
+
**A server you started yourself is recognised.** Start one by hand in a terminal and the globe
|
|
334
|
+
goes green on its own within a few seconds, with the right port, and switches it off if you ask
|
|
335
|
+
it to. A globe that only knew about its own servers would sit grey next to a site that is plainly
|
|
336
|
+
running, which is worse than no globe at all. On Windows this one part is missing — nothing there
|
|
337
|
+
can read another process's working directory — and k0 says so rather than showing those servers
|
|
338
|
+
as off.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## The ChangeLog
|
|
343
|
+
|
|
344
|
+
The git mark answers "is this safe" for one repository, right now. The **ChangeLog** answers a
|
|
345
|
+
different question — *what have I actually been doing* — across all of them at once. It is the
|
|
346
|
+
list-shaped button next to the `+`, and it opens in a page of its own.
|
|
347
|
+
|
|
348
|
+
It reads like a release note rather than a report. An opening paragraph saying how the day went,
|
|
349
|
+
then one block per repository: a line saying what is different now, a short paragraph, a few
|
|
350
|
+
points, and — the part that matters most — **what is done but not out yet**. A commit sitting on
|
|
351
|
+
your machine, three changelog lines written but not closed into a version, four files never
|
|
352
|
+
committed. That is the distinction the whole page is built on: what left here, and what only
|
|
353
|
+
looks finished.
|
|
354
|
+
|
|
355
|
+
Four windows across the top: **Today**, **Yesterday**, **Week**, **Month**. It opens on none of
|
|
356
|
+
them: it opens on **the last day you actually worked**. On a Monday that is Friday, because a
|
|
357
|
+
page that comes up empty after every weekend is a page you stop opening.
|
|
358
|
+
|
|
359
|
+
**Only what you touched in that window appears.** A repository that has been dirty since March
|
|
360
|
+
and that you have not opened since is not news, and a summary that repeats it every morning is
|
|
361
|
+
one nobody is still reading by Thursday. Something has to have happened there — a commit, a file
|
|
362
|
+
saved, a card moved — before k0 will tell you what is still outstanding in it. And only **your**
|
|
363
|
+
commits are counted: k0 asks git which email you commit with in that repository and shows that
|
|
364
|
+
person's day, so a repository shared with other people still tells you about yours.
|
|
365
|
+
|
|
366
|
+
Below every block the facts are folded away, one line to open them: the commits themselves, each
|
|
367
|
+
marked green if it is online and amber if it is still only here. That is what to open when you do
|
|
368
|
+
not believe the paragraph above it.
|
|
369
|
+
|
|
370
|
+
### Who writes it
|
|
371
|
+
|
|
372
|
+
k0 does not. **k0 makes no network requests**, and that is worth more than a better paragraph: a
|
|
373
|
+
model of its own would mean a key to keep, an account to configure, and your commit messages
|
|
374
|
+
leaving this machine.
|
|
375
|
+
|
|
376
|
+
It does not need one. If you are running k0 you already have Claude Code installed, signed in and
|
|
377
|
+
paid for — that is the whole point of the board — so the model is already here. k0 works out the
|
|
378
|
+
facts, hands them to Claude Code, and shows what comes back. No terminal opens, nothing appears
|
|
379
|
+
on the board, and there is nothing to set up: the skill that does the writing ships inside the
|
|
380
|
+
package, the same way `/k0-import` does.
|
|
381
|
+
|
|
382
|
+
It is written **in your language**, without a setting anywhere, because it is written out of your
|
|
383
|
+
own commit messages and card titles.
|
|
384
|
+
|
|
385
|
+
Nothing is saved. Every time you open the page it is written again, and changing the window
|
|
386
|
+
writes it again — which is why there is no stale summary to clear and no history to prune.
|
|
387
|
+
"Today" changes while you are reading it, so keeping a copy would be wrong more often than it
|
|
388
|
+
helped. If Claude Code is not on the machine the page still opens: you get the facts, and a line
|
|
389
|
+
saying why the rest is missing.
|
|
390
|
+
|
|
251
391
|
---
|
|
252
392
|
|
|
253
393
|
## The files of a session
|
|
@@ -366,10 +506,55 @@ which on an old session says nothing about this minute. And summing the memory o
|
|
|
366
506
|
counts shared memory twice, so the total is a little generous: it is the usual approximation, and
|
|
367
507
|
it is the right one for the question that matters — which one do I close to feel better.
|
|
368
508
|
|
|
509
|
+
And once you have decided, **`Close` on that card is how you give the memory back**: the session
|
|
510
|
+
is stopped and its window goes, the work stays on the board as it was, and `Resume` picks the
|
|
511
|
+
conversation up where it left off. Nothing has to be declared finished to stop weighing.
|
|
512
|
+
|
|
369
513
|
Reading the process table costs little on a calm machine and a lot on a struggling one, which is
|
|
370
514
|
exactly when you look at it. So it is sampled every three seconds, and **only while the board is
|
|
371
515
|
open**.
|
|
372
516
|
|
|
517
|
+
### And the ones you forget, k0 closes for you
|
|
518
|
+
|
|
519
|
+
The paragraph above assumes you noticed. Most of the time nobody does: the window that is costing
|
|
520
|
+
you a gigabyte is the one you stopped thinking about yesterday. So **after twelve hours with
|
|
521
|
+
nothing happening, k0 does that `Close` itself** — the session is stopped, its window goes, and the
|
|
522
|
+
memory comes back.
|
|
523
|
+
|
|
524
|
+
Nothing is lost by it. The card stays exactly where it is with the status it had, and **Resume**
|
|
525
|
+
picks the conversation up where it was. That is the whole reason this is safe: a closed terminal
|
|
526
|
+
has never been a lost session.
|
|
527
|
+
|
|
528
|
+
**Only yellow is ever touched** — *Your turn*, the ball in your court and nothing happening. The
|
|
529
|
+
rest is left alone, and each for its own reason:
|
|
530
|
+
|
|
531
|
+
- **Working** and **Planning** are somebody mid-thought. Stopping one is not memory saved, it is
|
|
532
|
+
work lost — the same rule the `Close` link has always followed.
|
|
533
|
+
- **Needs answer** and **Needs approval** would lose the very thing they were showing you. A
|
|
534
|
+
question and a finished plan are drawn by the terminal and are not written to the transcript
|
|
535
|
+
until they are answered, so closing that window throws the question away.
|
|
536
|
+
- A session sitting in a **shell** is left alone as well. That is a shell you dropped into, and
|
|
537
|
+
there may be a command of yours running in it; k0 cannot see what it is, so k0 does not touch it.
|
|
538
|
+
On the board it reads as yellow like any other, which is exactly why this one has to be said.
|
|
539
|
+
|
|
540
|
+
And a window you were using an hour ago is never closed, whatever the card's history says. k0 has
|
|
541
|
+
four clocks for a session — two of its own and two Claude Code keeps — and **the newest one wins**.
|
|
542
|
+
Being wrong in that direction leaves a window open until tomorrow; being wrong the other way closes
|
|
543
|
+
a terminal you were about to go back to.
|
|
544
|
+
|
|
545
|
+
A card k0 tidied away says so: where one you closed yourself reads *session closed*, this one reads
|
|
546
|
+
*closed automatically*. Same italic, same place, one word different.
|
|
547
|
+
|
|
548
|
+
And the memory chip at the end of the top bar says it is watching, and from when: **RAM 79% · CPU
|
|
549
|
+
19% · closes at 8h**, in lighter type than the numbers beside it. That is the whole announcement,
|
|
550
|
+
and it is on that chip rather than anywhere else because that is where the memory is already being
|
|
551
|
+
talked about — something that closes your windows for you should never be a surprise. Hover for
|
|
552
|
+
the sentence. Switched off, the chip says nothing at all rather than *0h*, which would read as
|
|
553
|
+
"closes immediately", and the hover is what tells you it is off.
|
|
554
|
+
|
|
555
|
+
Twelve hours is what it does if you never say otherwise. `closeIdleTerminalsAfterHours` in the
|
|
556
|
+
settings file changes it, and **`0` switches it off** — see below.
|
|
557
|
+
|
|
373
558
|
---
|
|
374
559
|
|
|
375
560
|
## Filling the board with what you have already done
|
|
@@ -580,6 +765,38 @@ Two side effects of the Cmd+V k0 uses to write your prompt, worth knowing:
|
|
|
580
765
|
|
|
581
766
|
---
|
|
582
767
|
|
|
768
|
+
## Settings
|
|
769
|
+
|
|
770
|
+
There is no settings page on the board, and there is not going to be one. The board is for what
|
|
771
|
+
changes during a working day; a number you set once and then never look at again is the opposite of
|
|
772
|
+
that, and putting it on screen would cost the one page that has to stay readable in exchange for
|
|
773
|
+
nothing.
|
|
774
|
+
|
|
775
|
+
So the settings are a file, and **the file is the list**:
|
|
776
|
+
|
|
777
|
+
```
|
|
778
|
+
~/.k0/config.json macOS and Linux
|
|
779
|
+
%LOCALAPPDATA%\k0\config.json Windows
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
k0 writes it on the first run with **every setting already in it at its default**, so opening it is
|
|
783
|
+
how you find out what there is to change. It sits beside the board rather than inside the app —
|
|
784
|
+
`npx` unpacks into a cache npm is free to wipe, and `k0-board install` overwrites the app
|
|
785
|
+
directory, so a setting kept in there would not survive an update. Change it and k0 picks it up by
|
|
786
|
+
itself on the next round: nothing to restart.
|
|
787
|
+
|
|
788
|
+
| | |
|
|
789
|
+
|---|---|
|
|
790
|
+
| `closeIdleTerminalsAfterHours` | How long a yellow card's terminal may sit there before k0 closes it and gives the memory back. `12` if you say nothing. **`0` switches it off.** Anything under an hour is treated as an hour — below that this stops being a tidy-up and starts closing windows while you are using them. |
|
|
791
|
+
|
|
792
|
+
Delete a line to go back to its default; a file k0 cannot read is ignored altogether and the
|
|
793
|
+
defaults hold, rather than k0 refusing to start over a stray comma.
|
|
794
|
+
|
|
795
|
+
**`k0-board doctor` prints the same list**, with the value actually in force and where the file is,
|
|
796
|
+
for the times you would rather ask than go and look.
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
583
800
|
## How it is put together
|
|
584
801
|
|
|
585
802
|
No dependencies, nothing to compile. Change a file, reload the page.
|
|
@@ -595,33 +812,61 @@ platform/
|
|
|
595
812
|
(menubar/, which is also what posts the notifications and reads the permission)
|
|
596
813
|
linux/ tmux and any emulator, systemd-inhibit, systemd user units, a GTK tray
|
|
597
814
|
win32/ PowerShell and Windows Terminal, execution state, Task Scheduler, a WinForms tray
|
|
598
|
-
shared/ what more than one of them needs: tmux, running commands, reading a process
|
|
815
|
+
shared/ what more than one of them needs: tmux, running commands, reading a process
|
|
816
|
+
table, and reading who is holding which port
|
|
599
817
|
server/
|
|
600
818
|
index.js the http server, the API, and the watching loop that runs every second
|
|
601
|
-
|
|
602
|
-
|
|
819
|
+
guard.js who is allowed to talk to this server at all: the Host and the Origin
|
|
820
|
+
db.js SQLite (node:sqlite): the card, session_event, pref and dev_server tables —
|
|
821
|
+
docs/database.md
|
|
822
|
+
paths.js where the board, the logs, the settings and the cache live
|
|
823
|
+
settings.js the few things you can change, and the file that is the list of them —
|
|
824
|
+
~/.k0/config.json, written out with everything in it and re-read when it changes
|
|
825
|
+
idle.js which terminals have sat still long enough to be closed and given back. Only the
|
|
826
|
+
deciding: it kills nothing, so every rule in it can be proved without a machine
|
|
603
827
|
watcher.js reads Claude Code's own files and derives the statuses; also renames a session
|
|
604
|
-
git.js the only one that talks to git: what is committed, what is pushed, whose it is
|
|
828
|
+
git.js the only one that talks to git: what is committed, what is pushed, whose it is,
|
|
829
|
+
and — for the ChangeLog — what the commits actually said
|
|
830
|
+
changelog.js gathers what happened in a window, out of git and out of session_event. Decides
|
|
831
|
+
which repositories are worth mentioning at all, and stores nothing
|
|
832
|
+
writer.js hands those facts to the Claude Code already on this machine and gets the words
|
|
833
|
+
back. The only place k0 starts a model, and it never leaves the machine to do it
|
|
605
834
|
launcher.js starts and resumes sessions, through the platform's terminal
|
|
835
|
+
servers.js the dev server of a repository: what starts it, whether it is up, and on what
|
|
836
|
+
port. The only thing k0 starts that is meant to outlive k0
|
|
606
837
|
mode.js the four modes: how awake to keep the machine, and whether the text goes large
|
|
607
838
|
projects.js the repositories, in the order you last used them
|
|
608
839
|
sessions.js digs already-lived sessions out of the transcripts, to import as cards
|
|
609
840
|
files.js the only one that reads the projects' disk: what is there, what changed, what it says
|
|
610
841
|
machine.js the only one that looks at processes and memory: what it all costs, and who is costing it
|
|
611
842
|
pdf.js the document on paper, printed by a headless browser
|
|
612
|
-
web/ the
|
|
843
|
+
web/ the three pages (html, css, js served exactly as they are)
|
|
613
844
|
index.html the board — board.js, board.css, view.js. A note is never taller than it is
|
|
614
845
|
wide: the title, the age and the buttons always show, the text in the middle is
|
|
615
846
|
what gets clipped
|
|
616
847
|
files.html the file viewer — files.js, files.css
|
|
617
|
-
|
|
848
|
+
changelog.html what you have been doing — changelog.js, changelog.css. The only page in k0 you
|
|
849
|
+
read top to bottom instead of looking at, so it is the only one that scrolls
|
|
850
|
+
base.css colours, fonts and scale: the house variables, shared by all three pages
|
|
618
851
|
md.js markdown laid out, written by hand because nothing here is compiled
|
|
852
|
+
recency.js which repositories are still warm, and which fold away into `Old`
|
|
619
853
|
fuzzy.js searching the names: the letters you type, in the order you type them
|
|
620
854
|
mentions.js which of a repository's files a piece of text names
|
|
621
855
|
refs.js what a name written inside a document points at, and which of the nine READMEs
|
|
622
856
|
.claude/skills/
|
|
623
857
|
k0-import/ the skill that fills the board with sessions you have already had
|
|
624
|
-
|
|
858
|
+
k0-changelog/ the one that turns the facts of a stretch of work into something readable. k0
|
|
859
|
+
calls it by itself when the ChangeLog is opened, so it is in the package too
|
|
860
|
+
changelog/ the one that writes the changelog and the documentation when work is finished.
|
|
861
|
+
For whoever works on k0, not for whoever uses it: it is not in the package
|
|
862
|
+
commit-push-deploy/
|
|
863
|
+
the road out: the documents, the tests, the commit, the push, and the tag that
|
|
864
|
+
publishes. Also for whoever works on k0, and also not in the package
|
|
865
|
+
docs/
|
|
866
|
+
database.md the shape of the database as it is now: the four tables, column by column
|
|
867
|
+
testing.md how the tests are written, run and measured, and what is left untested on purpose
|
|
868
|
+
test/ npm test — Node's own runner, no framework. See docs/testing.md
|
|
869
|
+
harness.mjs check(label, got, want), and the sections the labels are grouped under
|
|
625
870
|
```
|
|
626
871
|
|
|
627
872
|
### What it reads, and what it never does
|
|
@@ -637,6 +882,13 @@ writes for itself:
|
|
|
637
882
|
Sessions are launched with `--session-id` (k0 chooses the id, so the card ↔ session link is
|
|
638
883
|
certain), `-n` for the name, `--resume` to pick one up, and `--permission-mode plan` on new ones.
|
|
639
884
|
|
|
885
|
+
The ChangeLog runs Claude Code a second way: `claude -p /k0-changelog`, with the facts on
|
|
886
|
+
standard input and no terminal at all. It is the one time k0 starts a model, and it still opens
|
|
887
|
+
no socket of its own — the request is Claude Code's, made with your own account, from your own
|
|
888
|
+
machine. Those runs cannot come back as cards: `sessions.js` drops `claude -p` when it looks for
|
|
889
|
+
sessions to import, which is also how it tells a hundred and forty real sessions from eight
|
|
890
|
+
hundred automated ones.
|
|
891
|
+
|
|
640
892
|
### The traps
|
|
641
893
|
|
|
642
894
|
Field notes the code takes for granted.
|
|
@@ -665,6 +917,23 @@ Field notes the code takes for granted.
|
|
|
665
917
|
- **The session file appears before the interface is ready to receive.** Writing at that moment
|
|
666
918
|
loses the first characters and swallows the Enter. So k0 waits until it can see the input box —
|
|
667
919
|
and where a platform cannot read a terminal's screen, it waits a fixed moment and says so.
|
|
920
|
+
- **A service's PATH is not a shell's PATH.** Under launchd it is barely more than
|
|
921
|
+
`/usr/bin:/bin`, and `npm` and `node` usually arrive from a version manager whose PATH only
|
|
922
|
+
exists inside a shell. So a dev server is started through a **login shell** — which is the same
|
|
923
|
+
reason `findClaude` ends up asking one, and it is also what puts the project's own `.nvmrc` in
|
|
924
|
+
force.
|
|
925
|
+
- **`npm run dev` is not the server, it is the thing that starts it.** SIGTERM to npm alone leaves
|
|
926
|
+
`vite` holding the port, which is why the old per-repo skills all ended up reaching for
|
|
927
|
+
`pkill -f`. k0 starts the server **detached**, so it leads a process group of its own, and
|
|
928
|
+
stops the *group*. But only when the process it was told about really is that group's leader:
|
|
929
|
+
a server somebody started by hand in a shell without job control can share its group with the
|
|
930
|
+
**shell**, and signalling that would close the user's terminal to stop a dev server. Where the
|
|
931
|
+
leader is somebody else, the processes are signalled one at a time instead.
|
|
932
|
+
- **A remembered pid is only a number, and the system hands numbers out again.** A row written
|
|
933
|
+
before a reboot can point at a stranger's process — and that row is what a click on the globe
|
|
934
|
+
would kill. So a remembered server is only believed while the process at that pid is still
|
|
935
|
+
running the command it was started with, and `stop` reads the machine before it signals
|
|
936
|
+
anything rather than trusting what it stored.
|
|
668
937
|
- **`do script … in tab 1 of window id N`** writes into the interface with no simulated keystrokes
|
|
669
938
|
and no permissions, but it **always adds the Enter**. You can hold the Enter back with a
|
|
670
939
|
trailing backslash, but the backslash stays on screen and forces two presses: tried and rejected.
|
package/bin/k0-board.js
CHANGED
|
@@ -48,6 +48,7 @@ if (Number(process.versions.node.split('.')[0]) < MIN_NODE) {
|
|
|
48
48
|
|
|
49
49
|
const { name: platformName, capabilities, notes, service, extras, shell } = await import('../platform/index.js')
|
|
50
50
|
const paths = await import('../server/paths.js')
|
|
51
|
+
const settings = await import('../server/settings.js')
|
|
51
52
|
|
|
52
53
|
const APP_DIR = flag('from-source') ? SOURCE : paths.APP_DIR
|
|
53
54
|
const ENTRY = path.join(APP_DIR, 'server', 'index.js')
|
|
@@ -126,6 +127,31 @@ async function waitForServer(seconds = 10) {
|
|
|
126
127
|
return false
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
// ── Opening the board ────────────────────────────────────────────────────────
|
|
131
|
+
/**
|
|
132
|
+
* Opens the board in a browser and answers with the address it used.
|
|
133
|
+
*
|
|
134
|
+
* `k0.localhost` is the name the board deserves, and inside a browser it is guaranteed to mean
|
|
135
|
+
* this machine — RFC 6761 reserves the whole of `.localhost` for loopback and every browser
|
|
136
|
+
* honours it. A resolver is under no such obligation, and on Windows usually declines. So the
|
|
137
|
+
* name is tried first, from here, and the number is what we fall back to: a board at a duller
|
|
138
|
+
* address beats a blank page at a prettier one.
|
|
139
|
+
*
|
|
140
|
+
* Not being able to open anything at all is not a failure. Installing over SSH, or on a machine
|
|
141
|
+
* with no session to put a window in, is an ordinary thing to do, and the install has already
|
|
142
|
+
* succeeded by the time we get here.
|
|
143
|
+
*/
|
|
144
|
+
async function openBoard() {
|
|
145
|
+
let url = `http://k0.localhost:${PORT}`
|
|
146
|
+
try {
|
|
147
|
+
await fetch(`${url}/api/status`, { signal: AbortSignal.timeout(1500) })
|
|
148
|
+
} catch {
|
|
149
|
+
url = `http://127.0.0.1:${PORT}`
|
|
150
|
+
}
|
|
151
|
+
await shell.openBrowser(url).catch(() => {})
|
|
152
|
+
return url
|
|
153
|
+
}
|
|
154
|
+
|
|
129
155
|
// ── install ──────────────────────────────────────────────────────────────────
|
|
130
156
|
async function install() {
|
|
131
157
|
say(`\n${bold('k0')} — the board that drives your Claude Code sessions`)
|
|
@@ -218,7 +244,8 @@ async function install() {
|
|
|
218
244
|
appDir: APP_DIR,
|
|
219
245
|
tray,
|
|
220
246
|
})
|
|
221
|
-
|
|
247
|
+
const up = await waitForServer()
|
|
248
|
+
if (up) ok(`k0 is up: ${bold(`http://k0.localhost:${PORT}`)}`)
|
|
222
249
|
else warn(`the server is not answering yet — look in ${paths.LOG_DIR}`)
|
|
223
250
|
|
|
224
251
|
// ── The two invitations ────────────────────────────────────────────────────
|
|
@@ -231,6 +258,12 @@ async function install() {
|
|
|
231
258
|
note(`Where: ${extra.target}`)
|
|
232
259
|
}
|
|
233
260
|
|
|
261
|
+
// Last of all, on purpose: by now the imported cards are on the board, so what opens is
|
|
262
|
+
// somebody's own work rather than an empty grid they would have to reload.
|
|
263
|
+
if (up && !flag('no-open')) {
|
|
264
|
+
say(`\n${bold('Done.')} The board is open at ${await openBoard()}`)
|
|
265
|
+
return
|
|
266
|
+
}
|
|
234
267
|
say(`\n${bold('Done.')} The board is at http://k0.localhost:${PORT}`)
|
|
235
268
|
if (process.platform === 'win32') note(`If that name does not resolve, use http://127.0.0.1:${PORT}`)
|
|
236
269
|
}
|
|
@@ -334,16 +367,17 @@ async function uninstall() {
|
|
|
334
367
|
// ── start ────────────────────────────────────────────────────────────────────
|
|
335
368
|
/** Runs the server in the foreground and opens the board. No service, nothing installed. */
|
|
336
369
|
async function start() {
|
|
370
|
+
const open = !flag('no-open')
|
|
337
371
|
if (await alive()) {
|
|
338
372
|
ok(`k0 is already running on http://k0.localhost:${PORT}`)
|
|
339
|
-
|
|
373
|
+
if (open) await openBoard()
|
|
340
374
|
return
|
|
341
375
|
}
|
|
342
376
|
const child = spawn(process.execPath, [path.join(SOURCE, 'server', 'index.js')], {
|
|
343
377
|
stdio: 'inherit',
|
|
344
378
|
env: { ...process.env, K0_PORT: String(PORT) },
|
|
345
379
|
})
|
|
346
|
-
if (await waitForServer())
|
|
380
|
+
if ((await waitForServer()) && open) await openBoard()
|
|
347
381
|
child.on('exit', (code) => process.exit(code ?? 0))
|
|
348
382
|
}
|
|
349
383
|
|
|
@@ -387,6 +421,19 @@ async function doctor() {
|
|
|
387
421
|
]
|
|
388
422
|
for (const [what, yes] of rows) (yes ? ok : warn)(what)
|
|
389
423
|
|
|
424
|
+
// The settings, in full, with what is actually in force. k0 has no settings page and is not
|
|
425
|
+
// getting one, so this — and the file itself — is where the list lives. Printing every key
|
|
426
|
+
// rather than only the ones somebody has changed is the point: it is an inventory.
|
|
427
|
+
step('Settings')
|
|
428
|
+
const conf = settings.status()
|
|
429
|
+
say(dim(` ${conf.path}${conf.exists ? '' : ' (not written yet — the defaults are in force)'}`))
|
|
430
|
+
if (conf.broken) warn('this file could not be read: it is being ignored and the defaults are in force')
|
|
431
|
+
for (const [key, value] of Object.entries(conf.values)) {
|
|
432
|
+
const dflt = settings.DEFAULTS[key]
|
|
433
|
+
const off = key === 'closeIdleTerminalsAfterHours' && value === 0
|
|
434
|
+
say(` ${bold(key)} ${off ? 'off' : value}${value === dflt ? dim(' (default)') : dim(` (default ${dflt})`)}`)
|
|
435
|
+
}
|
|
436
|
+
|
|
390
437
|
const explained = Object.entries(notes)
|
|
391
438
|
if (explained.length) {
|
|
392
439
|
step('Why not')
|
|
@@ -434,6 +481,7 @@ ${bold('k0-board')} — a board of sticky notes that drives your Claude Code ses
|
|
|
434
481
|
Options
|
|
435
482
|
${bold('--yes')} do not ask, assume yes
|
|
436
483
|
${bold('--from-source')} point the service at this directory instead of copying it
|
|
484
|
+
${bold('--no-open')} do not open the board in a browser at the end
|
|
437
485
|
${bold('--no-<name>')} skip one optional change, e.g. --no-lid-sleep
|
|
438
486
|
`)
|
|
439
487
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "k0-board",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A board of sticky notes that drives your Claude Code sessions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -32,14 +32,17 @@
|
|
|
32
32
|
"web",
|
|
33
33
|
"menubar",
|
|
34
34
|
"!menubar/k0.app",
|
|
35
|
-
".claude/skills",
|
|
35
|
+
".claude/skills/k0-import",
|
|
36
|
+
".claude/skills/k0-changelog",
|
|
36
37
|
"README.md",
|
|
37
38
|
"CHANGELOG.md",
|
|
38
39
|
"LICENSE"
|
|
39
40
|
],
|
|
40
41
|
"scripts": {
|
|
41
42
|
"start": "node server/index.js",
|
|
42
|
-
"test": "node test
|
|
43
|
+
"test": "node --test \"test/*.test.mjs\"",
|
|
44
|
+
"test:watch": "node --test --watch \"test/*.test.mjs\"",
|
|
45
|
+
"coverage": "node --test --experimental-test-coverage --test-coverage-exclude=\"platform/**\" --test-coverage-exclude=\"test/**\" \"test/*.test.mjs\"",
|
|
43
46
|
"doctor": "node bin/k0-board.js doctor"
|
|
44
47
|
}
|
|
45
48
|
}
|
package/platform/contract.js
CHANGED
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
* @property {object} metrics
|
|
34
34
|
* @property {boolean} metrics.pressure the kernel publishes a memory-pressure verdict
|
|
35
35
|
* @property {boolean} metrics.swap swap usage is readable
|
|
36
|
+
* @property {object} servers
|
|
37
|
+
* @property {boolean} servers.run can start and stop a project's dev server
|
|
38
|
+
* @property {boolean} servers.ports can tell which port a process is listening on
|
|
39
|
+
* @property {boolean} servers.adopt can recognise a server started outside k0
|
|
36
40
|
* @property {object} service
|
|
37
41
|
* @property {boolean} service.autostart can register a service that starts at login
|
|
38
42
|
* @property {boolean} tray a menu bar / system tray icon is available
|
|
@@ -47,6 +51,7 @@ export const NO_CAPABILITIES = {
|
|
|
47
51
|
terminal: { windows: false, font: false, readScreen: false, pasteWithoutSending: false, title: false },
|
|
48
52
|
power: { keepAwake: false, keepDisplayAwake: false, lidSleep: false, battery: false },
|
|
49
53
|
metrics: { pressure: false, swap: false },
|
|
54
|
+
servers: { run: false, ports: false, adopt: false },
|
|
50
55
|
service: { autostart: false },
|
|
51
56
|
tray: false,
|
|
52
57
|
revealInFileManager: false,
|
|
@@ -101,6 +106,30 @@ export const NO_CAPABILITIES = {
|
|
|
101
106
|
* @property {() => Promise<number>} pressureLevel 1 normal, 2 warning, 4 critical
|
|
102
107
|
*/
|
|
103
108
|
|
|
109
|
+
/**
|
|
110
|
+
* The dev server a project runs while you are working on it — the one the globe in the column
|
|
111
|
+
* heading switches on and off. k0 starts it detached, so it outlives both the session that
|
|
112
|
+
* asked for it and k0 itself; what this adapter adds is the two things only the operating
|
|
113
|
+
* system knows, and the one gesture Node cannot make portably.
|
|
114
|
+
*
|
|
115
|
+
* @typedef {object} ServersAdapter
|
|
116
|
+
* @property {() => {run: boolean, ports: boolean, adopt: boolean}} capabilities
|
|
117
|
+
* @property {() => Promise<Map<number, Set<number>>>} listeners
|
|
118
|
+
* Every process holding a TCP port open, and which ports. This is how k0 knows a server is
|
|
119
|
+
* up: not by asking it, which would be a network request, but by seeing it hold the socket.
|
|
120
|
+
* @property {(pids: number[]) => Promise<Map<number, string>>} cwds
|
|
121
|
+
* Where each of those processes is working from, which is what ties one to a repository.
|
|
122
|
+
* An adapter that cannot say returns an empty map and reports `adopt: false`.
|
|
123
|
+
* @property {(command: string) => {file: string, args: string[]}} shell
|
|
124
|
+
* How to run `command` inside a project. It goes through a LOGIN shell on Unix, and that is
|
|
125
|
+
* not a flourish: the server runs from a launch agent whose PATH is barely more than
|
|
126
|
+
* /usr/bin:/bin, while npm and node usually arrive from a version manager whose PATH only
|
|
127
|
+
* exists inside a shell. It is the same reason `findClaude` ends up in a shell.
|
|
128
|
+
* @property {(root: number, pids: number[], signal?: string) => Promise<boolean>} stop
|
|
129
|
+
* Stops the server and everything it started. `pids` is the whole subtree, `root` included:
|
|
130
|
+
* killing the top process alone leaves the real server holding the port.
|
|
131
|
+
*/
|
|
132
|
+
|
|
104
133
|
/**
|
|
105
134
|
* @typedef {object} ShellAdapter
|
|
106
135
|
* @property {(absPath: string) => Promise<unknown>} revealInFileManager
|
|
@@ -129,6 +158,7 @@ export const NO_CAPABILITIES = {
|
|
|
129
158
|
* @property {TerminalAdapter} terminal
|
|
130
159
|
* @property {PowerAdapter} power
|
|
131
160
|
* @property {MetricsAdapter} metrics
|
|
161
|
+
* @property {ServersAdapter} servers
|
|
132
162
|
* @property {ShellAdapter} shell
|
|
133
163
|
* @property {ServiceAdapter} service
|
|
134
164
|
*/
|