kankaku 0.6.5 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,6 +41,37 @@ trusted, which a subagent child may not inherit.
41
41
 
42
42
  To try it without installing: `pi -e /absolute/path/to/kankaku`.
43
43
 
44
+ ## Quick start
45
+
46
+ Once installed, kankaku records every prompt on its own; there is nothing
47
+ to start. Inside pi's TUI, type `/kankaku` to open the panel, the one
48
+ place everything is managed from:
49
+
50
+ ```
51
+ ╭─ >_ kankaku ─────────────────────────────────────────╮
52
+ │ │
53
+ │ → Target Billing client, project, hub task │
54
+ │ Report Today/all totals, tasks, sessions │
55
+ │ Sync Status, sync now, sync all, backfill │
56
+ │ Export Write today's or every task as csv │
57
+ │ Doctor Orphan/uncertain subagent counts │
58
+ │ About Versions, KANKAKU_DIR, hub URL │
59
+ │ │
60
+ │ ↑↓ move · enter open · esc close │
61
+ ╰──────────────────────────────────────────────────────╯
62
+ ```
63
+
64
+ - **Target** is where you pick the client and project the time is billed
65
+ to and, with a hub, the task you are working on right now.
66
+ - **Report** shows today's work, waiting and cost, per task or grouped by
67
+ client or project.
68
+ - **Sync** pushes the consolidated tasks to your hub when one is configured.
69
+
70
+ Every panel action is also a subcommand (`/kankaku tasks`, `/kankaku sync`,
71
+ …) for scripts and headless runs — see "The `/kankaku` command" below. The
72
+ footer clock (`🕒 03:12 · acme`) shows the running prompt's elapsed time
73
+ and billing client while an agent works.
74
+
44
75
  ## Record schema
45
76
 
46
77
  Each line in `worklog.jsonl` is one JSON object:
@@ -687,17 +718,53 @@ the future, this is the signal that would surface it.
687
718
 
688
719
  ## The `/kankaku` command
689
720
 
690
- Run `/kankaku` inside pi to see today's totals (work, waiting, record count)
691
- per role, plus a union-based tasks segment. Each totals line also shows
692
- `cache hit NN%` when tokens were recorded: the share of prompt input tokens
693
- served from the provider's prompt cache, cache reads over input plus cache
694
- reads plus cache writes; the segment is omitted, not shown as `0%`, when no
695
- tokens were recorded. In the interactive TUI the report
696
- is appended to the chat transcript as a durable card that is never sent to
697
- the LLM; without a UI (print or RPC mode) it falls back to a notification.
698
- Arguments are whitespace-separated and order-insensitive:
699
-
700
- - `/kankaku` — today's role totals and tasks segment, each with its estimated cost.
721
+ `/kankaku` with no arguments, run inside pi's TUI, opens an overlay panel
722
+ modelled on pi's own `/settings` (the same pi-tui `SettingsList`/
723
+ `SelectList` widgets, the same keys) from which every kankaku view and
724
+ action is reachable:
725
+
726
+ - **Target** — billing client, project, hub task, and the legacy label
727
+ (see "Billing labels" and "Hub (PocketBase)" below). The Task row lists
728
+ the open/doing hub tasks of the current project; picking one links the
729
+ session, exactly like `/kankaku task pick` below — **session-only**,
730
+ never persisted (see "Linking to a hub task").
731
+ - **Report** — today/all totals, tasks, sessions, clients, and projects:
732
+ the same five views `/kankaku`'s subcommands produce.
733
+ - **Sync** (hub only) — status, sync now, sync all, backfill, and a
734
+ catalog refresh.
735
+ - **Export** — write today's or every task as csv/json.
736
+ - **Doctor** — orphan/uncertain subagent counts and ancestor-detection
737
+ availability.
738
+ - **About** — versions, the resolved `KANKAKU_DIR`, the hub URL, and every
739
+ env-only setting, read-only.
740
+
741
+ Keys: `↑↓` move, `Enter` open a section or select a value, `Esc` or `←` go
742
+ back (or close the panel at the root), `q` close from anywhere. Mouse: the
743
+ footer hints and list rows are clickable, but only in pi's fullscreen
744
+ mode — pi does not dispatch mouse events in its regular (non-fullscreen)
745
+ mode, so there the panel is keyboard-only.
746
+
747
+ Every subcommand below is unchanged, and is exactly what headless (print
748
+ or RPC) mode still uses — `/kankaku` there keeps showing today's totals
749
+ directly, never the panel, since there is no UI to open one in. Run
750
+ `/kankaku <subcommand>` (with arguments) in the TUI to skip the panel and
751
+ go straight to that subcommand's output, exactly as before the panel
752
+ existed.
753
+
754
+ Plain `/kankaku` (headless, or any subcommand below) shows today's totals
755
+ (work, waiting, record count) per role, plus a union-based tasks segment.
756
+ Each totals line also shows `cache hit NN%` when tokens were recorded: the
757
+ share of prompt input tokens served from the provider's prompt cache, cache
758
+ reads over input plus cache reads plus cache writes; the segment is
759
+ omitted, not shown as `0%`, when no tokens were recorded. In the
760
+ interactive TUI the report is appended to the chat transcript as a durable
761
+ card that is never sent to the LLM; without a UI (print or RPC mode) it
762
+ falls back to a notification. Arguments are whitespace-separated and
763
+ order-insensitive:
764
+
765
+ - `/kankaku` (headless only — the TUI opens the panel instead, whose
766
+ Report screen defaults to the same view) — today's role totals and
767
+ tasks segment, each with its estimated cost.
701
768
  - `/kankaku all` — same, but across every record.
