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 +90 -11
- package/package.json +1 -1
- package/src/adapters/hub-actions.ts +83 -0
- package/src/adapters/kankaku-command.ts +193 -243
- package/src/adapters/panel/kankaku-panel.ts +419 -0
- package/src/adapters/panel/panel-items.ts +87 -0
- package/src/adapters/panel/panel-lines.ts +13 -0
- package/src/adapters/panel/panel-theme.ts +32 -0
- package/src/adapters/panel/screens/about.ts +69 -0
- package/src/adapters/panel/screens/doctor.ts +89 -0
- package/src/adapters/panel/screens/export.ts +123 -0
- package/src/adapters/panel/screens/report.ts +143 -0
- package/src/adapters/panel/screens/sync.ts +136 -0
- package/src/adapters/panel/screens/target.ts +384 -0
- package/src/adapters/pi-tracker.ts +108 -6
- package/src/adapters/report-views.ts +98 -0
- package/src/adapters/session-target.ts +51 -4
- package/src/domain/panel-model.ts +270 -0
- package/src/extension.ts +18 -8
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
|
-
|
|
691
|
-
|
|
692
|
-
`
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
the
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
-
|
|
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
|
@@ -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
|
+
}
|