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 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.1.2...HEAD
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 you have closed the terminal: it reopens the same conversation exactly
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
- **Only documents are listed** — markdown, html, text, PDF, Word: things you read and print. In a
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
- The listing is flat, in three groups:
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: `audrep`
415
- finds `docs/audit-report.md`. It filters as you type, asking the server nothing.
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 called.
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: what is there, what changed, what it says
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 only documents are listed: code opens if you arrive at it
909
- from a link, but it is not browsable and the text search does not look at it. Files are read only:
910
- they are not edited, not diffed, and you cannot talk to them — for that you open the repository
911
- with Claude. Sessions opened by hand outside k0 are picked up with `/k0-import`, on request:
912
- nothing notices them by itself yet.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "k0-board",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "A board of sticky notes that drives your Claude Code sessions",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -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
@@ -18,6 +18,7 @@ export const capabilities = {
18
18
  service: service.capabilities,
19
19
  tray: true,
20
20
  revealInFileManager: true,
21
+ openInFileManager: true,
21
22
  }
22
23
 
23
24
  /**
@@ -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
@@ -65,6 +65,7 @@ const unsupported = {
65
65
  },
66
66
  shell: {
67
67
  revealInFileManager: async () => {},
68
+ openInFileManager: async () => {},
68
69
  openBrowser: async () => {},
69
70
  findChrome: () => null,
70
71
  homeSkipList: () => new Set(),
@@ -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
  : {
@@ -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() })
@@ -24,6 +24,7 @@ export const capabilities = {
24
24
  service: service.capabilities(),
25
25
  tray: hasPowerShell(),
26
26
  revealInFileManager: true,
27
+ openInFileManager: true,
27
28
  }
28
29
 
29
30
  export const notes = {
@@ -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)