@planckspace/cli 0.1.0 → 0.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planckspace/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "PlanckSpace CLI — sync AI token usage from your editor to the team ledger",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=correlate.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"correlate.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/correlate.test.ts"],"names":[],"mappings":""}
@@ -1,204 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll } from "vitest";
2
- import fs from "fs";
3
- import os from "os";
4
- import path from "path";
5
- import { simpleGit } from "simple-git";
6
- import { initDb, upsertSessions, getSessions, setSessionCorrelation, closeDb, } from "../db/store.js";
7
- import { correlateSessions } from "../correlate.js";
8
- // A fixed reference clock. Commit dates are placed relative to it, and the same
9
- // value is fed to correlateSessions() so the time-window logic is deterministic.
10
- const NOW = Date.parse("2026-06-01T12:00:00.000Z");
11
- const DAY = 24 * 60 * 60 * 1000;
12
- const HOUR = 60 * 60 * 1000;
13
- const iso = (ms) => new Date(ms).toISOString();
14
- const SESSION_EMAIL = "dev@example.com";
15
- // process.env minus undefined values — simple-git's .env() replaces the child's
16
- // whole environment, so we must carry PATH etc. through. Editor vars are dropped:
17
- // simple-git refuses to run with EDITOR/*_EDITOR set in a passed env.
18
- const BASE_ENV = {};
19
- for (const [k, v] of Object.entries(process.env)) {
20
- if (v !== undefined && !/EDITOR/i.test(k))
21
- BASE_ENV[k] = v;
22
- }
23
- let tmpRoot;
24
- const repoDirs = [];
25
- beforeAll(() => {
26
- tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "planck-correlate-"));
27
- initDb(path.join(tmpRoot, "local.db"));
28
- });
29
- afterAll(() => {
30
- closeDb();
31
- fs.rmSync(tmpRoot, { recursive: true, force: true });
32
- });
33
- // ── Helpers ─────────────────────────────────────────────────────────────────
34
- async function makeRepo(label) {
35
- const dir = fs.mkdtempSync(path.join(tmpRoot, `repo-${label}-`));
36
- repoDirs.push(dir);
37
- const git = simpleGit(dir);
38
- await git.init();
39
- await git.addConfig("user.email", SESSION_EMAIL);
40
- await git.addConfig("user.name", "Dev");
41
- await git.addConfig("commit.gpgsign", "false");
42
- return { dir, git };
43
- }
44
- /** Commit a file with explicit author + committer dates (and optional override author). */
45
- async function commitAt(git, dir, file, whenMs, authorOverride) {
46
- fs.writeFileSync(path.join(dir, file), `${file} @ ${whenMs}\n`);
47
- await git.add(file);
48
- git.env({ ...BASE_ENV, GIT_AUTHOR_DATE: iso(whenMs), GIT_COMMITTER_DATE: iso(whenMs) });
49
- const opts = authorOverride ? { "--author": authorOverride } : undefined;
50
- await git.commit(`add ${file}`, undefined, opts);
51
- }
52
- /** Rename the (post-first-commit) current branch to a stable name (default "main"). */
53
- async function renameTo(git, name = "main") {
54
- await git.branch(["-m", name]);
55
- }
56
- const renameToMain = (git) => renameTo(git);
57
- let sessionCounter = 0;
58
- function makeSession(over) {
59
- sessionCounter += 1;
60
- return {
61
- sessionId: `corr-session-${sessionCounter}`,
62
- tool: "claude_code",
63
- model: "claude-opus-4-8",
64
- startedAt: iso(NOW - DAY),
65
- endedAt: iso(NOW - DAY + HOUR),
66
- inputTokens: 100,
67
- outputTokens: 200,
68
- cacheReadTokens: 0,
69
- cacheWriteTokens: 0,
70
- costUsd: 0.01,
71
- turns: [],
72
- turnCount: 0,
73
- filesTouchedCount: 0,
74
- repoName: "repo",
75
- gitBranch: "main",
76
- ...over,
77
- };
78
- }
79
- function outcomeOf(id) {
80
- return getSessions().find((s) => s.id === id)?.outcome;
81
- }
82
- function emailOf(id) {
83
- return getSessions().find((s) => s.id === id)?.gitAuthorEmail;
84
- }
85
- // ── Tests ─────────────────────────────────────────────────────────────────────
86
- describe("correlateSessions", () => {
87
- it("marks a session shipped when its branch commit merged into main", async () => {
88
- const { dir, git } = await makeRepo("shipped");
89
- await commitAt(git, dir, "base.txt", NOW - 10 * DAY);
90
- await renameToMain(git);
91
- await git.checkoutLocalBranch("feature");
92
- await commitAt(git, dir, "feature.txt", NOW - 9 * DAY);
93
- await git.checkout("main");
94
- await git.merge(["feature"]); // fast-forward — feature becomes reachable from main
95
- const session = makeSession({
96
- workingDir: dir,
97
- gitBranch: "feature",
98
- startedAt: iso(NOW - 9.5 * DAY),
99
- });
100
- upsertSessions([session]);
101
- await correlateSessions(NOW);
102
- expect(outcomeOf(session.sessionId)).toBe("shipped");
103
- expect(emailOf(session.sessionId)).toBe(SESSION_EMAIL);
104
- });
105
- it("ships a commit landed directly on a `master` default branch (not just `main`)", async () => {
106
- // Regression: resolveDefaultBranch must detect `master`, not always pick the
107
- // first candidate. A commit on master is, by definition, on the default branch.
108
- const { dir, git } = await makeRepo("master-default");
109
- await commitAt(git, dir, "base.txt", NOW - 5 * DAY);
110
- await renameTo(git, "master");
111
- await commitAt(git, dir, "work.txt", NOW - 4 * DAY); // committed straight to master
112
- const session = makeSession({
113
- workingDir: dir,
114
- gitBranch: "master",
115
- startedAt: iso(NOW - 4.5 * DAY),
116
- });
117
- upsertSessions([session]);
118
- await correlateSessions(NOW);
119
- expect(outcomeOf(session.sessionId)).toBe("shipped");
120
- });
121
- it("marks a session partial when commits exist, unmerged, and the window has passed", async () => {
122
- const { dir, git } = await makeRepo("partial");
123
- await commitAt(git, dir, "base.txt", NOW - 10 * DAY);
124
- await renameToMain(git);
125
- await git.checkoutLocalBranch("feature");
126
- await commitAt(git, dir, "feature.txt", NOW - 8 * DAY); // never merged
127
- const session = makeSession({
128
- workingDir: dir,
129
- gitBranch: "feature",
130
- startedAt: iso(NOW - 9 * DAY), // 9 days ago → past the 7-day window
131
- });
132
- upsertSessions([session]);
133
- await correlateSessions(NOW);
134
- expect(outcomeOf(session.sessionId)).toBe("partial");
135
- expect(emailOf(session.sessionId)).toBe(SESSION_EMAIL);
136
- });
137
- it("keeps a session unknown when commits exist, unmerged, but still inside the window", async () => {
138
- const { dir, git } = await makeRepo("unknown-fresh");
139
- await commitAt(git, dir, "base.txt", NOW - 2 * DAY);
140
- await renameToMain(git);
141
- await git.checkoutLocalBranch("feature");
142
- await commitAt(git, dir, "feature.txt", NOW - 0.5 * DAY); // unmerged, recent
143
- const session = makeSession({
144
- workingDir: dir,
145
- gitBranch: "feature",
146
- startedAt: iso(NOW - DAY), // 1 day ago → still inside the 7-day window
147
- });
148
- upsertSessions([session]);
149
- await correlateSessions(NOW);
150
- expect(outcomeOf(session.sessionId)).toBe("unknown");
151
- });
152
- it("marks a session abandoned when it ended >24h ago with no commits by its author", async () => {
153
- const { dir, git } = await makeRepo("abandoned");
154
- // Only commit is authored by someone else, so the session's author has none.
155
- await commitAt(git, dir, "base.txt", NOW - 5 * DAY, "Other Dev <other@example.com>");
156
- await renameToMain(git);
157
- const session = makeSession({
158
- workingDir: dir,
159
- gitBranch: "main",
160
- startedAt: iso(NOW - 50 * HOUR),
161
- endedAt: iso(NOW - 48 * HOUR), // ended 2 days ago
162
- });
163
- upsertSessions([session]);
164
- await correlateSessions(NOW);
165
- expect(outcomeOf(session.sessionId)).toBe("abandoned");
166
- // We still record the session's git email even with no attributable commits.
167
- expect(emailOf(session.sessionId)).toBe(SESSION_EMAIL);
168
- });
169
- it("never overrides a scraper-recorded errored outcome", async () => {
170
- const { dir, git } = await makeRepo("errored");
171
- await commitAt(git, dir, "base.txt", NOW - 2 * DAY);
172
- await renameToMain(git);
173
- const session = makeSession({ workingDir: dir, gitBranch: "main" });
174
- upsertSessions([session]);
175
- setSessionCorrelation(session.sessionId, "errored", null); // as the scraper would
176
- await correlateSessions(NOW);
177
- expect(outcomeOf(session.sessionId)).toBe("errored");
178
- });
179
- it("skips a workingDir that is not a git repo, leaving the outcome untouched", async () => {
180
- const notARepo = fs.mkdtempSync(path.join(tmpRoot, "not-a-repo-"));
181
- const session = makeSession({ workingDir: notARepo, gitBranch: "main" });
182
- upsertSessions([session]); // default outcome is "unknown"
183
- await correlateSessions(NOW);
184
- expect(outcomeOf(session.sessionId)).toBe("unknown");
185
- expect(emailOf(session.sessionId)).toBeNull();
186
- });
187
- it("does not crash on a detached HEAD (null branch) — no commits attributable", async () => {
188
- const { dir, git } = await makeRepo("detached");
189
- await commitAt(git, dir, "base.txt", NOW - 3 * DAY);
190
- await renameToMain(git);
191
- const session = makeSession({
192
- workingDir: dir,
193
- gitBranch: null, // detached HEAD captured as no branch
194
- startedAt: iso(NOW - 50 * HOUR),
195
- endedAt: iso(NOW - 48 * HOUR),
196
- });
197
- upsertSessions([session]);
198
- await correlateSessions(NOW);
199
- // No branch ⇒ no commits ⇒ falls through to the time-based abandoned check.
200
- expect(outcomeOf(session.sessionId)).toBe("abandoned");
201
- expect(emailOf(session.sessionId)).toBe(SESSION_EMAIL);
202
- });
203
- });
204
- //# sourceMappingURL=correlate.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"correlate.test.js","sourceRoot":"","sources":["../../src/__tests__/correlate.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=claudeCode.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"claudeCode.test.d.ts","sourceRoot":"","sources":["../../../src/scrapers/__tests__/claudeCode.test.ts"],"names":[],"mappings":""}
@@ -1,115 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import fs from "fs";
3
- import path from "path";
4
- import { fileURLToPath } from "url";
5
- // We test the internal parser by re-exporting it through a thin helper so we
6
- // don't have to call the real scanClaudeCodeLogs() (which hits the filesystem).
7
- // Instead we parse fixture files directly using the same logic.
8
- import { scanClaudeCodeLogs } from "../claudeCode.js";
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
- const fixturesDir = path.join(__dirname, "fixtures");
11
- // Helper: run the parser over a single fixture file using the exported public
12
- // API by monkey-patching os.homedir to point at a temp dir we control.
13
- import os from "os";
14
- import { vi } from "vitest";
15
- function makeFakeHome(sessions) {
16
- const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "planck-test-"));
17
- const projectsDir = path.join(tmp, ".claude", "projects", "test-project");
18
- fs.mkdirSync(projectsDir, { recursive: true });
19
- for (const [name, content] of Object.entries(sessions)) {
20
- fs.writeFileSync(path.join(projectsDir, name), content, "utf-8");
21
- }
22
- return tmp;
23
- }
24
- function readFixture(name) {
25
- return fs.readFileSync(path.join(fixturesDir, name), "utf-8");
26
- }
27
- describe("scanClaudeCodeLogs", () => {
28
- it("parses a single-turn session correctly", async () => {
29
- const content = readFixture("session-single-turn.jsonl");
30
- const fakeHome = makeFakeHome({ "aaaaaaaa-0000-0000-0000-000000000001.jsonl": content });
31
- vi.spyOn(os, "homedir").mockReturnValue(fakeHome);
32
- const sessions = await scanClaudeCodeLogs();
33
- vi.restoreAllMocks();
34
- expect(sessions).toHaveLength(1);
35
- const s = sessions[0];
36
- expect(s.sessionId).toBe("aaaaaaaa-0000-0000-0000-000000000001");
37
- expect(s.tool).toBe("claude_code");
38
- expect(s.model).toBe("claude-sonnet-4-6");
39
- expect(s.turnCount).toBe(1);
40
- expect(s.inputTokens).toBe(1200);
41
- expect(s.outputTokens).toBe(350);
42
- expect(s.cacheWriteTokens).toBe(800);
43
- expect(s.cacheReadTokens).toBe(0);
44
- expect(s.costUsd).toBeGreaterThan(0);
45
- expect(s.startedAt).toBe("2026-05-01T10:00:00.000Z");
46
- expect(s.endedAt).toBe("2026-05-01T10:00:05.000Z");
47
- expect(s.turns).toHaveLength(1);
48
- expect(s.turns[0].sequence).toBe(1);
49
- expect(s.turns[0].model).toBe("claude-sonnet-4-6");
50
- expect(s.turns[0].costUsd).toBeGreaterThan(0);
51
- expect(s.turns[0].cacheReadTokens).toBe(0);
52
- expect(s.turns[0].cacheWriteTokens).toBe(800);
53
- expect(s.turns[0].toolCalls).toHaveLength(0);
54
- expect(s.turns[0].filesTouched).toHaveLength(0);
55
- });
56
- it("deduplicates events sharing the same message.id when counting tokens", async () => {
57
- const content = readFixture("session-multi-turn-tools.jsonl");
58
- const fakeHome = makeFakeHome({ "bbbbbbbb-0000-0000-0000-000000000002.jsonl": content });
59
- vi.spyOn(os, "homedir").mockReturnValue(fakeHome);
60
- const sessions = await scanClaudeCodeLogs();
61
- vi.restoreAllMocks();
62
- expect(sessions).toHaveLength(1);
63
- const s = sessions[0];
64
- expect(s.turnCount).toBe(3); // msg_BBBB0001, msg_BBBB0002, msg_BBBB0003
65
- // msg_BBBB0001 appears in 2 events but tokens should be counted once
66
- expect(s.inputTokens).toBe(2000 + 3000 + 3200);
67
- expect(s.outputTokens).toBe(120 + 200 + 180);
68
- expect(s.cacheReadTokens).toBe(0 + 3500 + 5500);
69
- expect(s.cacheWriteTokens).toBe(1500 + 0 + 0);
70
- });
71
- it("aggregates tool calls and filesTouched per turn", async () => {
72
- const content = readFixture("session-multi-turn-tools.jsonl");
73
- const fakeHome = makeFakeHome({ "bbbbbbbb-0000-0000-0000-000000000002.jsonl": content });
74
- vi.spyOn(os, "homedir").mockReturnValue(fakeHome);
75
- const sessions = await scanClaudeCodeLogs();
76
- vi.restoreAllMocks();
77
- const s = sessions[0];
78
- const turn1 = s.turns.find((t) => t.sequence === 1);
79
- const turn2 = s.turns.find((t) => t.sequence === 2);
80
- const turn3 = s.turns.find((t) => t.sequence === 3);
81
- expect(turn1.toolCalls).toEqual([{ name: "Read", count: 1 }]);
82
- expect(turn1.filesTouched).toEqual(["d:\\projects\\my-app\\src\\index.ts"]);
83
- expect(turn2.toolCalls).toEqual([{ name: "Edit", count: 1 }]);
84
- expect(turn2.filesTouched).toEqual(["d:\\projects\\my-app\\src\\index.ts"]);
85
- expect(turn3.toolCalls).toHaveLength(0);
86
- expect(turn3.filesTouched).toHaveLength(0);
87
- // filesTouchedCount is unique file count across all turns
88
- expect(s.filesTouchedCount).toBe(1);
89
- });
90
- it("skips malformed lines without crashing and deduplicates same message.id", async () => {
91
- const content = readFixture("session-malformed-lines.jsonl");
92
- const fakeHome = makeFakeHome({ "cccccccc-0000-0000-0000-000000000003.jsonl": content });
93
- vi.spyOn(os, "homedir").mockReturnValue(fakeHome);
94
- const sessions = await scanClaudeCodeLogs();
95
- vi.restoreAllMocks();
96
- expect(sessions).toHaveLength(1);
97
- const s = sessions[0];
98
- // msg_CCCC0001 appears twice but tokens counted once
99
- expect(s.inputTokens).toBe(500);
100
- expect(s.outputTokens).toBe(150);
101
- expect(s.cacheReadTokens).toBe(2000);
102
- expect(s.model).toBe("claude-opus-4-8");
103
- // The Bash tool_use is collected (not a file tool, so no filesTouched)
104
- expect(s.turns[0].toolCalls).toEqual([{ name: "Bash", count: 1 }]);
105
- expect(s.turns[0].filesTouched).toHaveLength(0);
106
- expect(s.filesTouchedCount).toBe(0);
107
- });
108
- it("returns empty array when projects dir does not exist", async () => {
109
- vi.spyOn(os, "homedir").mockReturnValue("/nonexistent-path-xyz");
110
- const sessions = await scanClaudeCodeLogs();
111
- vi.restoreAllMocks();
112
- expect(sessions).toEqual([]);
113
- });
114
- });
115
- //# sourceMappingURL=claudeCode.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"claudeCode.test.js","sourceRoot":"","sources":["../../../src/scrapers/__tests__/claudeCode.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=cursor.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cursor.test.d.ts","sourceRoot":"","sources":["../../../src/scrapers/__tests__/cursor.test.ts"],"names":[],"mappings":""}
@@ -1,173 +0,0 @@
1
- import { describe, it, expect, afterEach } from "vitest";
2
- import fs from "fs";
3
- import os from "os";
4
- import path from "path";
5
- import Database from "better-sqlite3";
6
- import { scanCursorLogs } from "../cursor.js";
7
- // Build a throwaway Cursor `state.vscdb` that mirrors the real schema we verified:
8
- // ItemTable["composer.composerHeaders"] + cursorDiskKV["composerData:*"]/["bubbleId:*"].
9
- function makeCursorDb(headers, kv) {
10
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "planck-cursor-"));
11
- const dbPath = path.join(dir, "state.vscdb");
12
- const db = new Database(dbPath);
13
- db.exec("CREATE TABLE ItemTable (key TEXT PRIMARY KEY, value BLOB)");
14
- db.exec("CREATE TABLE cursorDiskKV (key TEXT PRIMARY KEY, value BLOB)");
15
- db.prepare("INSERT INTO ItemTable (key, value) VALUES (?, ?)").run("composer.composerHeaders", JSON.stringify({ allComposers: headers }));
16
- const ins = db.prepare("INSERT INTO cursorDiskKV (key, value) VALUES (?, ?)");
17
- for (const [k, v] of Object.entries(kv))
18
- ins.run(k, JSON.stringify(v));
19
- db.close();
20
- return dbPath;
21
- }
22
- const tempDirs = [];
23
- function track(dbPath) {
24
- tempDirs.push(path.dirname(dbPath));
25
- return dbPath;
26
- }
27
- afterEach(() => {
28
- while (tempDirs.length) {
29
- const d = tempDirs.pop();
30
- try {
31
- fs.rmSync(d, { recursive: true, force: true });
32
- }
33
- catch {
34
- /* ignore */
35
- }
36
- }
37
- });
38
- describe("scanCursorLogs", () => {
39
- it("returns [] when the DB does not exist", () => {
40
- expect(scanCursorLogs(path.join(os.tmpdir(), "does-not-exist-xyz", "state.vscdb"))).toEqual([]);
41
- });
42
- it("parses a composer into a session: groups user prompts into turns, uses filesChangedCount, captures tool calls", () => {
43
- const id = "11111111-1111-1111-1111-111111111111";
44
- const headers = [
45
- {
46
- composerId: id,
47
- name: "Add feature",
48
- createdAt: 1_700_000_000_000,
49
- lastUpdatedAt: 1_700_000_900_000,
50
- filesChangedCount: 7,
51
- workspaceIdentifier: { uri: { fsPath: "/tmp/no-such-repo" } },
52
- },
53
- ];
54
- const order = [
55
- { bubbleId: "b1", type: 1 },
56
- { bubbleId: "b2", type: 2 },
57
- { bubbleId: "b3", type: 2 },
58
- { bubbleId: "b4", type: 1 },
59
- { bubbleId: "b5", type: 2 },
60
- ];
61
- const kv = {
62
- [`composerData:${id}`]: {
63
- composerId: id,
64
- createdAt: 1_700_000_000_000,
65
- modelConfig: { modelName: "default" },
66
- fullConversationHeadersOnly: order,
67
- },
68
- [`bubbleId:${id}:b1`]: { type: 1, createdAt: "2026-05-01T10:00:00.000Z" },
69
- [`bubbleId:${id}:b2`]: {
70
- type: 2,
71
- createdAt: "2026-05-01T10:00:05.000Z",
72
- tokenCount: { inputTokens: 0, outputTokens: 0 },
73
- modelInfo: { modelName: "default" },
74
- toolFormerData: { name: "read_file_v2", params: JSON.stringify({ targetFile: "src/a.ts" }) },
75
- },
76
- [`bubbleId:${id}:b3`]: {
77
- type: 2,
78
- createdAt: "2026-05-01T10:00:09.000Z",
79
- toolFormerData: { name: "edit_file_v2", params: JSON.stringify({ relativeWorkspacePath: "src/a.ts" }) },
80
- },
81
- [`bubbleId:${id}:b4`]: { type: 1, createdAt: "2026-05-01T10:00:20.000Z" },
82
- [`bubbleId:${id}:b5`]: {
83
- type: 2,
84
- createdAt: "2026-05-01T10:00:25.000Z",
85
- toolFormerData: { name: "edit_file_v2", params: JSON.stringify({ relativeWorkspacePath: "src/b.ts" }) },
86
- },
87
- };
88
- const dbPath = track(makeCursorDb(headers, kv));
89
- const sessions = scanCursorLogs(dbPath);
90
- expect(sessions).toHaveLength(1);
91
- const s = sessions[0];
92
- expect(s.sessionId).toBe(`cursor:${id}`);
93
- expect(s.tool).toBe("cursor");
94
- expect(s.model).toBe("default");
95
- // Two user prompts → two exchanges.
96
- expect(s.turnCount).toBe(2);
97
- expect(s.turns).toHaveLength(2);
98
- // Timing comes from the bubbles.
99
- expect(s.startedAt).toBe("2026-05-01T10:00:00.000Z");
100
- expect(s.endedAt).toBe("2026-05-01T10:00:25.000Z");
101
- // Cursor stores no tokens locally → honest zeros, cost $0.
102
- expect(s.inputTokens).toBe(0);
103
- expect(s.outputTokens).toBe(0);
104
- expect(s.costUsd).toBe(0);
105
- // filesTouchedCount is Cursor's authoritative count, not our scrape.
106
- expect(s.filesTouchedCount).toBe(7);
107
- // Tool calls are captured per turn (versioned names recognised).
108
- expect(s.turns[0].toolCalls).toEqual([
109
- { name: "read_file_v2", count: 1 },
110
- { name: "edit_file_v2", count: 1 },
111
- ]);
112
- expect(s.turns[0].filesTouched).toEqual(["src/a.ts"]);
113
- expect(s.turns[1].toolCalls).toEqual([{ name: "edit_file_v2", count: 1 }]);
114
- expect(s.turns[1].filesTouched).toEqual(["src/b.ts"]);
115
- });
116
- it("computes cost when a real model and token counts ARE present (so we don't hardcode 0)", () => {
117
- const id = "22222222-2222-2222-2222-222222222222";
118
- const headers = [{ composerId: id, createdAt: 1, filesChangedCount: 1, workspaceIdentifier: { uri: { fsPath: "/tmp/x" } } }];
119
- const kv = {
120
- [`composerData:${id}`]: {
121
- composerId: id,
122
- modelConfig: { modelName: "claude-sonnet-4-6" },
123
- fullConversationHeadersOnly: [
124
- { bubbleId: "u", type: 1 },
125
- { bubbleId: "a", type: 2 },
126
- ],
127
- },
128
- [`bubbleId:${id}:u`]: { type: 1, createdAt: "2026-05-01T10:00:00.000Z" },
129
- [`bubbleId:${id}:a`]: {
130
- type: 2,
131
- createdAt: "2026-05-01T10:00:05.000Z",
132
- modelInfo: { modelName: "claude-sonnet-4-6" },
133
- tokenCount: { inputTokens: 1000, outputTokens: 500 },
134
- },
135
- };
136
- const dbPath = track(makeCursorDb(headers, kv));
137
- const s = scanCursorLogs(dbPath)[0];
138
- expect(s.inputTokens).toBe(1000);
139
- expect(s.outputTokens).toBe(500);
140
- expect(s.model).toBe("claude-sonnet-4-6");
141
- // sonnet: (1000*3 + 500*15)/1e6 = 0.0105
142
- expect(s.costUsd).toBeCloseTo(0.0105, 6);
143
- });
144
- it("skips empty/placeholder composers that have no user prompt", () => {
145
- const id = "33333333-3333-3333-3333-333333333333";
146
- const headers = [{ composerId: id, createdAt: 1, workspaceIdentifier: { uri: { fsPath: "/tmp/x" } } }];
147
- const kv = {
148
- [`composerData:${id}`]: { composerId: id, fullConversationHeadersOnly: [] },
149
- };
150
- const dbPath = track(makeCursorDb(headers, kv));
151
- expect(scanCursorLogs(dbPath)).toEqual([]);
152
- });
153
- it("resolves repo name and branch from the workspace path's .git", () => {
154
- const id = "44444444-4444-4444-4444-444444444444";
155
- const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), "planck-repo-"));
156
- tempDirs.push(repoDir);
157
- fs.mkdirSync(path.join(repoDir, ".git"), { recursive: true });
158
- fs.writeFileSync(path.join(repoDir, ".git", "HEAD"), "ref: refs/heads/feature-x\n");
159
- const headers = [{ composerId: id, createdAt: 1, filesChangedCount: 0, workspaceIdentifier: { uri: { fsPath: repoDir } } }];
160
- const kv = {
161
- [`composerData:${id}`]: {
162
- composerId: id,
163
- fullConversationHeadersOnly: [{ bubbleId: "u", type: 1 }],
164
- },
165
- [`bubbleId:${id}:u`]: { type: 1, createdAt: "2026-05-01T10:00:00.000Z" },
166
- };
167
- const dbPath = track(makeCursorDb(headers, kv));
168
- const s = scanCursorLogs(dbPath)[0];
169
- expect(s.repoName).toBe(path.basename(repoDir));
170
- expect(s.gitBranch).toBe("feature-x");
171
- });
172
- });
173
- //# sourceMappingURL=cursor.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cursor.test.js","sourceRoot":"","sources":["../../../src/scrapers/__tests__/cursor.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=windsurf.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"windsurf.test.d.ts","sourceRoot":"","sources":["../../../src/scrapers/__tests__/windsurf.test.ts"],"names":[],"mappings":""}
@@ -1,87 +0,0 @@
1
- import { describe, it, expect, afterEach, vi } from "vitest";
2
- import fs from "fs";
3
- import os from "os";
4
- import path from "path";
5
- import Database from "better-sqlite3";
6
- import { scanWindsurfLogs } from "../windsurf.js";
7
- // Windsurf is a VS Code fork: its state DB is an ItemTable(key, value) KV store.
8
- function makeWindsurfDb(items) {
9
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "planck-windsurf-"));
10
- const dbPath = path.join(dir, "state.vscdb");
11
- const db = new Database(dbPath);
12
- db.exec("CREATE TABLE ItemTable (key TEXT PRIMARY KEY, value BLOB)");
13
- const ins = db.prepare("INSERT INTO ItemTable (key, value) VALUES (?, ?)");
14
- for (const [k, v] of Object.entries(items))
15
- ins.run(k, JSON.stringify(v));
16
- db.close();
17
- return dbPath;
18
- }
19
- const tempDirs = [];
20
- function track(p) {
21
- tempDirs.push(path.dirname(p));
22
- return p;
23
- }
24
- afterEach(() => {
25
- vi.restoreAllMocks();
26
- while (tempDirs.length) {
27
- try {
28
- fs.rmSync(tempDirs.pop(), { recursive: true, force: true });
29
- }
30
- catch {
31
- /* ignore */
32
- }
33
- }
34
- });
35
- describe("scanWindsurfLogs", () => {
36
- it("returns [] when no Windsurf DB exists", () => {
37
- expect(scanWindsurfLogs([path.join(os.tmpdir(), "nope-xyz", "state.vscdb")])).toEqual([]);
38
- });
39
- it("parses a recognised Cascade conversation when the schema is present", () => {
40
- const dbPath = track(makeWindsurfDb({
41
- "cascade.conversations": [
42
- {
43
- id: "conv-1",
44
- workspacePath: "/tmp/no-such-repo",
45
- messages: [
46
- { role: "user", createdAt: "2026-05-02T09:00:00.000Z" },
47
- {
48
- role: "assistant",
49
- createdAt: "2026-05-02T09:00:08.000Z",
50
- model: "claude-sonnet-4-6",
51
- usage: { inputTokens: 200, outputTokens: 100 },
52
- },
53
- ],
54
- },
55
- ],
56
- }));
57
- const sessions = scanWindsurfLogs([dbPath]);
58
- expect(sessions).toHaveLength(1);
59
- const s = sessions[0];
60
- expect(s.tool).toBe("windsurf");
61
- expect(s.sessionId).toBe("windsurf:conv-1");
62
- expect(s.turnCount).toBe(1);
63
- expect(s.startedAt).toBe("2026-05-02T09:00:00.000Z");
64
- expect(s.endedAt).toBe("2026-05-02T09:00:08.000Z");
65
- expect(s.model).toBe("claude-sonnet-4-6");
66
- expect(s.inputTokens).toBe(200);
67
- expect(s.outputTokens).toBe(100);
68
- // sonnet pricing: (200*3 + 100*15)/1e6
69
- expect(s.costUsd).toBeCloseTo(0.0021, 6);
70
- });
71
- it("does NOT fabricate sessions when a Cascade-like key exists but the schema is unrecognised", () => {
72
- const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
73
- const dbPath = track(makeWindsurfDb({
74
- // Key matches a Cascade pattern, but the value is not a conversation.
75
- "cascade.settings": { theme: "dark", enabled: true },
76
- }));
77
- expect(scanWindsurfLogs([dbPath])).toEqual([]);
78
- expect(warn).toHaveBeenCalledWith(expect.stringContaining("schema"));
79
- });
80
- it("ignores unrelated keys entirely (no warning, no sessions)", () => {
81
- const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
82
- const dbPath = track(makeWindsurfDb({ "workbench.layout": { sidebar: 200 } }));
83
- expect(scanWindsurfLogs([dbPath])).toEqual([]);
84
- expect(warn).not.toHaveBeenCalled();
85
- });
86
- });
87
- //# sourceMappingURL=windsurf.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"windsurf.test.js","sourceRoot":"","sources":["../../../src/scrapers/__tests__/windsurf.test.ts"],"names":[],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env tsx
2
- export {};
3
- //# sourceMappingURL=scanTest.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"scanTest.d.ts","sourceRoot":"","sources":["../../src/scripts/scanTest.ts"],"names":[],"mappings":""}
@@ -1,146 +0,0 @@
1
- #!/usr/bin/env tsx
2
- import { scanClaudeCodeLogs } from "../scrapers/claudeCode.js";
3
- import { MODEL_PRICING } from "@planckspace/core";
4
- async function main() {
5
- console.log("Scanning ~/.claude/projects/ …\n");
6
- const sessions = await scanClaudeCodeLogs();
7
- if (sessions.length === 0) {
8
- console.log("No sessions found.");
9
- return;
10
- }
11
- const dates = sessions.map((s) => s.startedAt).sort();
12
- const totalInput = sessions.reduce((s, x) => s + x.inputTokens, 0);
13
- const totalOutput = sessions.reduce((s, x) => s + x.outputTokens, 0);
14
- const totalCacheRead = sessions.reduce((s, x) => s + x.cacheReadTokens, 0);
15
- const totalCacheWrite = sessions.reduce((s, x) => s + x.cacheWriteTokens, 0);
16
- const totalCost = sessions.reduce((s, x) => s + x.costUsd, 0);
17
- console.log("─── Summary ──────────────────────────────────────────");
18
- console.log(`Sessions found : ${sessions.length}`);
19
- console.log(`Date range : ${dates[0]?.slice(0, 10)} → ${dates[dates.length - 1]?.slice(0, 10)}`);
20
- console.log(`Input tokens : ${totalInput.toLocaleString()}`);
21
- console.log(`Output tokens : ${totalOutput.toLocaleString()}`);
22
- console.log(`Cache read : ${totalCacheRead.toLocaleString()}`);
23
- console.log(`Cache write : ${totalCacheWrite.toLocaleString()}`);
24
- console.log(`Total cost : $${totalCost.toFixed(4)}`);
25
- console.log("");
26
- // ── Model string audit ────────────────────────────────────────────────────────
27
- // Count every distinct model string seen across all turns and flag any that
28
- // won't resolve to a MODEL_PRICING key (cost would silently be $0 for those).
29
- const modelCounts = new Map();
30
- for (const s of sessions) {
31
- for (const t of s.turns) {
32
- const m = t.model || "(empty)";
33
- modelCounts.set(m, (modelCounts.get(m) ?? 0) + 1);
34
- }
35
- }
36
- const unknownModels = [...modelCounts.keys()].filter((m) => !(m in MODEL_PRICING));
37
- console.log("─── Model string audit ───────────────────────────────");
38
- for (const [m, count] of [...modelCounts.entries()].sort((a, b) => b[1] - a[1])) {
39
- const known = m in MODEL_PRICING;
40
- const flag = known ? " " : "⚠ UNPRICED";
41
- console.log(` ${String(count).padStart(5)} turns ${m} ${flag}`);
42
- }
43
- if (unknownModels.length === 0) {
44
- console.log(" All model strings resolve to a pricing key.");
45
- }
46
- else {
47
- console.log(`\n !! ${unknownModels.length} unpriced model string(s) — those turns cost $0.0000 in the output above.`);
48
- }
49
- console.log("");
50
- // ── $0 session dump ───────────────────────────────────────────────────────────
51
- // For every session with costUsd === 0 that has non-zero tokens (i.e. the $0
52
- // is caused by an unpriced model, not a genuinely empty session), print the
53
- // per-turn model + usage so the broken model ID is visible.
54
- const zeroCostSessions = sessions.filter((s) => s.costUsd === 0 && (s.inputTokens + s.outputTokens + s.cacheReadTokens + s.cacheWriteTokens) > 0);
55
- if (zeroCostSessions.length > 0) {
56
- console.log("─── $0.0000 sessions with tokens (unpriced model) ────");
57
- for (const s of zeroCostSessions) {
58
- console.log(`\n Session : ${s.sessionId}`);
59
- console.log(` Repo : ${s.repoName ?? "(unknown)"}`);
60
- console.log(` Date : ${s.startedAt.slice(0, 10)}`);
61
- console.log(` ${"Seq".padStart(4)} ${"Model".padEnd(30)} ${"Input".padStart(8)} ${"Output".padStart(8)} ${"CacheR".padStart(10)} ${"CacheW".padStart(10)}`);
62
- console.log(` ${"─".repeat(80)}`);
63
- for (const t of s.turns) {
64
- const priced = t.model in MODEL_PRICING ? "" : " ⚠";
65
- console.log(` ${String(t.sequence).padStart(4)} ${(t.model + priced).padEnd(30)} ${t.inputTokens.toLocaleString().padStart(8)} ${t.outputTokens.toLocaleString().padStart(8)} ${t.cacheReadTokens.toLocaleString().padStart(10)} ${t.cacheWriteTokens.toLocaleString().padStart(10)}`);
66
- }
67
- }
68
- console.log("");
69
- }
70
- const modelMap = new Map();
71
- for (const s of sessions) {
72
- for (const t of s.turns) {
73
- const key = t.model || "(unknown)";
74
- const row = modelMap.get(key) ?? { turns: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costUsd: 0 };
75
- row.turns++;
76
- row.inputTokens += t.inputTokens;
77
- row.outputTokens += t.outputTokens;
78
- row.cacheReadTokens += t.cacheReadTokens;
79
- row.cacheWriteTokens += t.cacheWriteTokens;
80
- row.costUsd += t.costUsd;
81
- modelMap.set(key, row);
82
- }
83
- }
84
- const modelRows = [...modelMap.entries()].sort((a, b) => b[1].costUsd - a[1].costUsd);
85
- const W = { model: 28, turns: 8, inp: 14, out: 14, cr: 14, cw: 14, cost: 12 };
86
- console.log("─── Usage by model ───────────────────────────────────────────────────────────────────────────────────");
87
- console.log("Model".padEnd(W.model) +
88
- "Turns".padStart(W.turns) +
89
- "Input".padStart(W.inp) +
90
- "Output".padStart(W.out) +
91
- "Cache read".padStart(W.cr) +
92
- "Cache write".padStart(W.cw) +
93
- "Cost (USD)".padStart(W.cost));
94
- console.log("─".repeat(W.model + W.turns + W.inp + W.out + W.cr + W.cw + W.cost));
95
- for (const [model, r] of modelRows) {
96
- console.log(model.slice(0, W.model).padEnd(W.model) +
97
- String(r.turns).padStart(W.turns) +
98
- r.inputTokens.toLocaleString().padStart(W.inp) +
99
- r.outputTokens.toLocaleString().padStart(W.out) +
100
- r.cacheReadTokens.toLocaleString().padStart(W.cr) +
101
- r.cacheWriteTokens.toLocaleString().padStart(W.cw) +
102
- `$${r.costUsd.toFixed(4)}`.padStart(W.cost));
103
- }
104
- console.log("");
105
- // Top 5 repos by cost
106
- const repoCost = new Map();
107
- for (const s of sessions) {
108
- const key = s.repoName ?? "(unknown)";
109
- repoCost.set(key, (repoCost.get(key) ?? 0) + s.costUsd);
110
- }
111
- const top5 = [...repoCost.entries()]
112
- .sort((a, b) => b[1] - a[1])
113
- .slice(0, 5);
114
- console.log("─── Top 5 repos by cost ──────────────────────────────");
115
- console.log("Repo".padEnd(40) + "Cost (USD)".padStart(12));
116
- console.log("─".repeat(52));
117
- for (const [repo, cost] of top5) {
118
- console.log(repo.slice(0, 40).padEnd(40) + `$${cost.toFixed(4)}`.padStart(12));
119
- }
120
- console.log("");
121
- // Per-session table (last 10)
122
- const recent = [...sessions]
123
- .sort((a, b) => b.startedAt.localeCompare(a.startedAt))
124
- .slice(0, 10);
125
- console.log("─── Recent sessions ──────────────────────────────────");
126
- console.log("Date".padEnd(12) +
127
- "Repo".padEnd(30) +
128
- "Turns".padStart(6) +
129
- "Files".padStart(6) +
130
- "Cost".padStart(10));
131
- console.log("─".repeat(64));
132
- for (const s of recent) {
133
- const date = s.startedAt.slice(0, 10);
134
- const repo = (s.repoName ?? "(unknown)").slice(0, 28);
135
- console.log(date.padEnd(12) +
136
- repo.padEnd(30) +
137
- String(s.turnCount).padStart(6) +
138
- String(s.filesTouchedCount).padStart(6) +
139
- `$${s.costUsd.toFixed(4)}`.padStart(10));
140
- }
141
- }
142
- main().catch((err) => {
143
- console.error(err);
144
- process.exit(1);
145
- });
146
- //# sourceMappingURL=scanTest.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"scanTest.js","sourceRoot":"","sources":["../../src/scripts/scanTest.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=payload.privacy.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"payload.privacy.test.d.ts","sourceRoot":"","sources":["../../../src/sync/__tests__/payload.privacy.test.ts"],"names":[],"mappings":""}
@@ -1,100 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll } from "vitest";
2
- import fs from "fs";
3
- import os from "os";
4
- import path from "path";
5
- import { initDb, upsertSessions, getUnsyncedSessions, closeDb, } from "../../db/store.js";
6
- import { buildTelemetryPayload } from "../payload.js";
7
- // Distinctive markers for the kinds of content that must NEVER leave the machine.
8
- // They are embedded in the session's turns (which become turnsJson in the DB).
9
- const PROMPT_SECRET = "SECRET_PROMPT_how_do_i_exfiltrate_the_database";
10
- const RESPONSE_SECRET = "SECRET_RESPONSE_here_is_the_full_answer_text";
11
- const FILE_CONTENT_SECRET = "SECRET_FILE_CONTENTS_const_apiKey_eq_sk_live_12345";
12
- const SECRETS = [PROMPT_SECRET, RESPONSE_SECRET, FILE_CONTENT_SECRET];
13
- let tmpDir;
14
- beforeAll(() => {
15
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "planck-privacy-"));
16
- initDb(path.join(tmpDir, "local.db"));
17
- });
18
- afterAll(() => {
19
- closeDb();
20
- fs.rmSync(tmpDir, { recursive: true, force: true });
21
- });
22
- function fullyPopulatedSession() {
23
- return {
24
- sessionId: "privacy-test-session-0001",
25
- tool: "claude_code",
26
- model: "claude-opus-4-8",
27
- startedAt: "2026-06-01T10:00:00.000Z",
28
- endedAt: "2026-06-01T10:30:00.000Z",
29
- inputTokens: 1234,
30
- outputTokens: 5678,
31
- cacheReadTokens: 900,
32
- cacheWriteTokens: 100,
33
- costUsd: 0.4242,
34
- workingDir: "/home/dev/secret-project",
35
- turnCount: 2,
36
- filesTouchedCount: 1,
37
- repoName: "secret-project",
38
- gitBranch: "feature/keep-private",
39
- // turnsJson is JSON.stringify(turns) — pack every flavour of secret in here.
40
- turns: [
41
- {
42
- sequence: 1,
43
- model: "claude-opus-4-8",
44
- inputTokens: 1234,
45
- outputTokens: 5678,
46
- cacheReadTokens: 900,
47
- cacheWriteTokens: 100,
48
- costUsd: 0.4242,
49
- toolCalls: [{ name: PROMPT_SECRET, count: 1 }],
50
- filesTouched: [FILE_CONTENT_SECRET, RESPONSE_SECRET],
51
- timestamp: "2026-06-01T10:00:00.000Z",
52
- },
53
- ],
54
- };
55
- }
56
- describe("sync payload privacy guard", () => {
57
- it("never includes prompt/response text, file contents, or turnsJson on the wire", () => {
58
- const session = fullyPopulatedSession();
59
- upsertSessions([session]);
60
- const rows = getUnsyncedSessions();
61
- const row = rows.find((r) => r.id === session.sessionId);
62
- expect(row, "the inserted session should be unsynced").toBeDefined();
63
- const payload = buildTelemetryPayload(row, "ws_real_workspace");
64
- const serialized = JSON.stringify(payload);
65
- // The metadata we DO want is present.
66
- expect(payload.sessionId).toBe(session.sessionId);
67
- expect(payload.costUsd).toBe(session.costUsd);
68
- expect(payload.inputTokens).toBe(session.inputTokens);
69
- expect(payload.workspaceId).toBe("ws_real_workspace");
70
- // The content we must NEVER send is absent.
71
- for (const secret of SECRETS) {
72
- expect(serialized).not.toContain(secret);
73
- }
74
- expect(serialized).not.toContain("turnsJson");
75
- expect(serialized).not.toContain("turns");
76
- expect(serialized).not.toContain("workingDir");
77
- // Belt and suspenders: the payload has exactly the agreed TelemetryPayload keys.
78
- expect(Object.keys(payload).sort()).toEqual([
79
- "cacheReadTokens",
80
- "cacheWriteTokens",
81
- "costUsd",
82
- "endedAt",
83
- "filesTouchedCount",
84
- "gitAuthorEmail",
85
- "gitBranch",
86
- "inputTokens",
87
- "model",
88
- "outcome",
89
- "outputTokens",
90
- "repoName",
91
- "sessionId",
92
- "source",
93
- "startedAt",
94
- "tool",
95
- "turnCount",
96
- "workspaceId",
97
- ].sort());
98
- });
99
- });
100
- //# sourceMappingURL=payload.privacy.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"payload.privacy.test.js","sourceRoot":"","sources":["../../../src/sync/__tests__/payload.privacy.test.ts"],"names":[],"mappings":"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"}