702
769
  - `/kankaku tasks` — one line per task (time, union wall/work, cost,
703
770
  subagent count, truncated prompt) for the **current pi session**. Add `all` for
@@ -834,6 +901,12 @@ task view exposes it from the orchestrator record only.
834
901
  The status bar shows `💼 <client> · <project>` (or just `💼 <client>` without
835
902
  a project) in place of the legacy client label, both idle and during a run.
836
903
 
904
+ Mid-session, the `/kankaku` panel's Target screen (see "The `/kankaku`
905
+ command" above) is the interactive way to change the client or project
906
+ without going through `/kankaku target pick`'s picker dialog — it edits the
907
+ same session-level target, applies through the same `SessionTarget`, and
908
+ has its own "Remember" row for `<KANKAKU_DIR>/config.json`.
909
+
837
910
  ### Linking to a hub task
838
911
 
839
912
  `/kankaku task` (or `/kankaku task pick`) links the current session to one
@@ -861,6 +934,12 @@ the task view exposes it from the orchestrator record only, and
861
934
  `formatWorkTargetLabel` appends it to the status-bar/report label as
862
935
  `<client> · <project> › <task title>`.
863
936
 
937
+ The `/kankaku` panel's Target screen's Task row is the interactive
938
+ equivalent of `/kankaku task pick`/`clear`: it lists the same open/doing
939
+ tasks, applies the link through the same `SessionTarget.setTask`, and
940
+ stays just as session-only — picking a task there is never persisted to
941
+ `config.json` either.
942
+
864
943
  ### Caching and offline behaviour
865
944
 
866
945
  The catalog (clients/projects) is cached machine-wide at
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kankaku",
3
- "version": "0.6.5",
3
+ "version": "0.7.1",
4
4
  "description": "pi extension that records agent work time per prompt, excluding waits for the user, with subagent linkage and task/session views",
5
5
  "license": "MIT",
