@planckspace/cli 0.0.2 → 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.
Files changed (76) hide show
  1. package/README.md +109 -0
  2. package/dist/commands/init.d.ts +2 -0
  3. package/dist/commands/init.d.ts.map +1 -0
  4. package/dist/commands/init.js +12 -0
  5. package/dist/commands/init.js.map +1 -0
  6. package/dist/commands/login.d.ts +2 -0
  7. package/dist/commands/login.d.ts.map +1 -0
  8. package/dist/commands/login.js +45 -0
  9. package/dist/commands/login.js.map +1 -0
  10. package/dist/commands/logout.d.ts +2 -0
  11. package/dist/commands/logout.d.ts.map +1 -0
  12. package/dist/commands/logout.js +7 -0
  13. package/dist/commands/logout.js.map +1 -0
  14. package/dist/commands/scan.d.ts +5 -0
  15. package/dist/commands/scan.d.ts.map +1 -0
  16. package/dist/commands/scan.js +51 -0
  17. package/dist/commands/scan.js.map +1 -0
  18. package/dist/commands/status.d.ts +2 -0
  19. package/dist/commands/status.d.ts.map +1 -0
  20. package/dist/commands/status.js +31 -0
  21. package/dist/commands/status.js.map +1 -0
  22. package/dist/commands/sync.d.ts +15 -0
  23. package/dist/commands/sync.d.ts.map +1 -0
  24. package/dist/commands/sync.js +77 -0
  25. package/dist/commands/sync.js.map +1 -0
  26. package/dist/config.d.ts +6 -0
  27. package/dist/config.d.ts.map +1 -0
  28. package/dist/config.js +40 -0
  29. package/dist/config.js.map +1 -0
  30. package/dist/correlate.d.ts +21 -0
  31. package/dist/correlate.d.ts.map +1 -0
  32. package/dist/correlate.js +204 -0
  33. package/dist/correlate.js.map +1 -0
  34. package/dist/db/store.d.ts +46 -0
  35. package/dist/db/store.d.ts.map +1 -0
  36. package/dist/db/store.js +210 -0
  37. package/dist/db/store.js.map +1 -0
  38. package/dist/env.d.ts +6 -0
  39. package/dist/env.d.ts.map +1 -0
  40. package/dist/env.js +7 -0
  41. package/dist/env.js.map +1 -0
  42. package/dist/index.d.ts +3 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +97 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/scrapers/claudeCode.d.ts +4 -0
  47. package/dist/scrapers/claudeCode.d.ts.map +1 -0
  48. package/dist/scrapers/claudeCode.js +211 -0
  49. package/dist/scrapers/claudeCode.js.map +1 -0
  50. package/dist/scrapers/cursor.d.ts +9 -0
  51. package/dist/scrapers/cursor.d.ts.map +1 -0
  52. package/dist/scrapers/cursor.js +280 -0
  53. package/dist/scrapers/cursor.js.map +1 -0
  54. package/dist/scrapers/repo.d.ts +12 -0
  55. package/dist/scrapers/repo.d.ts.map +1 -0
  56. package/dist/scrapers/repo.js +40 -0
  57. package/dist/scrapers/repo.js.map +1 -0
  58. package/dist/scrapers/types.d.ts +36 -0
  59. package/dist/scrapers/types.d.ts.map +1 -0
  60. package/dist/scrapers/types.js +5 -0
  61. package/dist/scrapers/types.js.map +1 -0
  62. package/dist/scrapers/windsurf.d.ts +9 -0
  63. package/dist/scrapers/windsurf.d.ts.map +1 -0
  64. package/dist/scrapers/windsurf.js +255 -0
  65. package/dist/scrapers/windsurf.js.map +1 -0
  66. package/dist/sync/payload.d.ts +14 -0
  67. package/dist/sync/payload.d.ts.map +1 -0
  68. package/dist/sync/payload.js +36 -0
  69. package/dist/sync/payload.js.map +1 -0
  70. package/dist/sync/syncEngine.d.ts +16 -0
  71. package/dist/sync/syncEngine.d.ts.map +1 -0
  72. package/dist/sync/syncEngine.js +76 -0
  73. package/dist/sync/syncEngine.js.map +1 -0
  74. package/install.sh +126 -0
  75. package/package.json +39 -7
  76. 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,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"}
