@planckspace/cli 0.0.2 → 0.1.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 +109 -0
- package/dist/__tests__/correlate.test.d.ts +2 -0
- package/dist/__tests__/correlate.test.d.ts.map +1 -0
- package/dist/__tests__/correlate.test.js +204 -0
- package/dist/__tests__/correlate.test.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +12 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +45 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +7 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/scan.d.ts +5 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +51 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +31 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts +15 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +77 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +40 -0
- package/dist/config.js.map +1 -0
- package/dist/correlate.d.ts +21 -0
- package/dist/correlate.d.ts.map +1 -0
- package/dist/correlate.js +204 -0
- package/dist/correlate.js.map +1 -0
- package/dist/db/store.d.ts +46 -0
- package/dist/db/store.d.ts.map +1 -0
- package/dist/db/store.js +210 -0
- package/dist/db/store.js.map +1 -0
- package/dist/env.d.ts +6 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +7 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +97 -0
- package/dist/index.js.map +1 -0
- package/dist/scrapers/__tests__/claudeCode.test.d.ts +2 -0
- package/dist/scrapers/__tests__/claudeCode.test.d.ts.map +1 -0
- package/dist/scrapers/__tests__/claudeCode.test.js +115 -0
- package/dist/scrapers/__tests__/claudeCode.test.js.map +1 -0
- package/dist/scrapers/__tests__/cursor.test.d.ts +2 -0
- package/dist/scrapers/__tests__/cursor.test.d.ts.map +1 -0
- package/dist/scrapers/__tests__/cursor.test.js +173 -0
- package/dist/scrapers/__tests__/cursor.test.js.map +1 -0
- package/dist/scrapers/__tests__/windsurf.test.d.ts +2 -0
- package/dist/scrapers/__tests__/windsurf.test.d.ts.map +1 -0
- package/dist/scrapers/__tests__/windsurf.test.js +87 -0
- package/dist/scrapers/__tests__/windsurf.test.js.map +1 -0
- package/dist/scrapers/claudeCode.d.ts +4 -0
- package/dist/scrapers/claudeCode.d.ts.map +1 -0
- package/dist/scrapers/claudeCode.js +211 -0
- package/dist/scrapers/claudeCode.js.map +1 -0
- package/dist/scrapers/cursor.d.ts +9 -0
- package/dist/scrapers/cursor.d.ts.map +1 -0
- package/dist/scrapers/cursor.js +280 -0
- package/dist/scrapers/cursor.js.map +1 -0
- package/dist/scrapers/repo.d.ts +12 -0
- package/dist/scrapers/repo.d.ts.map +1 -0
- package/dist/scrapers/repo.js +40 -0
- package/dist/scrapers/repo.js.map +1 -0
- package/dist/scrapers/types.d.ts +36 -0
- package/dist/scrapers/types.d.ts.map +1 -0
- package/dist/scrapers/types.js +5 -0
- package/dist/scrapers/types.js.map +1 -0
- package/dist/scrapers/windsurf.d.ts +9 -0
- package/dist/scrapers/windsurf.d.ts.map +1 -0
- package/dist/scrapers/windsurf.js +255 -0
- package/dist/scrapers/windsurf.js.map +1 -0
- package/dist/scripts/scanTest.d.ts +3 -0
- package/dist/scripts/scanTest.d.ts.map +1 -0
- package/dist/scripts/scanTest.js +146 -0
- package/dist/scripts/scanTest.js.map +1 -0
- package/dist/sync/__tests__/payload.privacy.test.d.ts +2 -0
- package/dist/sync/__tests__/payload.privacy.test.d.ts.map +1 -0
- package/dist/sync/__tests__/payload.privacy.test.js +100 -0
- package/dist/sync/__tests__/payload.privacy.test.js.map +1 -0
- package/dist/sync/payload.d.ts +14 -0
- package/dist/sync/payload.d.ts.map +1 -0
- package/dist/sync/payload.js +36 -0
- package/dist/sync/payload.js.map +1 -0
- package/dist/sync/syncEngine.d.ts +16 -0
- package/dist/sync/syncEngine.d.ts.map +1 -0
- package/dist/sync/syncEngine.js +76 -0
- package/dist/sync/syncEngine.js.map +1 -0
- package/install.sh +126 -0
- package/package.json +39 -7
- package/index.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windsurf.d.ts","sourceRoot":"","sources":["../../src/scrapers/windsurf.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAQ,aAAa,EAAE,MAAM,YAAY,CAAC;AAwDtD,mFAAmF;AACnF,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAa1C;AAsJD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,MAAM,EAAsB,GAAG,aAAa,EAAE,CAkEvF"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import Database from "better-sqlite3";
|
|
5
|
+
import { computeCostUsd } from "@planckspace/core";
|
|
6
|
+
import { resolveRepo } from "./repo.js";
|
|
7
|
+
// ── Status: BETA / UNVERIFIED (see docs/COVERAGE.md) ────────────────────────────
|
|
8
|
+
//
|
|
9
|
+
// Windsurf (by Codeium) is a VS Code fork, so it uses the same per-user storage
|
|
10
|
+
// layout as VS Code / Cursor: <appSupport>/Windsurf/User/globalStorage/state.vscdb
|
|
11
|
+
// (an `ItemTable` key-value store), with its "Cascade" agent history kept there.
|
|
12
|
+
//
|
|
13
|
+
// IMPORTANT HONESTY NOTE: no Windsurf installation was available when this scraper
|
|
14
|
+
// was written, so its exact Cascade key names and message schema could NOT be
|
|
15
|
+
// verified against real data the way Cursor's were. This scraper therefore:
|
|
16
|
+
// • locates Windsurf's state DB across macOS / Linux / Windows (incl. "- Next"),
|
|
17
|
+
// • reads ONLY values under keys that plausibly hold Cascade conversations,
|
|
18
|
+
// • extracts only structurally-unambiguous fields (timestamps, message counts,
|
|
19
|
+
// and token/model fields *if present*),
|
|
20
|
+
// • and, crucially, does NOT fabricate sessions: if it finds the DB but cannot
|
|
21
|
+
// recognise a conversation schema, it warns once and returns nothing.
|
|
22
|
+
//
|
|
23
|
+
// On a machine with Windsurf this will either light up or print a clear "schema
|
|
24
|
+
// not recognised" diagnostic to drive the next iteration. We never invent tokens,
|
|
25
|
+
// costs, or models that the data does not contain.
|
|
26
|
+
// Keys under which a Windsurf/Cascade conversation might be stored. Kept narrow on
|
|
27
|
+
// purpose: matching loose patterns against every workbench value would manufacture
|
|
28
|
+
// junk sessions, which is worse than honestly reporting nothing.
|
|
29
|
+
const CASCADE_KEY_PATTERNS = [/cascade/i, /windsurf.*(chat|conversation|session)/i, /codeium.*(chat|conversation)/i];
|
|
30
|
+
// ── DB locations ─────────────────────────────────────────────────────────────────
|
|
31
|
+
/** Candidate Windsurf global-state DB paths for the current OS (stable + Next). */
|
|
32
|
+
export function windsurfDbPaths() {
|
|
33
|
+
const home = os.homedir();
|
|
34
|
+
const flavors = ["Windsurf", "Windsurf - Next"];
|
|
35
|
+
const bases = [];
|
|
36
|
+
if (process.platform === "darwin") {
|
|
37
|
+
for (const f of flavors)
|
|
38
|
+
bases.push(path.join(home, "Library", "Application Support", f));
|
|
39
|
+
}
|
|
40
|
+
else if (process.platform === "win32") {
|
|
41
|
+
const appData = process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
|
|
42
|
+
for (const f of flavors)
|
|
43
|
+
bases.push(path.join(appData, f));
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
for (const f of flavors)
|
|
47
|
+
bases.push(path.join(home, ".config", f));
|
|
48
|
+
}
|
|
49
|
+
return bases.map((b) => path.join(b, "User", "globalStorage", "state.vscdb"));
|
|
50
|
+
}
|
|
51
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────────
|
|
52
|
+
function parseJson(raw) {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(raw);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function toIso(v) {
|
|
61
|
+
if (v == null)
|
|
62
|
+
return null;
|
|
63
|
+
if (typeof v === "number") {
|
|
64
|
+
const d = new Date(v);
|
|
65
|
+
return Number.isNaN(d.getTime()) ? null : d.toISOString();
|
|
66
|
+
}
|
|
67
|
+
const d = new Date(v);
|
|
68
|
+
return Number.isNaN(d.getTime()) ? null : d.toISOString();
|
|
69
|
+
}
|
|
70
|
+
function messageTimestamp(m) {
|
|
71
|
+
return toIso(m.createdAt) ?? toIso(m.timestamp) ?? toIso(m.time);
|
|
72
|
+
}
|
|
73
|
+
function isUserMessage(m) {
|
|
74
|
+
const role = (m.role ?? m.sender ?? "").toString().toLowerCase();
|
|
75
|
+
if (role === "user" || role === "human")
|
|
76
|
+
return true;
|
|
77
|
+
if (m.type === 1 || m.type === "user")
|
|
78
|
+
return true;
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
function messageTokens(m) {
|
|
82
|
+
const tc = m.tokenCount;
|
|
83
|
+
const u = m.usage;
|
|
84
|
+
return {
|
|
85
|
+
input: tc?.inputTokens ?? u?.inputTokens ?? u?.input_tokens ?? 0,
|
|
86
|
+
output: tc?.outputTokens ?? u?.outputTokens ?? u?.output_tokens ?? 0,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** Recognise a value as a Cascade conversation and pull its message list out. */
|
|
90
|
+
function asConversation(value) {
|
|
91
|
+
if (!value || typeof value !== "object")
|
|
92
|
+
return null;
|
|
93
|
+
const obj = value;
|
|
94
|
+
const messages = obj.messages ?? obj.turns ?? obj.bubbles;
|
|
95
|
+
if (!Array.isArray(messages) || messages.length === 0)
|
|
96
|
+
return null;
|
|
97
|
+
// Require at least one message to carry a recognisable timestamp; otherwise we
|
|
98
|
+
// cannot honestly place this session in time, so we decline rather than guess.
|
|
99
|
+
if (!messages.some((m) => messageTimestamp(m) !== null))
|
|
100
|
+
return null;
|
|
101
|
+
return { ...obj, messages };
|
|
102
|
+
}
|
|
103
|
+
function buildSession(conv, fallbackId) {
|
|
104
|
+
const messages = conv.messages ?? [];
|
|
105
|
+
const timestamps = messages
|
|
106
|
+
.map(messageTimestamp)
|
|
107
|
+
.filter((t) => t !== null)
|
|
108
|
+
.sort();
|
|
109
|
+
if (timestamps.length === 0)
|
|
110
|
+
return null;
|
|
111
|
+
let totalInput = 0;
|
|
112
|
+
let totalOutput = 0;
|
|
113
|
+
let totalCost = 0;
|
|
114
|
+
const turns = [];
|
|
115
|
+
let current = null;
|
|
116
|
+
let sessionModel = "unknown";
|
|
117
|
+
const close = () => {
|
|
118
|
+
if (current)
|
|
119
|
+
turns.push(current);
|
|
120
|
+
current = null;
|
|
121
|
+
};
|
|
122
|
+
for (const m of messages) {
|
|
123
|
+
const ts = messageTimestamp(m) ?? "";
|
|
124
|
+
const model = m.model ?? m.modelName ?? sessionModel;
|
|
125
|
+
if (model && model !== "unknown" && sessionModel === "unknown")
|
|
126
|
+
sessionModel = model;
|
|
127
|
+
if (isUserMessage(m)) {
|
|
128
|
+
close();
|
|
129
|
+
current = {
|
|
130
|
+
sequence: turns.length + 1,
|
|
131
|
+
model,
|
|
132
|
+
inputTokens: 0,
|
|
133
|
+
outputTokens: 0,
|
|
134
|
+
cacheReadTokens: 0,
|
|
135
|
+
cacheWriteTokens: 0,
|
|
136
|
+
costUsd: 0,
|
|
137
|
+
toolCalls: [],
|
|
138
|
+
filesTouched: [],
|
|
139
|
+
timestamp: ts,
|
|
140
|
+
};
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (!current) {
|
|
144
|
+
current = {
|
|
145
|
+
sequence: turns.length + 1,
|
|
146
|
+
model,
|
|
147
|
+
inputTokens: 0,
|
|
148
|
+
outputTokens: 0,
|
|
149
|
+
cacheReadTokens: 0,
|
|
150
|
+
cacheWriteTokens: 0,
|
|
151
|
+
costUsd: 0,
|
|
152
|
+
toolCalls: [],
|
|
153
|
+
filesTouched: [],
|
|
154
|
+
timestamp: ts,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const { input, output } = messageTokens(m);
|
|
158
|
+
current.inputTokens += input;
|
|
159
|
+
current.outputTokens += output;
|
|
160
|
+
current.costUsd += computeCostUsd({ inputTokens: input, outputTokens: output, cacheReadTokens: 0, cacheWriteTokens: 0 }, model);
|
|
161
|
+
if (model && model !== "unknown")
|
|
162
|
+
current.model = model;
|
|
163
|
+
totalInput += input;
|
|
164
|
+
totalOutput += output;
|
|
165
|
+
}
|
|
166
|
+
close();
|
|
167
|
+
totalCost = turns.reduce((s, t) => s + t.costUsd, 0);
|
|
168
|
+
const workingDir = conv.workspacePath ?? conv.workspace ?? conv.cwd ?? "";
|
|
169
|
+
const { repoName, gitBranch } = resolveRepo(workingDir);
|
|
170
|
+
const id = conv.id ?? conv.conversationId ?? conv.sessionId ?? fallbackId;
|
|
171
|
+
return {
|
|
172
|
+
sessionId: `windsurf:${id}`,
|
|
173
|
+
tool: "windsurf",
|
|
174
|
+
model: sessionModel,
|
|
175
|
+
startedAt: timestamps[0],
|
|
176
|
+
endedAt: timestamps[timestamps.length - 1],
|
|
177
|
+
inputTokens: totalInput,
|
|
178
|
+
outputTokens: totalOutput,
|
|
179
|
+
cacheReadTokens: 0,
|
|
180
|
+
cacheWriteTokens: 0,
|
|
181
|
+
costUsd: totalCost,
|
|
182
|
+
turns,
|
|
183
|
+
workingDir,
|
|
184
|
+
turnCount: turns.length,
|
|
185
|
+
filesTouchedCount: 0, // not reliably derivable without a verified schema
|
|
186
|
+
repoName,
|
|
187
|
+
gitBranch,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// ── Main entrypoint ─────────────────────────────────────────────────────────────
|
|
191
|
+
/**
|
|
192
|
+
* Scan Windsurf's global state DB(s) for Cascade conversations.
|
|
193
|
+
* @param dbPaths override the DB locations (used by tests). Defaults to OS paths.
|
|
194
|
+
*/
|
|
195
|
+
export function scanWindsurfLogs(dbPaths = windsurfDbPaths()) {
|
|
196
|
+
const existing = dbPaths.filter((p) => fs.existsSync(p));
|
|
197
|
+
if (existing.length === 0)
|
|
198
|
+
return []; // Windsurf not installed — expected, silent.
|
|
199
|
+
const sessions = [];
|
|
200
|
+
for (const dbPath of existing) {
|
|
201
|
+
let db;
|
|
202
|
+
try {
|
|
203
|
+
db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
console.warn(`[windsurf] Cannot open ${dbPath}: ${String(err)} — skipping`);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map((t) => t.name);
|
|
211
|
+
let matchedKeys = 0;
|
|
212
|
+
let recognised = 0;
|
|
213
|
+
for (const table of tables) {
|
|
214
|
+
// Every VS Code KV table we know of is (key TEXT, value BLOB/TEXT).
|
|
215
|
+
const cols = db.prepare(`PRAGMA table_info("${table}")`).all().map((c) => c.name);
|
|
216
|
+
if (!cols.includes("key") || !cols.includes("value"))
|
|
217
|
+
continue;
|
|
218
|
+
const rows = db.prepare(`SELECT key, value FROM "${table}"`).all();
|
|
219
|
+
for (const row of rows) {
|
|
220
|
+
if (!CASCADE_KEY_PATTERNS.some((re) => re.test(row.key)))
|
|
221
|
+
continue;
|
|
222
|
+
matchedKeys++;
|
|
223
|
+
const parsed = parseJson(row.value.toString());
|
|
224
|
+
if (!parsed)
|
|
225
|
+
continue;
|
|
226
|
+
// A value may be a single conversation or an array of them.
|
|
227
|
+
const candidates = Array.isArray(parsed) ? parsed : [parsed];
|
|
228
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
229
|
+
const conv = asConversation(candidates[i]);
|
|
230
|
+
if (!conv)
|
|
231
|
+
continue;
|
|
232
|
+
const session = buildSession(conv, `${row.key}:${i}`);
|
|
233
|
+
if (session) {
|
|
234
|
+
sessions.push(session);
|
|
235
|
+
recognised++;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (matchedKeys > 0 && recognised === 0) {
|
|
241
|
+
console.warn(`[windsurf] Found ${matchedKeys} Cascade-like key(s) in ${dbPath} but none matched a ` +
|
|
242
|
+
`recognised conversation schema. Windsurf support is beta/unverified — please open an ` +
|
|
243
|
+
`issue so we can map its format. No sessions were fabricated.`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
console.warn(`[windsurf] Failed to read ${dbPath}: ${String(err)}`);
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
db.close();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return sessions;
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=windsurf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windsurf.js","sourceRoot":"","sources":["../../src/scrapers/windsurf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanTest.d.ts","sourceRoot":"","sources":["../../src/scripts/scanTest.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,146 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanTest.js","sourceRoot":"","sources":["../../src/scripts/scanTest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payload.privacy.test.d.ts","sourceRoot":"","sources":["../../../src/sync/__tests__/payload.privacy.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,100 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payload.privacy.test.js","sourceRoot":"","sources":["../../../src/sync/__tests__/payload.privacy.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TelemetryPayload } from "@planckspace/core";
|
|
2
|
+
import type { SessionRow } from "../db/store.js";
|
|
3
|
+
/** The shape returned by getUnsyncedSessions() — SessionRow with turnsJson stripped. */
|
|
4
|
+
export type UnsyncedSession = Omit<SessionRow, "turnsJson">;
|
|
5
|
+
/**
|
|
6
|
+
* Build the wire payload for a single session — METADATA ONLY.
|
|
7
|
+
*
|
|
8
|
+
* PRIVACY INVARIANT: this function maps each TelemetryPayload field EXPLICITLY.
|
|
9
|
+
* It never spreads the row and never references turnsJson, prompt text, response
|
|
10
|
+
* text, or file contents. Anything not listed below is, by construction, not sent.
|
|
11
|
+
* The privacy guard test (payload.privacy.test.ts) enforces this and must always pass.
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildTelemetryPayload(row: UnsyncedSession, workspaceId: string): TelemetryPayload;
|
|
14
|
+
//# sourceMappingURL=payload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../src/sync/payload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,wFAAwF;AACxF,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;AAE5D;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,eAAe,EACpB,WAAW,EAAE,MAAM,GAClB,gBAAgB,CA0BlB"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the wire payload for a single session — METADATA ONLY.
|
|
3
|
+
*
|
|
4
|
+
* PRIVACY INVARIANT: this function maps each TelemetryPayload field EXPLICITLY.
|
|
5
|
+
* It never spreads the row and never references turnsJson, prompt text, response
|
|
6
|
+
* text, or file contents. Anything not listed below is, by construction, not sent.
|
|
7
|
+
* The privacy guard test (payload.privacy.test.ts) enforces this and must always pass.
|
|
8
|
+
*/
|
|
9
|
+
export function buildTelemetryPayload(row, workspaceId) {
|
|
10
|
+
return {
|
|
11
|
+
sessionId: row.id,
|
|
12
|
+
// The DB only ever holds scraper-sourced Claude Code sessions today; the cast
|
|
13
|
+
// narrows the stored string to the core enum without widening the wire contract.
|
|
14
|
+
tool: row.tool,
|
|
15
|
+
model: row.model,
|
|
16
|
+
startedAt: row.startedAt,
|
|
17
|
+
endedAt: row.endedAt,
|
|
18
|
+
inputTokens: row.inputTokens,
|
|
19
|
+
outputTokens: row.outputTokens,
|
|
20
|
+
cacheReadTokens: row.cacheReadTokens,
|
|
21
|
+
cacheWriteTokens: row.cacheWriteTokens,
|
|
22
|
+
costUsd: row.costUsd,
|
|
23
|
+
// Backend schema requires a non-empty repoName.
|
|
24
|
+
repoName: row.repoName ?? "unknown",
|
|
25
|
+
gitBranch: row.gitBranch,
|
|
26
|
+
// Recorded by git correlation (the repo's `user.email`), not from prompt
|
|
27
|
+
// content. May differ from the login email — the cloud maps it to a member.
|
|
28
|
+
gitAuthorEmail: row.gitAuthorEmail,
|
|
29
|
+
outcome: row.outcome,
|
|
30
|
+
turnCount: row.turnCount,
|
|
31
|
+
filesTouchedCount: row.filesTouchedCount,
|
|
32
|
+
source: "scraper",
|
|
33
|
+
workspaceId,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=payload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payload.js","sourceRoot":"","sources":["../../src/sync/payload.ts"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CACnC,GAAoB,EACpB,WAAmB;IAEnB,OAAO;QACL,SAAS,EAAE,GAAG,CAAC,EAAE;QACjB,8EAA8E;QAC9E,iFAAiF;QACjF,IAAI,EAAE,GAAG,CAAC,IAAgC;QAC1C,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,eAAe,EAAE,GAAG,CAAC,eAAe;QACpC,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;QACtC,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,gDAAgD;QAChD,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,SAAS;QACnC,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,yEAAyE;QACzE,4EAA4E;QAC5E,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,OAAO,EAAE,GAAG,CAAC,OAAsC;QACnD,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;QACxC,MAAM,EAAE,SAAS;QACjB,WAAW;KACZ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type SyncDeps = {
|
|
2
|
+
apiUrl: string;
|
|
3
|
+
token: string;
|
|
4
|
+
workspaceId: string;
|
|
5
|
+
};
|
|
6
|
+
export type SyncResult = {
|
|
7
|
+
synced: number;
|
|
8
|
+
failed: number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Push all unsynced sessions to {apiUrl}/api/ingest in batches of up to 100.
|
|
12
|
+
* Successful batches are marked synced; failed batches record lastError and stay
|
|
13
|
+
* unsynced for the next run. Transient failures retry with exponential backoff.
|
|
14
|
+
*/
|
|
15
|
+
export declare function syncOnce(deps: SyncDeps): Promise<SyncResult>;
|
|
16
|
+
//# sourceMappingURL=syncEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncEngine.d.ts","sourceRoot":"","sources":["../../src/sync/syncEngine.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAkD5D;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CA+BlE"}
|