k0-board 0.2.0 → 0.4.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/CHANGELOG.md +116 -1
- package/README.md +179 -18
- package/bin/k0-board.js +16 -0
- package/package.json +1 -1
- package/platform/contract.js +7 -1
- package/platform/darwin/index.js +1 -0
- package/platform/darwin/shell.js +4 -1
- package/platform/index.js +1 -0
- package/platform/linux/index.js +11 -0
- package/platform/linux/shell.js +10 -1
- package/platform/win32/index.js +1 -0
- package/platform/win32/shell.js +4 -1
- package/server/db.js +11 -0
- package/server/files.js +105 -14
- package/server/idle.js +56 -0
- package/server/index.js +90 -4
- package/server/launcher.js +5 -1
- package/server/paths.js +5 -4
- package/server/settings.js +121 -0
- package/web/board.css +12 -0
- package/web/board.js +22 -4
- package/web/conf.js +118 -0
- package/web/files.css +206 -1
- package/web/files.html +10 -0
- package/web/files.js +463 -62
- package/web/fuzzy.js +73 -21
- package/web/json.js +145 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,119 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.0]
|
|
10
|
+
|
|
11
|
+
The file viewer stops being read-only, and stops being just a pile of documents. It now shows the
|
|
12
|
+
folders a repository is built from, and behind a switch it shows the configuration too — an
|
|
13
|
+
`.env`, a `package.json`, a workflow's YAML — laid out as the kind of thing each one is, and
|
|
14
|
+
editable in place with a Save button. The name search, meanwhile, turns out to have been quietly
|
|
15
|
+
wrong the whole time: it matched letters scattered across a whole path rather than the file you
|
|
16
|
+
meant, and it now looks only at the name and shows you why a row is there at all.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **Folders, at last.** The file viewer's listing starts with the folders, and clicking one takes
|
|
21
|
+
you inside it: the folders it holds first, then its files. The path is written across the top
|
|
22
|
+
one piece at a time and **every piece is a way back up** — from `docs/backlog/handover` you
|
|
23
|
+
reach `docs` in one click, and the whole repository by clicking its name. The path above an open
|
|
24
|
+
document does the same, so from a file you can step out into the folder it lives in. Arrow keys
|
|
25
|
+
walk folders, Enter goes in, Esc comes out, ⌘-click opens one in another tab, and the address
|
|
26
|
+
follows you so a reload puts you back where you were.
|
|
27
|
+
- **Open in Finder.** A button on a folder opens that folder in your file manager, and the same
|
|
28
|
+
button in the row above a file points the file out inside its own folder. It works on macOS,
|
|
29
|
+
Windows and Linux; where a system gives k0 no way to do it, the button stays where it is, greyed
|
|
30
|
+
out, with the reason in its tooltip rather than quietly doing nothing.
|
|
31
|
+
- **The `config` button beside the search shows the configuration.** The viewer lists
|
|
32
|
+
documents, which is right until the day you want to check a key in an `.env`. Switch it on and
|
|
33
|
+
`.env` (and `.env.local`, and the rest), `.json`, `.yml`, `.yaml`, `.toml` and `.ini` join the
|
|
34
|
+
listing, the name search and the search inside the text — **including the ones git is told to
|
|
35
|
+
ignore**, because an `.env` is ignored by definition and it is the file you came for. Code stays
|
|
36
|
+
out either way. The switch is remembered, per browser.
|
|
37
|
+
- **Configuration opens as the kind of thing it is.** A **JSON** file becomes a tree that folds,
|
|
38
|
+
coloured by type, with the first two levels open — and with **its own search box**, because the
|
|
39
|
+
browser's find does not look inside a closed branch: on a folded tree ⌘F says "not found" for
|
|
40
|
+
something three lines away. This one counts what it found and opens the branches holding it. An
|
|
41
|
+
**.env** becomes a table of names and values, comments kept. **YAML, TOML and INI** keep their
|
|
42
|
+
shape, with keys, values and comments told apart by colour.
|
|
43
|
+
- **Configuration and notes can be edited here, with a Save button.** The pencil in the row above
|
|
44
|
+
a document turns it into a text box as tall as the pane — not a small window over it, because a
|
|
45
|
+
README does not fit in one. ⌘S saves, Cancel goes back to what is on disk. Only `.env` and the
|
|
46
|
+
other configuration, `.md` and `.txt`: no code, no PDFs, no images, and no creating or deleting
|
|
47
|
+
files. If the file **changed on disk while you were editing it** — a session wrote it, or you
|
|
48
|
+
did, in another window — the save stops and says so, and what you typed stays exactly where it
|
|
49
|
+
is. k0 will not write over somebody else's work without telling you.
|
|
50
|
+
- **A search whose best answer is switched off says so.** Looking for `.env` with the switch off
|
|
51
|
+
used to give eighty files that merely contain those four letters somewhere in their path, and no
|
|
52
|
+
hint that the one you wanted was a click away. Now a line appears above the results — *3
|
|
53
|
+
configuration files also match — show them* — and it is the button: clicking it turns the switch
|
|
54
|
+
on. It appears only when a hidden file would have come **above** everything on screen, so it is
|
|
55
|
+
not there on the searches where it would only be noise.
|
|
56
|
+
- **The name search looks only inside the file's own name, and underlines what it found.** The
|
|
57
|
+
directory used to be searched too, scattered letters and all, which is how `.env` once turned up
|
|
58
|
+
`v1.0-ROADMAP.md` — three of its four letters came from `.planning/milestones/`, a folder nobody
|
|
59
|
+
was reading, with no way to see that from the row. Now the directory is never searched, and the
|
|
60
|
+
letters that matched are underlined right in the name: `aud`it-`rep`ort reads as the two words
|
|
61
|
+
you meant. Neighbouring letters are underlined as one stretch, not one by one. The mark is a
|
|
62
|
+
line and not the yellow used for the text search, so the two answers stay apart: yellow means
|
|
63
|
+
the word is inside the file, a line means it is in the name.
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
|
|
67
|
+
- **On a card, the git mark sits against the right edge** instead of hanging off the end of the
|
|
68
|
+
repository's name, where it landed in a different place on every note. It is now the same shape
|
|
69
|
+
as the heading of a column, so a row of cards has a line you can read down. A long repository
|
|
70
|
+
name is cut with an ellipsis rather than pushing the mark about.
|
|
71
|
+
- Nothing a user can see: the coverage floor moves up to 87% of lines and 85% of branches, which
|
|
72
|
+
is where the two new modules leave it.
|
|
73
|
+
|
|
74
|
+
## [0.3.0]
|
|
75
|
+
|
|
76
|
+
k0 could already tell you what each session was costing you in memory, and `Close` on a card was
|
|
77
|
+
already how you gave it back. This version is k0 remembering to do that for you: a terminal left
|
|
78
|
+
untouched all day is shut by itself, and the card it belongs to stays exactly where it was, ready
|
|
79
|
+
to be picked up again. It is careful about which ones — never one that is working, never one with
|
|
80
|
+
a question on screen — and it is the first thing in k0 you can change without changing k0, in a
|
|
81
|
+
settings file that writes itself.
|
|
82
|
+
|
|
83
|
+
### Added
|
|
84
|
+
|
|
85
|
+
- **The terminals you have stopped using close themselves, and the work stays.** A session left
|
|
86
|
+
open goes on costing memory whether or not anybody is looking at it — the `claude` process, the
|
|
87
|
+
MCP servers it started, the browsers those opened — and `Close` on a card has always been the
|
|
88
|
+
cure. Now k0 remembers to do it for you: **after twelve hours with nothing happening, a yellow
|
|
89
|
+
card's terminal is shut and its memory given back**. Nothing is lost. The card stays exactly
|
|
90
|
+
where it is with the status it had, and **Resume** picks the conversation up where it was, the
|
|
91
|
+
same as it always did. Only yellow — *Your turn* — is ever touched: a card that is **working**
|
|
92
|
+
is somebody mid-thought, and a card with a **question or a plan on screen** would lose the very
|
|
93
|
+
thing it was showing you, because a dialog that is open has not been written to the transcript
|
|
94
|
+
yet. A session sitting in a **shell** is left alone too — there may be a command of yours
|
|
95
|
+
running in it, and k0 cannot see what. A window you were using an hour ago is never closed,
|
|
96
|
+
whatever the card's history says: every clock k0 has for that session is consulted and the
|
|
97
|
+
newest one wins.
|
|
98
|
+
- **A card says who closed it, and the top bar says the closing is on.** Where a card used to read
|
|
99
|
+
*session closed* it now reads *closed automatically* if it was k0 that tidied it away — same
|
|
100
|
+
italic, same place, one word different. And the memory chip at the end of the top bar carries the
|
|
101
|
+
hours in lighter type: **RAM 79% · CPU 19% · closes at 8h**, with the whole sentence on hover.
|
|
102
|
+
That is where it belongs, because that is where the memory is already being talked about, and
|
|
103
|
+
something that closes your windows for you should never be a surprise. Switched off, the chip
|
|
104
|
+
says nothing rather than *0h* — which would read as "closes immediately" — and the hover says so
|
|
105
|
+
instead.
|
|
106
|
+
- **Settings, in a file that is the list of them.** `~/.k0/config.json` — on Windows
|
|
107
|
+
`%LOCALAPPDATA%\k0\config.json` — written out on the first run with every setting already in it
|
|
108
|
+
at its default, so opening it is how you find out what there is to change. It sits beside the
|
|
109
|
+
board rather than inside the app, so an update cannot throw it away, and k0 picks up a change by
|
|
110
|
+
itself without anything being restarted. `closeIdleTerminalsAfterHours` is the first entry:
|
|
111
|
+
**set it to `0` and the closing above never happens**. There is no settings page on the board and
|
|
112
|
+
there is not going to be one — the board is for what changes during a working day —
|
|
113
|
+
so `k0-board doctor` prints the whole list too, with what is in force and where the file is.
|
|
114
|
+
|
|
115
|
+
### Fixed
|
|
116
|
+
|
|
117
|
+
- **Resuming a session no longer types the old prompt back in.** A card's prompt belongs to the
|
|
118
|
+
moment the session starts; on **Resume** the conversation had already answered it hours ago, and
|
|
119
|
+
putting it back under the cursor was at best something stale in the way — at worst it was sent,
|
|
120
|
+
because where pasting is not available k0 types it instead. Start is unchanged.
|
|
121
|
+
|
|
9
122
|
## [0.2.0]
|
|
10
123
|
|
|
11
124
|
k0 could always tell you how the work stood. This version lets it tell you whether the work is
|
|
@@ -226,7 +339,9 @@ here is everything that had to change for it to be somebody else's tool as well.
|
|
|
226
339
|
- The personal fixtures k0 grew up on. `test/clean.test.mjs` now fails the build if any of them
|
|
227
340
|
come back.
|
|
228
341
|
|
|
229
|
-
[Unreleased]: https://github.com/alessio-ragni/k0-board/compare/v0.
|
|
342
|
+
[Unreleased]: https://github.com/alessio-ragni/k0-board/compare/v0.3.0...HEAD
|
|
343
|
+
[0.3.0]: https://github.com/alessio-ragni/k0-board/compare/v0.2.0...v0.3.0
|
|
344
|
+
[0.2.0]: https://github.com/alessio-ragni/k0-board/compare/v0.1.2...v0.2.0
|
|
230
345
|
[0.1.2]: https://github.com/alessio-ragni/k0-board/compare/v0.1.1...v0.1.2
|
|
231
346
|
[0.1.1]: https://github.com/alessio-ragni/k0-board/compare/v0.1.0...v0.1.1
|
|
232
347
|
[0.1.0]: https://github.com/alessio-ragni/k0-board/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -133,7 +133,7 @@ Nothing on this list happens before you have seen it and said yes. Everything on
|
|
|
133
133
|
| What | Where | Why |
|
|
134
134
|
|---|---|---|
|
|
135
135
|
| A copy of k0 | `~/.k0/app` | so the service does not point at a temporary npx cache |
|
|
136
|
-
| Your board, logs and cache | `~/.k0/` | `%LOCALAPPDATA%\k0` on Windows |
|
|
136
|
+
| Your board, settings, logs and cache | `~/.k0/` | `%LOCALAPPDATA%\k0` on Windows |
|
|
137
137
|
| A service that starts at login | LaunchAgents · systemd user unit · Task Scheduler | so k0 is simply there |
|
|
138
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 |
|
|
139
139
|
| **macOS only** — one key binding | your Terminal profile (backed up first) | so Shift+Enter starts a new line |
|
|
@@ -200,8 +200,10 @@ k0 makes **no network requests**. Not one, not even for a font.
|
|
|
200
200
|
- **double click** — brings that session's terminal back to the front, even if you had minimised
|
|
201
201
|
it. It is what stops you losing track of which window is which.
|
|
202
202
|
|
|
203
|
-
- **Resume** — appears once
|
|
204
|
-
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.
|
|
205
207
|
|
|
206
208
|
- **Done** — closes the work **and its terminal**, with no questions: undo it with **Reopen**, and
|
|
207
209
|
pick the conversation up again with **Resume**. It is not on backlog cards: they never started.
|
|
@@ -396,12 +398,12 @@ yet — a **lens** takes its place, so every card and every column still leads i
|
|
|
396
398
|
|
|
397
399
|
Files on the left, what they say on the right, and a divider in between that you can drag.
|
|
398
400
|
|
|
399
|
-
**
|
|
400
|
-
real repository code is 95% of the files, and among two thousand `.tsx` files the document you
|
|
401
|
+
**Documents are what is listed** — markdown, html, text, PDF, Word: things you read and print. In
|
|
402
|
+
a real repository code is 95% of the files, and among two thousand `.tsx` files the document you
|
|
401
403
|
were after cannot be found. This closes nothing off: a code file reached by a link inside a
|
|
402
|
-
document still opens, and so do images.
|
|
404
|
+
document still opens, and so do images — and the configuration is one switch away, below.
|
|
403
405
|
|
|
404
|
-
|
|
406
|
+
At the top come the **folders**, then the files, in three groups:
|
|
405
407
|
|
|
406
408
|
- **Changed** — what this session has touched: files still hanging in the working tree plus the
|
|
407
409
|
ones inside commits made since it started.
|
|
@@ -409,19 +411,91 @@ The listing is flat, in three groups:
|
|
|
409
411
|
no git.
|
|
410
412
|
- **All files** — everything else, in path order.
|
|
411
413
|
|
|
414
|
+
### Searching
|
|
415
|
+
|
|
412
416
|
The search at the top looks in two places and keeps them apart:
|
|
413
417
|
|
|
414
|
-
- **Names** — not the exact word, but the letters you type in the order you type them
|
|
415
|
-
finds `docs/audit-report.md`. It filters as you type, asking the
|
|
418
|
+
- **Names** — not the exact word, but the letters you type in the order you type them, **inside
|
|
419
|
+
the file's own name**: `audrep` finds `docs/audit-report.md`. It filters as you type, asking the
|
|
420
|
+
server nothing. The directory is never searched — it used to be, and `.env` would turn up a
|
|
421
|
+
`v1.0-ROADMAP.md` that merely had those four letters scattered across its folder, three of them
|
|
422
|
+
in a name nobody was reading. The letters that matched are **underlined**, so every row says why
|
|
423
|
+
it is there: `aud`it-`rep`ort reads as the two words you meant.
|
|
416
424
|
- **In the text** — inside the documents, with the line the word appears on and the word
|
|
417
|
-
highlighted. Because what you remember about a file is what was in it, not what it was
|
|
418
|
-
Accents do not need typing — `cafe` finds `café`.
|
|
425
|
+
highlighted in yellow. Because what you remember about a file is what was in it, not what it was
|
|
426
|
+
called. Accents do not need typing — `cafe` finds `café`.
|
|
427
|
+
|
|
428
|
+
The two marks are different on purpose: a line under the letters means they are in the file's
|
|
429
|
+
name, yellow means the word was found inside the file.
|
|
419
430
|
|
|
420
431
|
There is no index. The documents are read every time: a few hundred files of a few KB, all
|
|
421
432
|
already in the operating system's cache. An index would cost more and go stale.
|
|
422
433
|
|
|
423
434
|
`/` puts the cursor in the search, arrows scroll, Enter opens, Esc clears.
|
|
424
435
|
|
|
436
|
+
### Going into a folder
|
|
437
|
+
|
|
438
|
+
Click a folder and the listing narrows to it: the folders inside it first, then its files. At the
|
|
439
|
+
top the path is written out one piece at a time, and **every piece is a way back up** — from
|
|
440
|
+
`docs/backlog/handover` you get to `docs` in one click, and to the whole repository by clicking
|
|
441
|
+
the repository's name. The path above an open document works the same way, so from a file you can
|
|
442
|
+
step into the folder it lives in.
|
|
443
|
+
|
|
444
|
+
The arrows walk folders like anything else and Enter goes in; Esc comes back out. A folder is a
|
|
445
|
+
real link, so ⌘/Ctrl-click opens it in another tab, and the address follows you: reload it and
|
|
446
|
+
you are still where you were.
|
|
447
|
+
|
|
448
|
+
Next to it is a button that hands the folder to your **file manager** — the Finder on a Mac,
|
|
449
|
+
Explorer on Windows, whatever your desktop uses on Linux. Every file has the same button in the
|
|
450
|
+
row above it, and there it points the file out inside its folder rather than opening it. On a
|
|
451
|
+
system where k0 has no way to do that, the button stays where it is, greyed out, with the reason
|
|
452
|
+
in its tooltip.
|
|
453
|
+
|
|
454
|
+
### The `config` switch, beside the search
|
|
455
|
+
|
|
456
|
+
The viewer lists documents. The one thing that was missing from that is **configuration**: an
|
|
457
|
+
`.env` you want to check a key in, the `package.json`, a workflow's YAML. They are not documents,
|
|
458
|
+
and in a repository of prose they would be noise — so they live behind a switch, the **`config`**
|
|
459
|
+
button to the right of the search box.
|
|
460
|
+
|
|
461
|
+
Switch it on and `.env` (and `.env.local`, and the rest of them), `.json`, `.yml`, `.yaml`,
|
|
462
|
+
`.toml` and `.ini` join the listing, the name search and the search inside the text. **Including
|
|
463
|
+
the ones git is told to ignore** — an `.env` is ignored by definition, and it is the file you came
|
|
464
|
+
for. Code does not join: `.js`, `.py`, `.css` stay out either way.
|
|
465
|
+
|
|
466
|
+
The switch is remembered, per browser. It is not a search, it is a way of working.
|
|
467
|
+
|
|
468
|
+
And if you forget it is off, the search says so: when a hidden configuration file would have come
|
|
469
|
+
**above** everything on screen — searching `.env` in a repository that has one — a line appears at
|
|
470
|
+
the top of the results saying how many, and clicking it turns the switch on. Only then: a hidden
|
|
471
|
+
file that merely matches somewhere does not raise it, or the line would be there on every search
|
|
472
|
+
and you would stop seeing it.
|
|
473
|
+
|
|
474
|
+
Each of them opens as the kind of thing it is:
|
|
475
|
+
|
|
476
|
+
- a **JSON** file becomes a tree that folds, coloured by type, with the first two levels open;
|
|
477
|
+
- an **.env** becomes a table of names and values, comments and all;
|
|
478
|
+
- **YAML, TOML and INI** keep their shape, with keys, values and comments told apart by colour.
|
|
479
|
+
|
|
480
|
+
A JSON tree has its **own search box**, and that is not a duplicate of the browser's: the
|
|
481
|
+
browser's find does not look inside a branch that is closed, so on a folded tree ⌘F says "not
|
|
482
|
+
found" for something three lines away. This one counts what it found and opens the branches
|
|
483
|
+
holding it.
|
|
484
|
+
|
|
485
|
+
### Editing a file
|
|
486
|
+
|
|
487
|
+
Configuration and notes can be changed from here: `.env` and the rest of the configuration above,
|
|
488
|
+
plus `.md` and `.txt`. Click the **pencil** in the row above the document and the file becomes a
|
|
489
|
+
text box as tall as the pane — not a small window over it, because a README does not fit in one.
|
|
490
|
+
**Save** (or ⌘/Ctrl+S) writes it; **Cancel** goes back to what is on disk.
|
|
491
|
+
|
|
492
|
+
Nothing else is editable: no code, no PDFs, no images, and no creating or deleting files. That is
|
|
493
|
+
what you opened the repository with Claude for.
|
|
494
|
+
|
|
495
|
+
If the file **changed on disk** while you were editing — a session wrote it, or you did, in
|
|
496
|
+
another window — the save stops and says so, and what you typed stays where it is. k0 will not
|
|
497
|
+
quietly write over somebody else's work.
|
|
498
|
+
|
|
425
499
|
### Pasting a chunk of chat
|
|
426
500
|
|
|
427
501
|
There is a way to reach files that does not go through searching for them. When a session ends,
|
|
@@ -512,6 +586,47 @@ Reading the process table costs little on a calm machine and a lot on a struggli
|
|
|
512
586
|
exactly when you look at it. So it is sampled every three seconds, and **only while the board is
|
|
513
587
|
open**.
|
|
514
588
|
|
|
589
|
+
### And the ones you forget, k0 closes for you
|
|
590
|
+
|
|
591
|
+
The paragraph above assumes you noticed. Most of the time nobody does: the window that is costing
|
|
592
|
+
you a gigabyte is the one you stopped thinking about yesterday. So **after twelve hours with
|
|
593
|
+
nothing happening, k0 does that `Close` itself** — the session is stopped, its window goes, and the
|
|
594
|
+
memory comes back.
|
|
595
|
+
|
|
596
|
+
Nothing is lost by it. The card stays exactly where it is with the status it had, and **Resume**
|
|
597
|
+
picks the conversation up where it was. That is the whole reason this is safe: a closed terminal
|
|
598
|
+
has never been a lost session.
|
|
599
|
+
|
|
600
|
+
**Only yellow is ever touched** — *Your turn*, the ball in your court and nothing happening. The
|
|
601
|
+
rest is left alone, and each for its own reason:
|
|
602
|
+
|
|
603
|
+
- **Working** and **Planning** are somebody mid-thought. Stopping one is not memory saved, it is
|
|
604
|
+
work lost — the same rule the `Close` link has always followed.
|
|
605
|
+
- **Needs answer** and **Needs approval** would lose the very thing they were showing you. A
|
|
606
|
+
question and a finished plan are drawn by the terminal and are not written to the transcript
|
|
607
|
+
until they are answered, so closing that window throws the question away.
|
|
608
|
+
- A session sitting in a **shell** is left alone as well. That is a shell you dropped into, and
|
|
609
|
+
there may be a command of yours running in it; k0 cannot see what it is, so k0 does not touch it.
|
|
610
|
+
On the board it reads as yellow like any other, which is exactly why this one has to be said.
|
|
611
|
+
|
|
612
|
+
And a window you were using an hour ago is never closed, whatever the card's history says. k0 has
|
|
613
|
+
four clocks for a session — two of its own and two Claude Code keeps — and **the newest one wins**.
|
|
614
|
+
Being wrong in that direction leaves a window open until tomorrow; being wrong the other way closes
|
|
615
|
+
a terminal you were about to go back to.
|
|
616
|
+
|
|
617
|
+
A card k0 tidied away says so: where one you closed yourself reads *session closed*, this one reads
|
|
618
|
+
*closed automatically*. Same italic, same place, one word different.
|
|
619
|
+
|
|
620
|
+
And the memory chip at the end of the top bar says it is watching, and from when: **RAM 79% · CPU
|
|
621
|
+
19% · closes at 8h**, in lighter type than the numbers beside it. That is the whole announcement,
|
|
622
|
+
and it is on that chip rather than anywhere else because that is where the memory is already being
|
|
623
|
+
talked about — something that closes your windows for you should never be a surprise. Hover for
|
|
624
|
+
the sentence. Switched off, the chip says nothing at all rather than *0h*, which would read as
|
|
625
|
+
"closes immediately", and the hover is what tells you it is off.
|
|
626
|
+
|
|
627
|
+
Twelve hours is what it does if you never say otherwise. `closeIdleTerminalsAfterHours` in the
|
|
628
|
+
settings file changes it, and **`0` switches it off** — see below.
|
|
629
|
+
|
|
515
630
|
---
|
|
516
631
|
|
|
517
632
|
## Filling the board with what you have already done
|
|
@@ -722,6 +837,38 @@ Two side effects of the Cmd+V k0 uses to write your prompt, worth knowing:
|
|
|
722
837
|
|
|
723
838
|
---
|
|
724
839
|
|
|
840
|
+
## Settings
|
|
841
|
+
|
|
842
|
+
There is no settings page on the board, and there is not going to be one. The board is for what
|
|
843
|
+
changes during a working day; a number you set once and then never look at again is the opposite of
|
|
844
|
+
that, and putting it on screen would cost the one page that has to stay readable in exchange for
|
|
845
|
+
nothing.
|
|
846
|
+
|
|
847
|
+
So the settings are a file, and **the file is the list**:
|
|
848
|
+
|
|
849
|
+
```
|
|
850
|
+
~/.k0/config.json macOS and Linux
|
|
851
|
+
%LOCALAPPDATA%\k0\config.json Windows
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
k0 writes it on the first run with **every setting already in it at its default**, so opening it is
|
|
855
|
+
how you find out what there is to change. It sits beside the board rather than inside the app —
|
|
856
|
+
`npx` unpacks into a cache npm is free to wipe, and `k0-board install` overwrites the app
|
|
857
|
+
directory, so a setting kept in there would not survive an update. Change it and k0 picks it up by
|
|
858
|
+
itself on the next round: nothing to restart.
|
|
859
|
+
|
|
860
|
+
| | |
|
|
861
|
+
|---|---|
|
|
862
|
+
| `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. |
|
|
863
|
+
|
|
864
|
+
Delete a line to go back to its default; a file k0 cannot read is ignored altogether and the
|
|
865
|
+
defaults hold, rather than k0 refusing to start over a stray comma.
|
|
866
|
+
|
|
867
|
+
**`k0-board doctor` prints the same list**, with the value actually in force and where the file is,
|
|
868
|
+
for the times you would rather ask than go and look.
|
|
869
|
+
|
|
870
|
+
---
|
|
871
|
+
|
|
725
872
|
## How it is put together
|
|
726
873
|
|
|
727
874
|
No dependencies, nothing to compile. Change a file, reload the page.
|
|
@@ -744,7 +891,11 @@ server/
|
|
|
744
891
|
guard.js who is allowed to talk to this server at all: the Host and the Origin
|
|
745
892
|
db.js SQLite (node:sqlite): the card, session_event, pref and dev_server tables —
|
|
746
893
|
docs/database.md
|
|
747
|
-
paths.js where the board, the logs and the cache live
|
|
894
|
+
paths.js where the board, the logs, the settings and the cache live
|
|
895
|
+
settings.js the few things you can change, and the file that is the list of them —
|
|
896
|
+
~/.k0/config.json, written out with everything in it and re-read when it changes
|
|
897
|
+
idle.js which terminals have sat still long enough to be closed and given back. Only the
|
|
898
|
+
deciding: it kills nothing, so every rule in it can be proved without a machine
|
|
748
899
|
watcher.js reads Claude Code's own files and derives the statuses; also renames a session
|
|
749
900
|
git.js the only one that talks to git: what is committed, what is pushed, whose it is,
|
|
750
901
|
and — for the ChangeLog — what the commits actually said
|
|
@@ -758,7 +909,8 @@ server/
|
|
|
758
909
|
mode.js the four modes: how awake to keep the machine, and whether the text goes large
|
|
759
910
|
projects.js the repositories, in the order you last used them
|
|
760
911
|
sessions.js digs already-lived sessions out of the transcripts, to import as cards
|
|
761
|
-
files.js the only one that reads the projects' disk
|
|
912
|
+
files.js the only one that reads the projects' disk — and the only one that writes back
|
|
913
|
+
into it: what is there, what changed, what it says, and the one small write
|
|
762
914
|
machine.js the only one that looks at processes and memory: what it all costs, and who is costing it
|
|
763
915
|
pdf.js the document on paper, printed by a headless browser
|
|
764
916
|
web/ the three pages (html, css, js served exactly as they are)
|
|
@@ -774,6 +926,8 @@ web/ the three pages (html, css, js served exactly as they are)
|
|
|
774
926
|
fuzzy.js searching the names: the letters you type, in the order you type them
|
|
775
927
|
mentions.js which of a repository's files a piece of text names
|
|
776
928
|
refs.js what a name written inside a document points at, and which of the nine READMEs
|
|
929
|
+
json.js a JSON file as a tree that folds, and the search that opens the right branches
|
|
930
|
+
conf.js an .env as the table it always was, and YAML/TOML/INI with their parts told apart
|
|
777
931
|
.claude/skills/
|
|
778
932
|
k0-import/ the skill that fills the board with sessions you have already had
|
|
779
933
|
k0-changelog/ the one that turns the facts of a stretch of work into something readable. k0
|
|
@@ -792,6 +946,12 @@ test/ npm test — Node's own runner, no framework. See docs/testing.
|
|
|
792
946
|
|
|
793
947
|
### What it reads, and what it never does
|
|
794
948
|
|
|
949
|
+
k0 reads your repositories. It writes into one of them in exactly one place: the pencil in the
|
|
950
|
+
file viewer, on a configuration file or a note that is already there. That write is deliberately
|
|
951
|
+
narrow — no code, no new files, no deletions — it carries the file's own permissions across, it
|
|
952
|
+
lands as a rename so an interrupted save leaves the old file whole, and it refuses outright if the
|
|
953
|
+
file has changed on disk since the page read it.
|
|
954
|
+
|
|
795
955
|
k0 does not emulate a terminal and installs no hooks. It reads two things Claude Code already
|
|
796
956
|
writes for itself:
|
|
797
957
|
|
|
@@ -905,11 +1065,12 @@ The full picture, and how to report a problem privately, is in [SECURITY.md](SEC
|
|
|
905
1065
|
|
|
906
1066
|
## What is not here yet
|
|
907
1067
|
|
|
908
|
-
Checkboxes inside a card. In the viewer
|
|
909
|
-
from a link, but it is not browsable and the text search does not look at it.
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1068
|
+
Checkboxes inside a card. In the viewer the listing is documents and configuration: code opens if
|
|
1069
|
+
you arrive at it from a link, but it is not browsable and the text search does not look at it.
|
|
1070
|
+
Only configuration and notes can be edited, one file at a time — nothing is diffed, no file is
|
|
1071
|
+
created or deleted, and you cannot talk to them; for that you open the repository with Claude.
|
|
1072
|
+
Sessions opened by hand outside k0 are picked up with `/k0-import`, on request: nothing notices
|
|
1073
|
+
them by itself yet.
|
|
913
1074
|
|
|
914
1075
|
## Contributing
|
|
915
1076
|
|
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')
|
|
@@ -417,9 +418,24 @@ async function doctor() {
|
|
|
417
418
|
['read the kernel memory-pressure verdict', capabilities.metrics.pressure],
|
|
418
419
|
['start at login', capabilities.service.autostart],
|
|
419
420
|
['show a tray icon', capabilities.tray],
|
|
421
|
+
['show a file in the file manager', capabilities.revealInFileManager],
|
|
422
|
+
['open a folder in the file manager', capabilities.openInFileManager],
|
|
420
423
|
]
|
|
421
424
|
for (const [what, yes] of rows) (yes ? ok : warn)(what)
|
|
422
425
|
|
|
426
|
+
// The settings, in full, with what is actually in force. k0 has no settings page and is not
|
|
427
|
+
// getting one, so this — and the file itself — is where the list lives. Printing every key
|
|
428
|
+
// rather than only the ones somebody has changed is the point: it is an inventory.
|
|
429
|
+
step('Settings')
|
|
430
|
+
const conf = settings.status()
|
|
431
|
+
say(dim(` ${conf.path}${conf.exists ? '' : ' (not written yet — the defaults are in force)'}`))
|
|
432
|
+
if (conf.broken) warn('this file could not be read: it is being ignored and the defaults are in force')
|
|
433
|
+
for (const [key, value] of Object.entries(conf.values)) {
|
|
434
|
+
const dflt = settings.DEFAULTS[key]
|
|
435
|
+
const off = key === 'closeIdleTerminalsAfterHours' && value === 0
|
|
436
|
+
say(` ${bold(key)} ${off ? 'off' : value}${value === dflt ? dim(' (default)') : dim(` (default ${dflt})`)}`)
|
|
437
|
+
}
|
|
438
|
+
|
|
423
439
|
const explained = Object.entries(notes)
|
|
424
440
|
if (explained.length) {
|
|
425
441
|
step('Why not')
|
package/package.json
CHANGED
package/platform/contract.js
CHANGED
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
* @property {object} service
|
|
41
41
|
* @property {boolean} service.autostart can register a service that starts at login
|
|
42
42
|
* @property {boolean} tray a menu bar / system tray icon is available
|
|
43
|
-
* @property {boolean} revealInFileManager
|
|
43
|
+
* @property {boolean} revealInFileManager can show a file selected in the file manager
|
|
44
|
+
* @property {boolean} openInFileManager can open a directory in the file manager
|
|
44
45
|
*/
|
|
45
46
|
|
|
46
47
|
/**
|
|
@@ -55,6 +56,7 @@ export const NO_CAPABILITIES = {
|
|
|
55
56
|
service: { autostart: false },
|
|
56
57
|
tray: false,
|
|
57
58
|
revealInFileManager: false,
|
|
59
|
+
openInFileManager: false,
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
/**
|
|
@@ -133,6 +135,10 @@ export const NO_CAPABILITIES = {
|
|
|
133
135
|
/**
|
|
134
136
|
* @typedef {object} ShellAdapter
|
|
135
137
|
* @property {(absPath: string) => Promise<unknown>} revealInFileManager
|
|
138
|
+
* Shows the file in its own folder, selected. A folder handed to this gets selected in its
|
|
139
|
+
* parent, which is the neighbouring thing and not the same one.
|
|
140
|
+
* @property {(absDir: string) => Promise<unknown>} openInFileManager
|
|
141
|
+
* Opens the directory itself, so you are looking at what is inside it.
|
|
136
142
|
* @property {(url: string) => Promise<unknown>} openBrowser
|
|
137
143
|
* @property {() => string|null} findChrome a Chromium-family browser able to print
|
|
138
144
|
* @property {() => Set<string>} homeSkipList directories under $HOME that are never projects
|
package/platform/darwin/index.js
CHANGED
package/platform/darwin/shell.js
CHANGED
|
@@ -7,6 +7,9 @@ const OPEN = () => which('open', ['/usr/bin/open'])
|
|
|
7
7
|
|
|
8
8
|
export const revealInFileManager = (abs) => run(OPEN(), ['-R', abs], { timeout: 4000 })
|
|
9
9
|
|
|
10
|
+
/** Without `-R` the Finder opens the directory instead of picking it out in the one above. */
|
|
11
|
+
export const openInFileManager = (abs) => run(OPEN(), [abs], { timeout: 4000 })
|
|
12
|
+
|
|
10
13
|
export const openBrowser = (url) => run(OPEN(), [url], { timeout: 4000 })
|
|
11
14
|
|
|
12
15
|
/**
|
|
@@ -56,4 +59,4 @@ export function findClaude() {
|
|
|
56
59
|
return claudeBin
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
export const capabilities = { revealInFileManager: true }
|
|
62
|
+
export const capabilities = { revealInFileManager: true, openInFileManager: true }
|
package/platform/index.js
CHANGED
package/platform/linux/index.js
CHANGED
|
@@ -24,6 +24,7 @@ export const capabilities = {
|
|
|
24
24
|
service: service.capabilities(),
|
|
25
25
|
tray: !!which('python3'),
|
|
26
26
|
revealInFileManager: shell.capabilities().revealInFileManager,
|
|
27
|
+
openInFileManager: shell.capabilities().openInFileManager,
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export const notes = {
|
|
@@ -61,6 +62,16 @@ export const notes = {
|
|
|
61
62
|
'Neither ss nor lsof was found, so k0 cannot see which port a dev server is listening ' +
|
|
62
63
|
'on. Install iproute2 (for ss) and the globe will know where to send you.',
|
|
63
64
|
}),
|
|
65
|
+
...(capabilities.openInFileManager
|
|
66
|
+
? {}
|
|
67
|
+
: {
|
|
68
|
+
revealInFileManager:
|
|
69
|
+
'xdg-open was not found, so k0 has no way to hand a file or a folder to your file ' +
|
|
70
|
+
'manager. Install xdg-utils and the button will work.',
|
|
71
|
+
openInFileManager:
|
|
72
|
+
'xdg-open was not found, so k0 has no way to hand a file or a folder to your file ' +
|
|
73
|
+
'manager. Install xdg-utils and the button will work.',
|
|
74
|
+
}),
|
|
64
75
|
...(capabilities.servers.adopt
|
|
65
76
|
? {}
|
|
66
77
|
: {
|
package/platform/linux/shell.js
CHANGED
|
@@ -31,6 +31,15 @@ export async function revealInFileManager(abs) {
|
|
|
31
31
|
if (open) await runQuiet(open, [path.dirname(abs)])
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Opening a directory needs none of the dance above: every desktop that has a file manager at
|
|
36
|
+
* all hands a directory to it through xdg-open.
|
|
37
|
+
*/
|
|
38
|
+
export async function openInFileManager(abs) {
|
|
39
|
+
const open = XDG_OPEN()
|
|
40
|
+
if (open) await runQuiet(open, [abs])
|
|
41
|
+
}
|
|
42
|
+
|
|
34
43
|
export const openBrowser = (url) => run(XDG_OPEN(), [url], { timeout: 4000 })
|
|
35
44
|
|
|
36
45
|
const CHROME_CANDIDATES = [
|
|
@@ -94,4 +103,4 @@ export function findClaude() {
|
|
|
94
103
|
return claudeBin
|
|
95
104
|
}
|
|
96
105
|
|
|
97
|
-
export const capabilities = () => ({ revealInFileManager: !!XDG_OPEN() })
|
|
106
|
+
export const capabilities = () => ({ revealInFileManager: !!XDG_OPEN(), openInFileManager: !!XDG_OPEN() })
|
package/platform/win32/index.js
CHANGED
package/platform/win32/shell.js
CHANGED
|
@@ -8,6 +8,9 @@ const EXPLORER = () => which('explorer') || 'explorer.exe'
|
|
|
8
8
|
/** `explorer /select,` opens the folder with the file already highlighted. */
|
|
9
9
|
export const revealInFileManager = (abs) => runQuiet(EXPLORER(), [`/select,${path.normalize(abs)}`])
|
|
10
10
|
|
|
11
|
+
/** The same command without `/select,` opens the folder itself. */
|
|
12
|
+
export const openInFileManager = (abs) => runQuiet(EXPLORER(), [path.normalize(abs)])
|
|
13
|
+
|
|
11
14
|
export const openBrowser = (url) => runQuiet('cmd', ['/c', 'start', '', url])
|
|
12
15
|
|
|
13
16
|
const programFiles = [
|
|
@@ -61,4 +64,4 @@ export function findClaude() {
|
|
|
61
64
|
return claudeBin
|
|
62
65
|
}
|
|
63
66
|
|
|
64
|
-
export const capabilities = () => ({ revealInFileManager: true })
|
|
67
|
+
export const capabilities = () => ({ revealInFileManager: true, openInFileManager: true })
|
package/server/db.js
CHANGED
|
@@ -73,6 +73,7 @@ for (const [col, decl] of [
|
|
|
73
73
|
['imported_at', 'INTEGER'],
|
|
74
74
|
['work_path', 'TEXT'],
|
|
75
75
|
['head_at_start', 'TEXT'],
|
|
76
|
+
['auto_closed', 'INTEGER NOT NULL DEFAULT 0'],
|
|
76
77
|
]) {
|
|
77
78
|
const has = db.prepare('SELECT 1 FROM pragma_table_info(?) WHERE name = ?').get('card', col)
|
|
78
79
|
if (!has) db.exec(`ALTER TABLE card ADD COLUMN ${col} ${decl}`)
|
|
@@ -214,6 +215,16 @@ export function setWorkPath(id, workPath) {
|
|
|
214
215
|
return getCard(id)
|
|
215
216
|
}
|
|
216
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Who shut this session's terminal: you, or k0 tidying up after a long silence. The card says so
|
|
220
|
+
* in one word, and the word has to be right — a window you closed yourself two minutes ago must
|
|
221
|
+
* not claim k0 did it. Does not touch `updated_at`: this is not work on the card.
|
|
222
|
+
*/
|
|
223
|
+
export function setAutoClosed(id, on) {
|
|
224
|
+
db.prepare('UPDATE card SET auto_closed = ? WHERE id = ?').run(on ? 1 : 0, id)
|
|
225
|
+
return getCard(id)
|
|
226
|
+
}
|
|
227
|
+
|
|
217
228
|
/** Where HEAD was when the session started: everything after it is this session's doing. */
|
|
218
229
|
export function setHeadAtStart(id, sha) {
|
|
219
230
|
db.prepare('UPDATE card SET head_at_start = ? WHERE id = ?').run(sha || null, id)
|