@@ -0,0 +1,76 @@
1
+ import { getUnsyncedSessions, markSessionsSynced, recordSyncError, } from "../db/store.js";
2
+ import { buildTelemetryPayload } from "./payload.js";
3
+ const BATCH_SIZE = 100;
4
+ const MAX_RETRIES = 3;
5
+ function chunk(arr, size) {
6
+ const out = [];
7
+ for (let i = 0; i < arr.length; i += size)
8
+ out.push(arr.slice(i, i + size));
9
+ return out;
10
+ }
11
+ function sleep(ms) {
12
+ return new Promise((resolve) => setTimeout(resolve, ms));
13
+ }
14
+ /** A failure worth retrying: no response (network) or a 5xx / 429 from the server. */
15
+ function isTransient(outcome) {
16
+ if (outcome.status === undefined)
17
+ return true;
18
+ return outcome.status >= 500 || outcome.status === 429;
19
+ }
20
+ async function postBatch(deps, payloads) {
21
+ try {
22
+ const res = await fetch(`${deps.apiUrl}/api/ingest`, {
23
+ method: "POST",
24
+ headers: {
25
+ "Content-Type": "application/json",
26
+ Authorization: `Bearer ${deps.token}`,
27
+ },
28
+ body: JSON.stringify(payloads),
29
+ });
30
+ if (res.ok)
31
+ return { ok: true, status: res.status };
32
+ const text = await res.text().catch(() => "");
33
+ return {
34
+ ok: false,
35
+ status: res.status,
36
+ error: `HTTP ${res.status}${text ? `: ${text.slice(0, 200)}` : ""}`,
37
+ };
38
+ }
39
+ catch (err) {
40
+ return { ok: false, error: `network error: ${String(err)}` };
41
+ }
42
+ }
43
+ /**
44
+ * Push all unsynced sessions to {apiUrl}/api/ingest in batches of up to 100.
45
+ * Successful batches are marked synced; failed batches record lastError and stay
46
+ * unsynced for the next run. Transient failures retry with exponential backoff.
47
+ */
48
+ export async function syncOnce(deps) {
49
+ const rows = getUnsyncedSessions();
50
+ if (rows.length === 0)
51
+ return { synced: 0, failed: 0 };
52
+ let synced = 0;
53
+ let failed = 0;
54
+ for (const batch of chunk(rows, BATCH_SIZE)) {
55
+ const ids = batch.map((r) => r.id);
56
+ const payloads = batch.map((r) => buildTelemetryPayload(r, deps.workspaceId));
57
+ let outcome = { ok: false };
58
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
59
+ outcome = await postBatch(deps, payloads);
60
+ if (outcome.ok || !isTransient(outcome) || attempt === MAX_RETRIES)
61
+ break;
62
+ // Exponential backoff: 500ms, 1s, 2s.
63
+ await sleep(500 * 2 ** attempt);
64
+ }
65
+ if (outcome.ok) {
66
+ markSessionsSynced(ids);
67
+ synced += ids.length;
68
+ }
69
+ else {
70
+ recordSyncError(ids, outcome.error ?? "unknown error");
71
+ failed += ids.length;
72
+ }
73
+ }
74
+ return { synced, failed };
75
+ }
76
+ //# sourceMappingURL=syncEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syncEngine.js","sourceRoot":"","sources":["../../src/sync/syncEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,UAAU,GAAG,GAAG,CAAC;AACvB,MAAM,WAAW,GAAG,CAAC,CAAC;AAUtB,SAAS,KAAK,CAAI,GAAQ,EAAE,IAAY;IACtC,MAAM,GAAG,GAAU,EAAE,CAAC;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,IAAI;QAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAC5E,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AASD,sFAAsF;AACtF,SAAS,WAAW,CAAC,OAAoB;IACvC,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC9C,OAAO,OAAO,CAAC,MAAM,IAAI,GAAG,IAAI,OAAO,CAAC,MAAM,KAAK,GAAG,CAAC;AACzD,CAAC;AAED,KAAK,UAAU,SAAS,CACtB,IAAc,EACd,QAAmB;IAEnB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,aAAa,EAAE;YACnD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE;aACtC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;SAC/B,CAAC,CAAC;QACH,IAAI,GAAG,CAAC,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9C,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,KAAK,EAAE,QAAQ,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;SACpE,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,kBAAkB,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;IAC/D,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAc;IAC3C,MAAM,IAAI,GAAG,mBAAmB,EAAE,CAAC;IACnC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IAEvD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,CAAC;QAC5C,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACnC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC/B,qBAAqB,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CAC3C,CAAC;QAEF,IAAI,OAAO,GAAgB,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;QACzC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;YACxD,OAAO,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YAC1C,IAAI,OAAO,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,OAAO,KAAK,WAAW;gBAAE,MAAM;YAC1E,sCAAsC;YACtC,MAAM,KAAK,CAAC,GAAG,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC;QAClC,CAAC;QAED,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;YACf,kBAAkB,CAAC,GAAG,CAAC,CAAC;YACxB,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC;QACvB,CAAC;aAAM,CAAC;YACN,eAAe,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,IAAI,eAAe,CAAC,CAAC;YACvD,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC;QACvB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC"}
package/install.sh ADDED
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env bash
2
+ # PlanckSpace installer — https://planckspace.dev/install
3
+ #
4
+ # Usage:
5
+ # curl -fsSL https://planckspace.dev/install | sh
6
+ # curl -fsSL https://planckspace.dev/install | sh -s -- --token pk_live_xxx
7
+ set -euo pipefail
8
+
9
+ DASHBOARD_URL="https://app.planckspace.dev"
10
+ VSCODE_EXT_URL="https://marketplace.visualstudio.com/items?itemName=planckspace.planckspace"
11
+ PKG="@planckspace/cli"
12
+
13
+ # ── helpers ──────────────────────────────────────────────────────────────────
14
+ say() { printf '\n%s\n' "$*"; }
15
+ info() { printf ' %s\n' "$*"; }
16
+ die() { printf '\nERROR: %s\n\n' "$*" >&2; exit 1; }
17
+
18
+ # ── parse args ───────────────────────────────────────────────────────────────
19
+ TOKEN=""
20
+ while [[ $# -gt 0 ]]; do
21
+ case "$1" in
22
+ --token) TOKEN="${2:-}"; shift 2 ;;
23
+ --token=*) TOKEN="${1#--token=}"; shift ;;
24
+ *) shift ;;
25
+ esac
26
+ done
27
+
28
+ # ── OS / arch check ──────────────────────────────────────────────────────────
29
+ OS="$(uname -s 2>/dev/null || echo unknown)"
30
+ ARCH="$(uname -m 2>/dev/null || echo unknown)"
31
+
32
+ case "$OS" in
33
+ Darwin|Linux) ;;
34
+ MINGW*|MSYS*|CYGWIN*|Windows_NT)
35
+ say "Windows detected."
36
+ say "PlanckSpace requires WSL (Windows Subsystem for Linux)."
37
+ info "1. Open PowerShell as Administrator and run: wsl --install"
38
+ info "2. After WSL is set up, open a WSL terminal and run:"
39
+ info " curl -fsSL https://planckspace.dev/install | sh"
40
+ exit 1
41
+ ;;
42
+ *)
43
+ die "Unsupported OS: $OS. Install manually with: npm install -g $PKG"
44
+ ;;
45
+ esac
46
+
47
+ say "PlanckSpace installer (OS: $OS / arch: $ARCH)"
48
+
49
+ # ── Node.js check ────────────────────────────────────────────────────────────
50
+ node_ok() {
51
+ command -v node >/dev/null 2>&1 || return 1
52
+ local v
53
+ v="$(node --version 2>/dev/null | sed 's/v//' | cut -d. -f1)"
54
+ [[ "${v:-0}" -ge 20 ]]
55
+ }
56
+
57
+ if ! node_ok; then
58
+ say "Node.js 20+ is required but was not found."
59
+ say "Install it with nvm (recommended — no sudo needed):"
60
+ info "curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash"
61
+ info "source \"\${HOME}/.nvm/nvm.sh\""
62
+ info "nvm install 20"
63
+ say "Or download from: https://nodejs.org/en/download"
64
+ say "Then re-run this installer."
65
+ exit 1
66
+ fi
67
+
68
+ NODE_VER="$(node --version)"
69
+ info "Node $NODE_VER found."
70
+
71
+ # ── npm check ────────────────────────────────────────────────────────────────
72
+ command -v npm >/dev/null 2>&1 || die "npm not found. Re-install Node.js from https://nodejs.org"
73
+
74
+ # ── install CLI ──────────────────────────────────────────────────────────────
75
+ say "Installing $PKG..."
76
+ npm install -g "$PKG" --loglevel error
77
+
78
+ # verify the binary is reachable
79
+ command -v planck >/dev/null 2>&1 || {
80
+ # npm global bin might not be in PATH — try to fix
81
+ NPM_BIN="$(npm bin -g 2>/dev/null || true)"
82
+ if [[ -n "$NPM_BIN" && -x "$NPM_BIN/planck" ]]; then
83
+ export PATH="$NPM_BIN:$PATH"
84
+ else
85
+ die "'planck' binary not found after install. Add \`$(npm prefix -g)/bin\` to your PATH."
86
+ fi
87
+ }
88
+
89
+ # ── init ─────────────────────────────────────────────────────────────────────
90
+ say "Initializing ~/.planckspace..."
91
+ planck init
92
+
93
+ # ── login ────────────────────────────────────────────────────────────────────
94
+ CONNECTED=false
95
+ if [[ -n "$TOKEN" ]]; then
96
+ say "Connecting to your workspace..."
97
+ planck login --token "$TOKEN" && CONNECTED=true || true
98
+ else
99
+ say "No token provided."
100
+ info "Visit $DASHBOARD_URL to get your invite token, then run:"
101
+ info " planck login <your-token>"
102
+ fi
103
+
104
+ # ── scan (backfill existing history) ─────────────────────────────────────────
105
+ say "Scanning local AI session history (this is fast)..."
106
+ planck scan
107
+
108
+ # ── sync (only when connected) ───────────────────────────────────────────────
109
+ if [[ "$CONNECTED" == "true" ]]; then
110
+ say "Syncing data to your workspace..."
111
+ planck sync
112
+ fi
113
+
114
+ # ── done ─────────────────────────────────────────────────────────────────────
115
+ say "--------------------------------------"
116
+ say "PlanckSpace is ready."
117
+ say "--------------------------------------"
118
+ if [[ "$CONNECTED" == "true" ]]; then
119
+ info "Dashboard: $DASHBOARD_URL"
120
+ else
121
+ info "Next step: planck login <token>"
122
+ info "Dashboard: $DASHBOARD_URL"
123
+ fi
124
+ info "VS Code extension: $VSCODE_EXT_URL"
125
+ info "Check setup: planck status"
126
+ say ""
package/package.json CHANGED
@@ -1,10 +1,42 @@
1
1
  {
2
2
  "name": "@planckspace/cli",
3
- "version": "0.0.2",
4
- "description": "Planck CLI — write AI rules once, compile to every tool",
5
- "main": "index.js",
6
- "bin": { "planck": "./index.js" },
7
- "keywords": ["ai", "context", "claude", "cursor", "developer-tools"],
8
- "license": "MIT",
9
- "engines": { "node": ">=20" }
3
+ "version": "0.1.1",
4
+ "description": "PlanckSpace CLI — sync AI token usage from your editor to the team ledger",
5
+ "type": "module",
6
+ "bin": {
7
+ "planck": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsx src/index.ts",
12
+ "clean": "rm -rf dist",
13
+ "scan:test": "tsx src/scripts/scanTest.ts",
14
+ "test": "vitest run",
15
+ "prepublishOnly": "npm run build",
16
+ "release": "npm publish"
17
+ },
18
+ "engines": {
19
+ "node": ">=20"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "install.sh"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "dependencies": {
29
+ "@planckspace/core": "^0.1.0",
30
+ "better-sqlite3": "^12.10.0",
31
+ "commander": "^15.0.0",
32
+ "dotenv": "^17.4.2",
33
+ "simple-git": "^3.36.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/better-sqlite3": "^7.6.13",
37
+ "@types/node": "^25.9.1",
38
+ "tsx": "^4.22.4",
39
+ "typescript": "^6.0.3",
40
+ "vitest": "^4.1.7"
41
+ }
10
42
  }
package/index.js DELETED
@@ -1 +0,0 @@
1
- // placeholder