kankaku-claude 1.4.0 → 1.4.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/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ 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
+ ## Unreleased
8
+
9
+ ### Added
10
+
11
+ - `/kankaku:task new <title> [--ref <externalRef>]` creates an open task in
12
+ the resolved project and links it session-only, caching the returned task
13
+ immediately. Creation stays out of hooks.
14
+
7
15
  ## 1.4.0 — 2026-09-30
8
16
 
9
17
  ### Added
package/README.md CHANGED
@@ -297,6 +297,11 @@ for the folder, so the records written from then on carry that task:
297
297
  `/kankaku:task <id>` by hub id, and `/kankaku:task <text>` by a unique
298
298
  case-insensitive part of the title (several matches are listed and nothing
299
299
  changes). A purely numeric argument is always a list number.
300
+ - `/kankaku:task new <title> [--ref <externalRef>]` creates an open task
301
+ in the resolved project and links it to this session. The title is required;
302
+ `--ref` takes one argument (quote references containing spaces). Creation
303
+ requires a reachable hub; failure leaves the existing link unchanged.
304
+ The returned task is cached immediately so record resolution retains its id.
300
305
  - `/kankaku:task clear` removes the link.
301
306
 
302
307
  The link is session-only, like `/kankaku task pick` in pi: it is stored in
package/commands/task.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
- description: Link this session to a hub task (list open tasks, pick one, or clear the link)
3
- argument-hint: [number | id | text | clear]
2
+ description: Create or link a hub task for this session, list open tasks, or clear the link
3
+ argument-hint: [number | id | text | clear | new <title> [--ref <externalRef>]]
4
4
  allowed-tools: Bash(node:*)
5
5
  ---
6
6
 
@@ -1,5 +1,5 @@
1
1
  import { join } from "node:path";
2
- import { CachedCatalog, PocketBaseClient, createPocketBaseCatalogFetcher, resolveHubCredentials } from "kankaku-pi/hub";
2
+ import { CachedCatalog, PocketBaseClient, createHubTask, createPocketBaseCatalogFetcher, resolveHubCredentials } from "kankaku-pi/hub";
3
3
  const DEFAULT_REFRESH_MS = 3000;
4
4
  /**
5
5
  * Best-effort refresh bounded by `catalogTimeoutMs`; on failure the cached
@@ -32,6 +32,26 @@ export async function refreshCatalog(deps, homeDir) {
32
32
  return undefined;
33
33
  return { snapshot: cached, text: `catalog from cache, ${formatAge(deps.now() - cached.fetchedAt)} old` };
34
34
  }
35
+ /** Explicit task command only: POST and adopt before the session link is written. */
36
+ export async function createCatalogTask(deps, homeDir, projectId, title, externalRef) {
37
+ const hub = resolveHubCredentials({ env: deps.env, homeDir: () => homeDir });
38
+ if (!hub.credentials)
39
+ throw new Error("hub credentials are not configured");
40
+ const client = new PocketBaseClient({ ...hub.credentials, ...(deps.fetch ? { fetch: deps.fetch } : {}) });
41
+ const catalog = new CachedCatalog({ filePath: join(homeDir, ".kankaku", "catalog.json"), url: hub.credentials.url, clock: { now: deps.now }, fetchCatalog: createPocketBaseCatalogFetcher(client) });
42
+ if (!catalog.read())
43
+ throw new Error("no catalog cache");
44
+ const controller = new AbortController();
45
+ const timer = setTimeout(() => controller.abort(), deps.catalogTimeoutMs ?? DEFAULT_REFRESH_MS);
46
+ try {
47
+ const task = await createHubTask(client, { projectId, title, ...(externalRef ? { externalRef } : {}) }, controller.signal);
48
+ catalog.adoptTask(task);
49
+ return task;
50
+ }
51
+ finally {
52
+ clearTimeout(timer);
53
+ }
54
+ }
35
55
  function formatAge(ms) {
36
56
  const seconds = Math.max(0, Math.round(ms / 1000));
37
57
  if (seconds < 60)
package/dist/task-cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { homedir } from "node:os";
2
2
  import { join } from "node:path";
3
- import { refreshCatalog } from "./catalog-refresh.js";
3
+ import { createCatalogTask, refreshCatalog } from "./catalog-refresh.js";
4
4
  import { findSession } from "./find-session.js";
5
5
  import { resolveKankakuDir, resolveTargetFile } from "./paths.js";
6
6
  import { readSessionTarget, sessionInputs, withoutTaskLink, writeSessionTarget } from "./session-target-store.js";
@@ -28,6 +28,13 @@ export async function runTaskCli(args, deps) {
28
28
  const file = resolveTargetFile(claudeDir, session);
29
29
  const stored = readSessionTarget(file) ?? { lastList: { kind: "tasks", ids: [] } };
30
30
  const arg = args.join(" ").trim();
31
+ const creating = args[0] === "new";
32
+ const refIndex = args.indexOf("--ref");
33
+ const title = creating ? args.slice(1, refIndex === -1 ? undefined : refIndex).join(" ").trim() : "";
34
+ const externalRef = refIndex === -1 ? undefined : args[refIndex + 1]?.trim();
35
+ if (creating && (!title || (refIndex !== -1 && (!externalRef || refIndex !== args.length - 2)))) {
36
+ return say(["usage: /kankaku:task new <title> [--ref <externalRef>]"], 1);
37
+ }
31
38
  if (arg === "clear") {
32
39
  if (stored.hubTaskId === undefined)
33
40
  return say(["no task linked"], 0);
@@ -50,6 +57,16 @@ export async function runTaskCli(args, deps) {
50
57
  }
51
58
  const projectName = resolved.target.projectName ?? projectId;
52
59
  const open = listOpenTasks(tasks, projectId);
60
+ if (creating) {
61
+ try {
62
+ const task = await createCatalogTask(deps, homeDir, projectId, title, externalRef);
63
+ writeSessionTarget(file, { ...stored, hubTaskId: task.id, hubTaskTitle: task.title, projectId, pickedAt: deps.now() });
64
+ return say([...prefix, `created and linked: ${task.title}`], 0);
65
+ }
66
+ catch {
67
+ return say([...prefix, "task creation failed; existing session link unchanged"], 1);
68
+ }
69
+ }
53
70
  if (arg === "" || arg === "list") {
54
71
  if (open.length === 0)
55
72
  return say([...prefix, `no open tasks in project ${projectName}`], 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kankaku-claude",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
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.4.0"
30
+ "kankaku-pi": "^1.4.1"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^24.13.4",