kankaku 0.6.5 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -687,17 +687,53 @@ the future, this is the signal that would surface it.
687
687
 
688
688
  ## The `/kankaku` command
689
689
 
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.
690
+ `/kankaku` with no arguments, run inside pi's TUI, opens an overlay panel
691
+ modelled on pi's own `/settings` (the same pi-tui `SettingsList`/
692
+ `SelectList` widgets, the same keys) from which every kankaku view and
693
+ action is reachable:
694
+
695
+ - **Target** — billing client, project, hub task, and the legacy label
696
+ (see "Billing labels" and "Hub (PocketBase)" below). The Task row lists
697
+ the open/doing hub tasks of the current project; picking one links the
698
+ session, exactly like `/kankaku task pick` below — **session-only**,
699
+ never persisted (see "Linking to a hub task").
700
+ - **Report** — today/all totals, tasks, sessions, clients, and projects:
701
+ the same five views `/kankaku`'s subcommands produce.
702
+ - **Sync** (hub only) — status, sync now, sync all, backfill, and a
703
+ catalog refresh.
704
+ - **Export** — write today's or every task as csv/json.
705
+ - **Doctor** — orphan/uncertain subagent counts and ancestor-detection
706
+ availability.
707
+ - **About** — versions, the resolved `KANKAKU_DIR`, the hub URL, and every
708
+ env-only setting, read-only.
709
+
710
+ Keys: `↑↓` move, `Enter` open a section or select a value, `Esc` go back
711
+ (or close the panel at the root), `q` close from anywhere. Mouse: the
712
+ footer hints and list rows are clickable, but only in pi's fullscreen
713
+ mode — pi does not dispatch mouse events in its regular (non-fullscreen)
714
+ mode, so there the panel is keyboard-only.
715
+
716
+ Every subcommand below is unchanged, and is exactly what headless (print
717
+ or RPC) mode still uses — `/kankaku` there keeps showing today's totals
718
+ directly, never the panel, since there is no UI to open one in. Run
719
+ `/kankaku <subcommand>` (with arguments) in the TUI to skip the panel and
720
+ go straight to that subcommand's output, exactly as before the panel
721
+ existed.
722
+
723
+ Plain `/kankaku` (headless, or any subcommand below) shows today's totals
724
+ (work, waiting, record count) per role, plus a union-based tasks segment.
725
+ Each totals line also shows `cache hit NN%` when tokens were recorded: the
726
+ share of prompt input tokens served from the provider's prompt cache, cache
727
+ reads over input plus cache reads plus cache writes; the segment is
728
+ omitted, not shown as `0%`, when no tokens were recorded. In the
729
+ interactive TUI the report is appended to the chat transcript as a durable
730
+ card that is never sent to the LLM; without a UI (print or RPC mode) it
731
+ falls back to a notification. Arguments are whitespace-separated and
732
+ order-insensitive:
733
+
734
+ - `/kankaku` (headless only — the TUI opens the panel instead, whose
735
+ Report screen defaults to the same view) — today's role totals and
736
+ tasks segment, each with its estimated cost.
701
737
  - `/kankaku all` — same, but across every record.
702
738
  - `/kankaku tasks` — one line per task (time, union wall/work, cost,
703
739
  subagent count, truncated prompt) for the **current pi session**. Add `all` for
@@ -834,6 +870,12 @@ task view exposes it from the orchestrator record only.
834
870
  The status bar shows `💼 <client> · <project>` (or just `💼 <client>` without
835
871
  a project) in place of the legacy client label, both idle and during a run.
836
872
 
873
+ Mid-session, the `/kankaku` panel's Target screen (see "The `/kankaku`
874
+ command" above) is the interactive way to change the client or project
875
+ without going through `/kankaku target pick`'s picker dialog — it edits the
876
+ same session-level target, applies through the same `SessionTarget`, and
877
+ has its own "Remember" row for `<KANKAKU_DIR>/config.json`.
878
+
837
879
  ### Linking to a hub task
838
880
 
839
881
  `/kankaku task` (or `/kankaku task pick`) links the current session to one
@@ -861,6 +903,12 @@ the task view exposes it from the orchestrator record only, and
861
903
  `formatWorkTargetLabel` appends it to the status-bar/report label as
862
904
  `<client> · <project> › <task title>`.
863
905
 
906
+ The `/kankaku` panel's Target screen's Task row is the interactive
907
+ equivalent of `/kankaku task pick`/`clear`: it lists the same open/doing
908
+ tasks, applies the link through the same `SessionTarget.setTask`, and
909
+ stays just as session-only — picking a task there is never persisted to
910
+ `config.json` either.
911
+
864
912
  ### Caching and offline behaviour
865
913
 
866
914
  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.0",
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
+ }