kankaku-claude 1.0.0 → 1.0.2
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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +29 -0
- package/README.md +46 -5
- package/dist/cli-core.js +46 -3
- package/dist/cost-chain.js +20 -0
- package/dist/handle-hook.js +23 -13
- package/dist/inflight-recovery.js +4 -2
- package/dist/replay.js +6 -1
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kankaku",
|
|
3
3
|
"description": "Records how long Claude Code works on each of your prompts: wall time, waiting time, work time and cost, per prompt, in kankaku's worklog format.",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "soyunninja"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,35 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## 1.0.2 — 2026-09-29
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **`/kankaku:status` promised a recovery that would not happen.** The
|
|
12
|
+
line compared the session total with the sum of ALL the session's
|
|
13
|
+
records and, when idle, called the whole difference "unrecorded, goes
|
|
14
|
+
to the next prompt". For a session that spent money before it had a
|
|
15
|
+
chained baseline (upgraded mid-way, or resumed without its state) that
|
|
16
|
+
was false: the next prompt only carries what was spent since the last
|
|
17
|
+
settle. The line now separates `this prompt so far`, `pending, goes to
|
|
18
|
+
the next prompt` and `never recorded`. Attribution itself was correct
|
|
19
|
+
in 1.0.1 and is unchanged.
|
|
20
|
+
|
|
21
|
+
## 1.0.1 — 2026-09-29
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Spend that landed between two prompts was in no record: on a real session
|
|
26
|
+
the statusline total was 461.06 USD while its 134 records summed to
|
|
27
|
+
299.80 USD, so 35 % of the spend was missing. A prompt's cost was measured
|
|
28
|
+
from the statusline snapshot at submit, which left background subagent
|
|
29
|
+
work and late statusline refreshes after a `Stop` unattributed. The
|
|
30
|
+
session state now keeps `costBaseline`, the total at the last settle, and
|
|
31
|
+
each prompt is measured from it (Stop, SessionEnd and crash recovery), so
|
|
32
|
+
the records of a session add up to its total. A new session starts at 0; a
|
|
33
|
+
counter reset records the new total. `/kankaku:status` shows `recorded $X
|
|
34
|
+
of $Y` and flags an unrecorded gap. Headless runs still have no cost.
|
|
35
|
+
|
|
7
36
|
## 1.0.0 — 2026-09-29
|
|
8
37
|
|
|
9
38
|
### Changed
|
package/README.md
CHANGED
|
@@ -140,8 +140,16 @@ anything behind in whatever project happens to be open.
|
|
|
140
140
|
`node dist/cli.js report`).
|
|
141
141
|
- `/kankaku:status` — the sessions kankaku-claude currently has state for:
|
|
142
142
|
session id, whether its process is still alive, whether a prompt is open,
|
|
143
|
-
and the last cost the statusline reported,
|
|
144
|
-
|
|
143
|
+
and the last cost the statusline reported, followed by `recorded $X of
|
|
144
|
+
$Y` (the sum of this session's record costs in this project's worklog
|
|
145
|
+
against the session total). When they differ by more than a cent the line says what the
|
|
146
|
+
difference is: `(this prompt so far $R)` for the open prompt's running
|
|
147
|
+
spend, `(pending $P, goes to the next prompt)` for what was spent since
|
|
148
|
+
the last settle of a session that has a chained baseline, and
|
|
149
|
+
`(never recorded $Z)` for spend from before the session had one (a
|
|
150
|
+
session upgraded mid-way, or resumed without its state) — no later
|
|
151
|
+
prompt carries that part.
|
|
152
|
+
The resolved work target comes first (wraps `node dist/cli.js status`).
|
|
145
153
|
- `/kankaku:task` — links this session to a hub task (wraps
|
|
146
154
|
`node dist/cli.js task`); see "Linking a task" below.
|
|
147
155
|
- `/kankaku:setup` — prints the `statusLine` snippet described above (wraps
|
|
@@ -257,6 +265,37 @@ from Claude Code is not supported yet. Assignment is create-only
|
|
|
257
265
|
on the hub: a row already uploaded as unassigned stays that way until it is
|
|
258
266
|
reassigned in the web app; a later sync does not move it.
|
|
259
267
|
|
|
268
|
+
## How cost is derived
|
|
269
|
+
|
|
270
|
+
Claude Code hooks carry no cost. The only source is the statusline, whose
|
|
271
|
+
`cost.total_cost_usd` is the running total of the session; the statusline
|
|
272
|
+
command stores the latest value under your home directory (see "Where the
|
|
273
|
+
files live").
|
|
274
|
+
|
|
275
|
+
- **Per-prompt difference.** A prompt's cost is the session total when the
|
|
276
|
+
prompt settles minus the total the prompt is measured from, rounded to
|
|
277
|
+
micro-dollars and never negative.
|
|
278
|
+
- **Chained baseline.** The session state keeps `costBaseline`, the session
|
|
279
|
+
total at the last settle. The next prompt is measured from that baseline,
|
|
280
|
+
not from the snapshot at submit, so the records of a session add up to its
|
|
281
|
+
total. A session Claude Code reports as newly started (`SessionStart`
|
|
282
|
+
source `startup`) begins with baseline 0. A resumed session keeps the
|
|
283
|
+
baseline of its state file; with no state file its first prompt is
|
|
284
|
+
measured from the snapshot at submit.
|
|
285
|
+
- **Spend between prompts belongs to the next record.** `worklog.jsonl` is
|
|
286
|
+
append-only and the previous record is already written, so anything spent
|
|
287
|
+
after a settle and before the next prompt (background subagents that keep
|
|
288
|
+
running, a statusline refresh that arrives after `Stop`) is added to the
|
|
289
|
+
next record of the same session.
|
|
290
|
+
- **Counter reset.** If the total is lower than the value the prompt is
|
|
291
|
+
measured from, the counter was reset: the prompt's cost is the new total
|
|
292
|
+
and the baseline restarts from it.
|
|
293
|
+
- **Headless runs have no cost.** `claude -p` renders no statusline, so
|
|
294
|
+
there is no total: the record stays without cost (`costObserved` unset)
|
|
295
|
+
and the baseline does not move.
|
|
296
|
+
- `/kankaku:status` shows `recorded $X of $Y` per live session so a gap is
|
|
297
|
+
visible.
|
|
298
|
+
|
|
260
299
|
## Where the files live
|
|
261
300
|
|
|
262
301
|
- `<KANKAKU_DIR>/worklog.jsonl` — the append-only log of settled records,
|
|
@@ -304,9 +343,11 @@ still open is closed at that same instant.
|
|
|
304
343
|
only the statusline's aggregate `total_cost_usd`; per-record `usage` token
|
|
305
344
|
fields stay at zero, cost is the only populated figure.
|
|
306
345
|
- **Cost is a per-prompt delta of the session total, from the statusline,
|
|
307
|
-
and needs the manual setup step.**
|
|
308
|
-
|
|
309
|
-
|
|
346
|
+
and needs the manual setup step.** See "How cost is derived". Spend that
|
|
347
|
+
lands between two prompts (a late statusline refresh, a background
|
|
348
|
+
subagent still running) is attributed to the next record of the session,
|
|
349
|
+
not lost. Headless `claude -p` runs have no statusline, so their records
|
|
350
|
+
carry no cost.
|
|
310
351
|
- **A permission wait ends at the next hook event, not when you actually
|
|
311
352
|
click.** There is no documented hook that fires the moment you answer a
|
|
312
353
|
permission dialog, so the waiting span closes at whatever hook fires next
|
package/dist/cli-core.js
CHANGED
|
@@ -52,12 +52,21 @@ function runStatus(deps) {
|
|
|
52
52
|
if (files.length === 0) {
|
|
53
53
|
return { stdout: `${targetLine}\nNo active sessions.\n`, exitCode: 0 };
|
|
54
54
|
}
|
|
55
|
+
const recordedBySession = new Map();
|
|
56
|
+
for (const record of new JsonlWorkLog(kankakuDir).readAll()) {
|
|
57
|
+
if (record.sessionId === undefined)
|
|
58
|
+
continue;
|
|
59
|
+
recordedBySession.set(record.sessionId, (recordedBySession.get(record.sessionId) ?? 0) + record.usage.cost);
|
|
60
|
+
}
|
|
55
61
|
const lines = files
|
|
56
62
|
.sort()
|
|
57
|
-
.map((file) =>
|
|
63
|
+
.map((file) => {
|
|
64
|
+
const sessionId = sessionIdFromStateFile(file);
|
|
65
|
+
return formatStatusLine(sessionId, file, deps.isAlive, deps.env, recordedBySession.get(sessionId) ?? 0);
|
|
66
|
+
});
|
|
58
67
|
return { stdout: [targetLine, ...lines].join("\n") + "\n", exitCode: 0 };
|
|
59
68
|
}
|
|
60
|
-
function formatStatusLine(sessionId, stateFile, isAlive, env) {
|
|
69
|
+
function formatStatusLine(sessionId, stateFile, isAlive, env, recordedUsd) {
|
|
61
70
|
const state = readState(stateFile);
|
|
62
71
|
if (!state)
|
|
63
72
|
return `${sessionId} (unreadable state)`;
|
|
@@ -67,7 +76,41 @@ function formatStatusLine(sessionId, stateFile, isAlive, env) {
|
|
|
67
76
|
: "idle";
|
|
68
77
|
const cost = readCost(env, sessionId);
|
|
69
78
|
const costWord = cost ? `$${cost.totalUsd.toFixed(2)}` : "-";
|
|
70
|
-
|
|
79
|
+
const base = `${sessionId} pid ${state.pid} (${aliveWord}) ${promptWord} cost ${costWord}`;
|
|
80
|
+
if (!cost)
|
|
81
|
+
return base;
|
|
82
|
+
return `${base} recorded $${recordedUsd.toFixed(2)} of $${cost.totalUsd.toFixed(2)}${describeGap(state, cost.totalUsd, recordedUsd)}`;
|
|
83
|
+
}
|
|
84
|
+
/** Amounts of one cent or less are rounding noise, never reported. */
|
|
85
|
+
const REPORTABLE_USD = 0.01;
|
|
86
|
+
/**
|
|
87
|
+
* Splits the difference between the session's total and what its records
|
|
88
|
+
* hold into what it actually is, so the line never promises a recovery
|
|
89
|
+
* that will not happen:
|
|
90
|
+
*
|
|
91
|
+
* - `this prompt so far`: the open prompt's running spend (total − its
|
|
92
|
+
* `costAtStart`); it lands in that prompt's own record at settle.
|
|
93
|
+
* - `pending, goes to the next prompt`: only when idle AND the session has
|
|
94
|
+
* a `costBaseline` — the next prompt starts there, so it picks this up.
|
|
95
|
+
* Without a baseline the next prompt starts at the snapshot and picks up
|
|
96
|
+
* nothing, so nothing is promised.
|
|
97
|
+
* - `never recorded`: whatever is left — spent before this session had a
|
|
98
|
+
* chained baseline (a session upgraded mid-way, or resumed without its
|
|
99
|
+
* state). It is in no record and no later prompt will carry it.
|
|
100
|
+
*/
|
|
101
|
+
function describeGap(state, totalUsd, recordedUsd) {
|
|
102
|
+
const start = state.promptOpen?.costAtStart;
|
|
103
|
+
const running = state.promptOpen && typeof start === "number" ? Math.max(0, totalUsd - start) : 0;
|
|
104
|
+
const pending = !state.promptOpen && typeof state.costBaseline === "number" ? Math.max(0, totalUsd - state.costBaseline) : 0;
|
|
105
|
+
const neverRecorded = totalUsd - recordedUsd - running - pending;
|
|
106
|
+
const parts = [];
|
|
107
|
+
if (running > REPORTABLE_USD)
|
|
108
|
+
parts.push(` (this prompt so far $${running.toFixed(2)})`);
|
|
109
|
+
if (pending > REPORTABLE_USD)
|
|
110
|
+
parts.push(` (pending $${pending.toFixed(2)}, goes to the next prompt)`);
|
|
111
|
+
if (neverRecorded > REPORTABLE_USD)
|
|
112
|
+
parts.push(` (never recorded $${neverRecorded.toFixed(2)})`);
|
|
113
|
+
return parts.join("");
|
|
71
114
|
}
|
|
72
115
|
function sessionIdFromStateFile(file) {
|
|
73
116
|
return basename(file).replace(/\.state\.json$/, "");
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chained per-prompt cost rule. `totalUsd` is the session total at
|
|
3
|
+
* settle, `start` the total the prompt is measured from (the previous
|
|
4
|
+
* settle's baseline, or the snapshot at submit for a session with none).
|
|
5
|
+
*
|
|
6
|
+
* - No finite total (headless, no statusline): nothing observed, baseline
|
|
7
|
+
* untouched.
|
|
8
|
+
* - Total below the start: the counter was reset, so the cost is the total.
|
|
9
|
+
* - Otherwise cost = total - start, in micro-dollars (no binary float noise).
|
|
10
|
+
*
|
|
11
|
+
* Node builtins only; safe on the light hook path.
|
|
12
|
+
*/
|
|
13
|
+
export function settleCost(totalUsd, start) {
|
|
14
|
+
if (typeof totalUsd !== "number" || !Number.isFinite(totalUsd))
|
|
15
|
+
return {};
|
|
16
|
+
if (typeof start !== "number" || !Number.isFinite(start))
|
|
17
|
+
return { baseline: totalUsd };
|
|
18
|
+
const raw = totalUsd < start ? totalUsd : totalUsd - start;
|
|
19
|
+
return { cost: Math.max(0, Math.round(raw * 1e6) / 1e6), baseline: totalUsd };
|
|
20
|
+
}
|
package/dist/handle-hook.js
CHANGED
|
@@ -5,6 +5,7 @@ import { readState, writeState, updateState } from "./session-state.js";
|
|
|
5
5
|
import { resolveClaudePid } from "./claude-pid.js";
|
|
6
6
|
import { splitPrompts } from "./prompts.js";
|
|
7
7
|
import { readCost, deleteCost, sweepStaleCostFiles } from "./cost-store.js";
|
|
8
|
+
import { settleCost } from "./cost-chain.js";
|
|
8
9
|
const STOP_COST_WAIT_POLL_MS = 100;
|
|
9
10
|
const STOP_COST_WAIT_MAX_MS = 1500;
|
|
10
11
|
/**
|
|
@@ -31,14 +32,16 @@ export async function handleHook(input, deps) {
|
|
|
31
32
|
? { ts, event: "UserPromptSubmit", prompt, promptId }
|
|
32
33
|
: { ts, event: "UserPromptSubmit", prompt };
|
|
33
34
|
appendEvent(paths.eventsFile, event);
|
|
34
|
-
const
|
|
35
|
+
const snapshot = readCost(deps.env, sessionId)?.totalUsd;
|
|
35
36
|
updateState(paths.stateFile, (state) => ({
|
|
36
37
|
pid: state?.pid ?? 0,
|
|
37
38
|
parentPid: state?.parentPid ?? 0,
|
|
38
39
|
cwd: state?.cwd || cwd,
|
|
39
40
|
startedAt: state?.startedAt ?? ts,
|
|
40
|
-
|
|
41
|
+
// The chained baseline wins over the snapshot: spend since the last settle belongs to this prompt.
|
|
42
|
+
promptOpen: { id: promptId ?? `${sessionId}:${ts}`, startedAt: ts, costAtStart: state?.costBaseline ?? snapshot },
|
|
41
43
|
permissionOpen: null,
|
|
44
|
+
...(state?.costBaseline !== undefined ? { costBaseline: state.costBaseline } : {}),
|
|
42
45
|
}));
|
|
43
46
|
return;
|
|
44
47
|
}
|
|
@@ -68,6 +71,7 @@ export async function handleHook(input, deps) {
|
|
|
68
71
|
startedAt: state?.startedAt ?? ts,
|
|
69
72
|
promptOpen: state?.promptOpen ?? null,
|
|
70
73
|
permissionOpen: ts,
|
|
74
|
+
...(state?.costBaseline !== undefined ? { costBaseline: state.costBaseline } : {}),
|
|
71
75
|
}));
|
|
72
76
|
return;
|
|
73
77
|
}
|
|
@@ -95,7 +99,7 @@ export async function handleHook(input, deps) {
|
|
|
95
99
|
case "SessionStart": {
|
|
96
100
|
const source = readString(raw.source) ?? "startup";
|
|
97
101
|
appendEvent(paths.eventsFile, { ts, event: "SessionStart", source });
|
|
98
|
-
await handleSessionStart(paths, sessionId, cwd, source, ts, deps);
|
|
102
|
+
await handleSessionStart(paths, sessionId, cwd, source, raw.source === "startup", ts, deps);
|
|
99
103
|
return;
|
|
100
104
|
}
|
|
101
105
|
case "SessionEnd": {
|
|
@@ -128,25 +132,27 @@ async function handleStop(paths, sessionId, deps) {
|
|
|
128
132
|
deps.stderr(`kankaku: no session state at Stop for ${sessionId}`);
|
|
129
133
|
return;
|
|
130
134
|
}
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
? Math.max(0, Math.round((totalUsd - costAtStart) * 1e6) / 1e6) // micro-dollars: no binary float noise in the record
|
|
135
|
-
: undefined;
|
|
136
|
-
const core = replayPrompt(last, { cost: costDelta });
|
|
135
|
+
// A Stop with no open prompt settles nothing: no cost, no baseline move.
|
|
136
|
+
const settled = state.promptOpen ? settleCost(cost?.totalUsd, state.promptOpen.costAtStart) : {};
|
|
137
|
+
const core = replayPrompt(last, { cost: settled.cost });
|
|
137
138
|
if (core) {
|
|
138
139
|
const assignment = await assignmentResolver(paths, deps);
|
|
139
140
|
const record = buildClaudeRecord(core, state, sessionId, cost?.model, assignment(state.cwd, sessionId));
|
|
140
141
|
const log = deps.log ?? new JsonlWorkLog(paths.kankakuDir);
|
|
141
142
|
log.append(record);
|
|
142
143
|
}
|
|
143
|
-
writeState(paths.stateFile, {
|
|
144
|
+
writeState(paths.stateFile, {
|
|
145
|
+
...state,
|
|
146
|
+
promptOpen: null,
|
|
147
|
+
permissionOpen: null,
|
|
148
|
+
...(settled.baseline !== undefined ? { costBaseline: settled.baseline } : {}),
|
|
149
|
+
});
|
|
144
150
|
const keep = events.slice(0, events.length - last.events.length);
|
|
145
151
|
dropSettledPrompts(paths.eventsFile, keep);
|
|
146
152
|
if (core)
|
|
147
153
|
await syncHeavy("agent_settled", state.cwd, deps);
|
|
148
154
|
}
|
|
149
|
-
async function handleSessionStart(paths, sessionId, cwd, source, ts, deps) {
|
|
155
|
+
async function handleSessionStart(paths, sessionId, cwd, source, isStartup, ts, deps) {
|
|
150
156
|
if (source !== "compact") {
|
|
151
157
|
const { recoverStaleSessions } = await import("./inflight-recovery.js");
|
|
152
158
|
const { JsonlWorkLog } = await import("kankaku-pi/hub");
|
|
@@ -180,6 +186,8 @@ async function handleSessionStart(paths, sessionId, cwd, source, ts, deps) {
|
|
|
180
186
|
startedAt: ts,
|
|
181
187
|
promptOpen: null,
|
|
182
188
|
permissionOpen: null,
|
|
189
|
+
// A newly started session's counter starts at zero; any other source keeps the snapshot-at-submit fallback.
|
|
190
|
+
...(isStartup ? { costBaseline: 0 } : {}),
|
|
183
191
|
};
|
|
184
192
|
writeState(paths.stateFile, state);
|
|
185
193
|
await syncHeavy("session_start", cwd, deps);
|
|
@@ -194,9 +202,11 @@ async function handleSessionEnd(paths, sessionId, cwd, ts, deps) {
|
|
|
194
202
|
const prompts = splitPrompts(events);
|
|
195
203
|
const last = prompts[prompts.length - 1];
|
|
196
204
|
if (last) {
|
|
197
|
-
const
|
|
205
|
+
const costNow = readCost(deps.env, sessionId);
|
|
206
|
+
const settled = settleCost(costNow?.totalUsd, state.promptOpen.costAtStart);
|
|
207
|
+
const core = replayPrompt(last, { settledAt: ts, cost: settled.cost });
|
|
198
208
|
if (core) {
|
|
199
|
-
const model =
|
|
209
|
+
const model = costNow?.model;
|
|
200
210
|
const assignment = await assignmentResolver(paths, deps);
|
|
201
211
|
const record = buildClaudeRecord(core, state, sessionId, model, assignment(state.cwd, sessionId));
|
|
202
212
|
const log = deps.log ?? new JsonlWorkLog(paths.kankakuDir);
|
|
@@ -6,6 +6,7 @@ import { readEventLog } from "./event-log.js";
|
|
|
6
6
|
import { splitPrompts, replayPrompt } from "./replay.js";
|
|
7
7
|
import { buildClaudeRecord } from "./record.js";
|
|
8
8
|
import { readCost, deleteCost } from "./cost-store.js";
|
|
9
|
+
import { settleCost } from "./cost-chain.js";
|
|
9
10
|
/**
|
|
10
11
|
* Crash recovery: for every other session's state file whose pid is dead,
|
|
11
12
|
* replay its still-open prompt (if any) into an `interrupted` record, then
|
|
@@ -46,9 +47,10 @@ export function recoverStaleSessions(input) {
|
|
|
46
47
|
// `now` is only a fallback when no usable timestamp exists.
|
|
47
48
|
const lastTs = last.events[last.events.length - 1]?.ts;
|
|
48
49
|
const settledAt = typeof lastTs === "number" && Number.isFinite(lastTs) ? lastTs : input.now;
|
|
49
|
-
const
|
|
50
|
+
const costNow = readCost(input.env, sessionId);
|
|
51
|
+
const core = replayPrompt(last, { settledAt, cost: settleCost(costNow?.totalUsd, state.promptOpen.costAtStart).cost });
|
|
50
52
|
if (core) {
|
|
51
|
-
const model =
|
|
53
|
+
const model = costNow?.model;
|
|
52
54
|
records.push(buildClaudeRecord(core, state, sessionId, model, input.resolveAssignment?.(state.cwd, sessionId)));
|
|
53
55
|
}
|
|
54
56
|
}
|
package/dist/replay.js
CHANGED
|
@@ -97,5 +97,10 @@ export function replayPrompt(prompt, opts = {}) {
|
|
|
97
97
|
if (terminalStopIndex >= 0)
|
|
98
98
|
return settled;
|
|
99
99
|
current = opts.settledAt ?? prompt.events[prompt.events.length - 1].ts;
|
|
100
|
-
|
|
100
|
+
const interrupted = tracker.onShutdown();
|
|
101
|
+
if (!interrupted || typeof opts.cost !== "number" || !Number.isFinite(opts.cost))
|
|
102
|
+
return interrupted;
|
|
103
|
+
// An interrupted prompt ends no turn, so the tracker saw no cost; stamp the
|
|
104
|
+
// measured delta without inventing a turn.
|
|
105
|
+
return { ...interrupted, usage: { ...interrupted.usage, cost: interrupted.usage.cost + opts.cost }, costObserved: true };
|
|
101
106
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kankaku-claude",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Claude Code plugin that records how long the agent works on each user prompt, in kankaku's worklog format.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"prepublishOnly": "npm run check"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"kankaku-pi": "^1.0.
|
|
30
|
+
"kankaku-pi": "^1.0.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^24.13.4",
|