kankaku 0.4.5 → 0.5.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 +964 -16
- package/package.json +4 -2
- package/src/adapters/agent-info.ts +86 -0
- package/src/adapters/ancestry.ts +260 -0
- package/src/adapters/cached-catalog.ts +131 -0
- package/src/adapters/file-inflight-store.ts +32 -0
- package/src/adapters/file-modes.ts +35 -0
- package/src/adapters/hub-credentials.ts +95 -0
- package/src/adapters/jsonl-work-log.ts +19 -2
- package/src/adapters/kankaku-command.ts +729 -0
- package/src/adapters/kankaku-dir.ts +92 -0
- package/src/adapters/lazy-jsonl-work-log.ts +4 -0
- package/src/adapters/machine-process-registry.ts +256 -0
- package/src/adapters/pi-tracker.ts +432 -266
- package/src/adapters/pocketbase-catalog.ts +60 -0
- package/src/adapters/pocketbase-client.ts +197 -0
- package/src/adapters/pocketbase-sink.ts +224 -0
- package/src/adapters/process-identity-memo.ts +102 -0
- package/src/adapters/process-identity.ts +162 -0
- package/src/adapters/project-config.ts +73 -1
- package/src/adapters/report.ts +89 -8
- package/src/adapters/session-client.ts +116 -0
- package/src/adapters/session-dir.ts +28 -0
- package/src/adapters/session-target.ts +262 -0
- package/src/adapters/status-bar.ts +86 -0
- package/src/adapters/subagent-startup.ts +66 -0
- package/src/adapters/sync-runner.ts +301 -0
- package/src/adapters/sync-state-store.ts +227 -0
- package/src/adapters/target-picker.ts +82 -0
- package/src/config.ts +444 -6
- package/src/domain/ancestry-match.ts +84 -0
- package/src/domain/hub-entry.ts +339 -0
- package/src/domain/registry-health.ts +87 -0
- package/src/domain/subagent-profile.ts +495 -0
- package/src/domain/sync-plan.ts +251 -0
- package/src/domain/task-view.ts +307 -23
- package/src/domain/work-record.ts +165 -4
- package/src/domain/work-target.ts +185 -0
- package/src/domain/work-tracker.ts +257 -56
- package/src/extension.ts +303 -6
- package/src/ports/catalog.ts +31 -0
- package/src/ports/process-registry.ts +75 -0
- package/src/ports/work-log.ts +9 -0
- package/src/ports/work-sink.ts +35 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { loadHubEnvCredentials, validateHubUrl } from "../config.ts";
|
|
4
|
+
|
|
5
|
+
const CREDENTIALS_FILE = join(".kankaku", "credentials.json");
|
|
6
|
+
|
|
7
|
+
export interface HubCredentials {
|
|
8
|
+
url: string;
|
|
9
|
+
email: string;
|
|
10
|
+
password: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface HubCredentialsFile {
|
|
14
|
+
url?: string;
|
|
15
|
+
email?: string;
|
|
16
|
+
password?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Read `<homeDir>/.kankaku/credentials.json`. Tolerates a missing file,
|
|
21
|
+
* malformed JSON, a non-object document, or non-string fields — all
|
|
22
|
+
* return `{}` rather than throwing, since this file is optional and
|
|
23
|
+
* hand-edited (mirrors `project-config.ts#readProjectClient`).
|
|
24
|
+
*/
|
|
25
|
+
function readCredentialsFile(filePath: string): HubCredentialsFile {
|
|
26
|
+
if (!existsSync(filePath)) return {};
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const parsed: unknown = JSON.parse(readFileSync(filePath, "utf8"));
|
|
30
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
31
|
+
const record = parsed as Record<string, unknown>;
|
|
32
|
+
return {
|
|
33
|
+
url: typeof record["url"] === "string" ? record["url"] : undefined,
|
|
34
|
+
email: typeof record["email"] === "string" ? record["email"] : undefined,
|
|
35
|
+
password: typeof record["password"] === "string" ? record["password"] : undefined,
|
|
36
|
+
};
|
|
37
|
+
} catch {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ResolveHubCredentialsDeps {
|
|
43
|
+
env: NodeJS.ProcessEnv;
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the home directory; called lazily, inside this function, and
|
|
46
|
+
* defensively. A throwing provider (no `HOME`, a sandboxed environment
|
|
47
|
+
* without a resolvable home directory) is treated the same as "no home
|
|
48
|
+
* directory" rather than propagating — env-only credentials must still
|
|
49
|
+
* resolve, and the hub-unconfigured case must stay a no-op regardless of
|
|
50
|
+
* the host environment. Injectable for tests; defaults to `os.homedir` at
|
|
51
|
+
* the call site (extension.ts) — passed as a reference, never invoked
|
|
52
|
+
* there, so a throw never escapes before this function's own try/catch.
|
|
53
|
+
*/
|
|
54
|
+
homeDir: () => string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ResolveHubCredentialsResult {
|
|
58
|
+
/** `undefined` when unconfigured (no url/email/password from any source) or the URL is refused. */
|
|
59
|
+
credentials: HubCredentials | undefined;
|
|
60
|
+
/** Set only when a hub URL was given but rejected by {@link validateHubUrl}; surface it once via `ctx.ui.notify`. */
|
|
61
|
+
invalidReason?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Resolve hub credentials: `KANKAKU_PB_URL`/`_EMAIL`/`_PASSWORD` take
|
|
66
|
+
* precedence per field over `~/.kankaku/credentials.json` (so a partially
|
|
67
|
+
* set environment still combines with the file rather than failing
|
|
68
|
+
* closed). Never reads the project's own `.kankaku/config.json` — that
|
|
69
|
+
* file is project-local and frequently committed.
|
|
70
|
+
*/
|
|
71
|
+
/** Resolve `homeDir()` defensively: any failure (no `HOME`, a sandboxed environment) yields `undefined` instead of throwing. Exported so callers with their own homedir-dependent path (e.g. `extension.ts`'s catalog cache) can share the same guard. */
|
|
72
|
+
export function safeHomeDir(homeDir: () => string): string | undefined {
|
|
73
|
+
try {
|
|
74
|
+
return homeDir();
|
|
75
|
+
} catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function resolveHubCredentials(deps: ResolveHubCredentialsDeps): ResolveHubCredentialsResult {
|
|
81
|
+
const fromEnv = loadHubEnvCredentials(deps.env);
|
|
82
|
+
const homeDir = safeHomeDir(deps.homeDir);
|
|
83
|
+
const fromFile = homeDir !== undefined ? readCredentialsFile(join(homeDir, CREDENTIALS_FILE)) : {};
|
|
84
|
+
|
|
85
|
+
const url = fromEnv.url ?? fromFile.url;
|
|
86
|
+
const email = fromEnv.email ?? fromFile.email;
|
|
87
|
+
const password = fromEnv.password ?? fromFile.password;
|
|
88
|
+
|
|
89
|
+
if (!url || !email || !password) return { credentials: undefined };
|
|
90
|
+
|
|
91
|
+
const validation = validateHubUrl(url);
|
|
92
|
+
if (!validation.ok) return { credentials: undefined, invalidReason: validation.reason };
|
|
93
|
+
|
|
94
|
+
return { credentials: { url, email, password } };
|
|
95
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { dedupeById } from "../domain/task-view.ts";
|
|
3
4
|
import type { WorkLog } from "../ports/work-log.ts";
|
|
4
5
|
import type { WorkRecord } from "../domain/work-record.ts";
|
|
5
6
|
import { isWorkRecord } from "../domain/work-record.ts";
|
|
@@ -27,6 +28,20 @@ export class JsonlWorkLog implements WorkLog {
|
|
|
27
28
|
appendFileSync(this.filePath, `${JSON.stringify(record)}\n`);
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Cheap change signal: `mtimeMs:size` of the log file, computed with a
|
|
33
|
+
* single `statSync` rather than reading the file. `"0:0"` when the file
|
|
34
|
+
* does not exist yet (before the first `append`).
|
|
35
|
+
*/
|
|
36
|
+
version(): string {
|
|
37
|
+
try {
|
|
38
|
+
const stats = statSync(this.filePath);
|
|
39
|
+
return `${stats.mtimeMs}:${stats.size}`;
|
|
40
|
+
} catch {
|
|
41
|
+
return "0:0";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
30
45
|
readAll(): WorkRecord[] {
|
|
31
46
|
if (!existsSync(this.filePath)) return [];
|
|
32
47
|
const content = readFileSync(this.filePath, "utf8");
|
|
@@ -45,6 +60,8 @@ export class JsonlWorkLog implements WorkLog {
|
|
|
45
60
|
// Tolerate malformed lines (e.g. a torn write); skip them.
|
|
46
61
|
}
|
|
47
62
|
}
|
|
48
|
-
|
|
63
|
+
// One id can be in the file twice (written at settle, then re-appended by
|
|
64
|
+
// crash recovery): every reader gets it once — see dedupeById.
|
|
65
|
+
return dedupeById(records);
|
|
49
66
|
}
|
|
50
67
|
}
|