6
6
  "author": "soyunninja",
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Shared line-building for the hub-facing `/kankaku` subcommands (`sync`,
3
+ * `sync all`, `sync status`, `backfill`, `catalog refresh`) and the panel's
4
+ * `sync`/`export` screens (`adapters/panel/screens/sync.ts`), extracted from
5
+ * `kankaku-command.ts` so the subcommands and the panel can never drift —
6
+ * see odd/tasks/kankaku-panel.md P4.
7
+ */
8
+ import type { CatalogSnapshot } from "../ports/catalog.ts";
9
+ import type { SyncCommandDeps } from "./kankaku-command.ts";
10
+ import type { SyncSummary } from "./sync-runner.ts";
11
+
12
+ /**
13
+ * `/kankaku sync status`'s report lines: the watermark, the pending count,
14
+ * how many tasks fell outside this run's revisit window (R3), and the last
15
+ * error, when any. Exact body of `handleSyncCommand`'s `status` branch.
16
+ */
17
+ export function buildSyncStatusLines(status: ReturnType<SyncCommandDeps["status"]>): string[] {
18
+ const { state, pending, staleOutsideWindow } = status;
19
+ const lines = [state?.syncedThrough ? `synced through ${state.syncedThrough}` : "never synced", `pending: ${pending}`];
20
+ if (staleOutsideWindow > 0) {
21
+ lines.push(`${staleOutsideWindow} task(s) never synced fall outside the sync window — run '/kankaku sync all' to upload them`);
22
+ }
23
+ if (state?.lastError) lines.push(`last error: ${state.lastError.message} (at ${state.lastError.at})`);
24
+ return lines;
25
+ }
26
+
27
+ /**
28
+ * Render a {@link SyncSummary} as report lines: counts, any stop reason, the
29
+ * new watermark, and the unassigned/failed breakdowns. Used by `/kankaku
30
+ * sync`/`sync all` and the panel's sync screen.
31
+ */
32
+ export function formatSyncSummaryLines(summary: SyncSummary): string[] {
33
+ const lines = [`uploaded ${summary.uploaded}, updated ${summary.updated}, skipped ${summary.skipped}, failed ${summary.failed.length}`];
34
+
35
+ if (summary.locked) lines.push("another sync is already in progress; nothing was attempted");
36
+ if (summary.error) lines.push(`stopped early: ${summary.error}`);
37
+ if (summary.syncedThrough) lines.push(`synced through ${summary.syncedThrough}`);
38
+
39
+ const unassignedEntries = Object.entries(summary.unassigned).sort(([a], [b]) => a.localeCompare(b));
40
+ if (unassignedEntries.length > 0) {
41
+ lines.push("unassigned (Sin determinar):");
42
+ for (const [label, count] of unassignedEntries) lines.push(` ${label}: ${count}`);
43
+ }
44
+
45
+ if (summary.failed.length > 0) {
46
+ lines.push("failed:");
47
+ for (const entry of summary.failed) lines.push(` ${entry.id}: ${entry.reason}`);
48
+ }
49
+
50
+ return lines;
51
+ }
52
+
53
+ /**
54
+ * `/kankaku backfill`'s report lines: tasks routed to Sin determinar this
55
+ * run, grouped by their old free-text label, or "no unassigned tasks" when
56
+ * none were. Exact body of `handleBackfillCommand`.
57
+ */
58
+ export function formatBackfillLines(summary: SyncSummary): string[] {
59
+ const unassignedEntries = Object.entries(summary.unassigned).sort(([a], [b]) => a.localeCompare(b));
60
+
61
+ const lines =
62
+ unassignedEntries.length > 0
63
+ ? [
64
+ ...unassignedEntries.map(([label, count]) => `${label}: ${count} task(s) -> Sin determinar`),
65
+ "reassign these in the hub web app's unassigned queue",
66
+ ]
67
+ : ["no unassigned tasks"];
68
+
69
+ if (summary.error) lines.push(`stopped early: ${summary.error}`);
70
+ return lines;
71
+ }
72
+
73
+ /**
74
+ * `/kankaku catalog refresh`'s report lines: client/project counts, or an
75
+ * unreachable notice when the refresh failed (`snapshot` is `undefined`).
76
+ * The subcommand still notifies that failure as an error rather than a
77
+ * report (see `handleCatalogCommand`); the panel's sync screen shows
78
+ * whatever this returns either way.
79
+ */
80
+ export function formatCatalogRefreshLines(snapshot: CatalogSnapshot | undefined): string[] {
81
+ if (!snapshot) return ["hub unreachable; catalog not refreshed"];
82
+ return [`refreshed: ${snapshot.clients.length} client(s), ${snapshot.projects.length} project(s)`];
83
+ }