@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.
- package/README.md +109 -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/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/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,204 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { simpleGit } from "simple-git";
|
|
3
|
+
import { getSessionsForCorrelation, setSessionCorrelation, } from "./db/store.js";
|
|
4
|
+
// ── Heuristics & time windows ───────────────────────────────────────────────
|
|
5
|
+
// All correlation thresholds live here. Outcomes are recomputed on every run
|
|
6
|
+
// (branches merge later, sessions age past a window), so these are read fresh
|
|
7
|
+
// each time rather than baked into a stored verdict.
|
|
8
|
+
/** A session-branch commit that reaches the default branch within this many days = shipped. */
|
|
9
|
+
export const SHIPPED_MERGE_WINDOW_DAYS = 7;
|
|
10
|
+
/** A session that ended this long ago with no commits by its author = abandoned. */
|
|
11
|
+
export const ABANDONED_AFTER_HOURS = 24;
|
|
12
|
+
/** While a session is still this fresh and undetermined, we hold off and call it unknown. */
|
|
13
|
+
export const UNKNOWN_WINDOW_DAYS = 7;
|
|
14
|
+
/** Branch names treated as the repo's default/trunk, in priority order. */
|
|
15
|
+
export const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
16
|
+
const HOUR_MS = 60 * 60 * 1000;
|
|
17
|
+
const DAY_MS = 24 * HOUR_MS;
|
|
18
|
+
// Unit separator keeps the format unambiguous even if an author name contains spaces/pipes.
|
|
19
|
+
const FIELD_SEP = "\x1f";
|
|
20
|
+
const COMMIT_FORMAT = ["%H", "%ae", "%aI", "%cI"].join(FIELD_SEP);
|
|
21
|
+
// ── Low-level git probes (each fails closed, never throws) ───────────────────
|
|
22
|
+
async function isGitRepo(git) {
|
|
23
|
+
try {
|
|
24
|
+
return await git.checkIsRepo();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** The repo-local `git config user.email`, or null if unset/unreadable. */
|
|
31
|
+
async function getAuthorEmail(git) {
|
|
32
|
+
try {
|
|
33
|
+
const cfg = await git.getConfig("user.email");
|
|
34
|
+
const value = cfg.value?.trim();
|
|
35
|
+
return value ? value : null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function resolveDefaultBranch(git) {
|
|
42
|
+
// Enumerate real local branches rather than probing each candidate with
|
|
43
|
+
// `rev-parse --verify --quiet`: simple-git resolves that command even when it
|
|
44
|
+
// exits non-zero on a missing ref, so the probe would always "find" the first
|
|
45
|
+
// candidate. branchLocal reports what actually exists.
|
|
46
|
+
let names;
|
|
47
|
+
try {
|
|
48
|
+
names = (await git.branchLocal()).all;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null; // no branches (e.g. a repo with no commits)
|
|
52
|
+
}
|
|
53
|
+
for (const name of DEFAULT_BRANCH_CANDIDATES) {
|
|
54
|
+
if (names.includes(name))
|
|
55
|
+
return name;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Commits reachable from `branch`. Returns [] for any failure mode we expect to
|
|
61
|
+
* hit in the wild: missing/detached branch, shallow clone, a repo with no commits.
|
|
62
|
+
*/
|
|
63
|
+
async function commitsOnBranch(git, branch) {
|
|
64
|
+
let raw;
|
|
65
|
+
try {
|
|
66
|
+
raw = await git.raw(["log", branch, `--pretty=format:${COMMIT_FORMAT}`]);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
const commits = [];
|
|
72
|
+
for (const line of raw.split("\n")) {
|
|
73
|
+
const trimmed = line.trim();
|
|
74
|
+
if (!trimmed)
|
|
75
|
+
continue;
|
|
76
|
+
const [hash, authorEmail, authorDate, committerDate] = trimmed.split(FIELD_SEP);
|
|
77
|
+
if (!hash)
|
|
78
|
+
continue;
|
|
79
|
+
commits.push({
|
|
80
|
+
hash,
|
|
81
|
+
authorEmail: authorEmail ?? "",
|
|
82
|
+
authorDate: authorDate ?? "",
|
|
83
|
+
committerDate: committerDate ?? "",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return commits;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The set of commit hashes reachable from `ref` — i.e. everything that has
|
|
90
|
+
* landed on that branch. A commit is "merged into default" iff its hash is in
|
|
91
|
+
* this set. (We can't use `merge-base --is-ancestor`: simple-git resolves it
|
|
92
|
+
* regardless of the exit code that carries the actual answer.)
|
|
93
|
+
*/
|
|
94
|
+
async function reachableFrom(git, ref) {
|
|
95
|
+
try {
|
|
96
|
+
const raw = await git.raw(["rev-list", ref]);
|
|
97
|
+
const hashes = raw
|
|
98
|
+
.split("\n")
|
|
99
|
+
.map((h) => h.trim())
|
|
100
|
+
.filter(Boolean);
|
|
101
|
+
return new Set(hashes);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return new Set();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// ── Classification ───────────────────────────────────────────────────────────
|
|
108
|
+
/**
|
|
109
|
+
* Decide the outcome for one session. Pure with respect to `now` so tests can
|
|
110
|
+
* pin the clock; all real git state comes through `git`.
|
|
111
|
+
*/
|
|
112
|
+
async function classify(git, session, email, now) {
|
|
113
|
+
// A non-zero exit / error captured by the scraper is authoritative — git
|
|
114
|
+
// history can't un-error a crashed session, so we never override it.
|
|
115
|
+
if (session.outcome === "errored")
|
|
116
|
+
return "errored";
|
|
117
|
+
const startedMs = Date.parse(session.startedAt);
|
|
118
|
+
const endedMs = Date.parse(session.endedAt);
|
|
119
|
+
// Commits by this session's git author, on its branch, after it began.
|
|
120
|
+
// No branch (detached HEAD) or no known email ⇒ nothing we can attribute.
|
|
121
|
+
let sessionCommits = [];
|
|
122
|
+
if (session.gitBranch && email) {
|
|
123
|
+
const emailLc = email.toLowerCase();
|
|
124
|
+
const branchCommits = await commitsOnBranch(git, session.gitBranch);
|
|
125
|
+
sessionCommits = branchCommits.filter((c) => {
|
|
126
|
+
if (c.authorEmail.toLowerCase() !== emailLc)
|
|
127
|
+
return false;
|
|
128
|
+
const authoredMs = Date.parse(c.authorDate);
|
|
129
|
+
// Keep the commit if we can't compare dates; better to over-attribute
|
|
130
|
+
// than to silently drop a real session commit.
|
|
131
|
+
if (Number.isNaN(authoredMs) || Number.isNaN(startedMs))
|
|
132
|
+
return true;
|
|
133
|
+
return authoredMs >= startedMs;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (sessionCommits.length > 0) {
|
|
137
|
+
const defaultBranch = await resolveDefaultBranch(git);
|
|
138
|
+
if (defaultBranch) {
|
|
139
|
+
const merged = await reachableFrom(git, defaultBranch);
|
|
140
|
+
for (const commit of sessionCommits) {
|
|
141
|
+
if (!merged.has(commit.hash))
|
|
142
|
+
continue;
|
|
143
|
+
const mergedMs = Date.parse(commit.committerDate);
|
|
144
|
+
const withinWindow = Number.isNaN(mergedMs) || Number.isNaN(startedMs)
|
|
145
|
+
? true
|
|
146
|
+
: mergedMs - startedMs <= SHIPPED_MERGE_WINDOW_DAYS * DAY_MS;
|
|
147
|
+
if (withinWindow)
|
|
148
|
+
return "shipped";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Committed but not merged into default (within window). Hold the verdict
|
|
152
|
+
// open while we're still inside the window — it may yet ship.
|
|
153
|
+
if (!Number.isNaN(startedMs) && now - startedMs <= UNKNOWN_WINDOW_DAYS * DAY_MS) {
|
|
154
|
+
return "unknown";
|
|
155
|
+
}
|
|
156
|
+
return "partial";
|
|
157
|
+
}
|
|
158
|
+
// No attributable commits. Once enough time has passed with nothing produced,
|
|
159
|
+
// call it abandoned; otherwise it's too early to say.
|
|
160
|
+
if (!Number.isNaN(endedMs) && now - endedMs > ABANDONED_AFTER_HOURS * HOUR_MS) {
|
|
161
|
+
return "abandoned";
|
|
162
|
+
}
|
|
163
|
+
return "unknown";
|
|
164
|
+
}
|
|
165
|
+
// ── Entry point ───────────────────────────────────────────────────────────────
|
|
166
|
+
/**
|
|
167
|
+
* Correlate every stored session that has a repoName + workingDir against its
|
|
168
|
+
* git repo, recording the git author email and a recomputed outcome.
|
|
169
|
+
*
|
|
170
|
+
* Safe to re-run: outcomes are derived fresh each pass, and any repo that's
|
|
171
|
+
* gone missing, isn't a repo anymore, or otherwise misbehaves is skipped
|
|
172
|
+
* without aborting the rest. `now` is injectable for testing.
|
|
173
|
+
*/
|
|
174
|
+
export async function correlateSessions(now = Date.now()) {
|
|
175
|
+
const sessions = getSessionsForCorrelation();
|
|
176
|
+
let updated = 0;
|
|
177
|
+
for (const session of sessions) {
|
|
178
|
+
if (!session.repoName || !session.workingDir)
|
|
179
|
+
continue;
|
|
180
|
+
if (!fs.existsSync(session.workingDir))
|
|
181
|
+
continue;
|
|
182
|
+
let git;
|
|
183
|
+
try {
|
|
184
|
+
git = simpleGit(session.workingDir);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (!(await isGitRepo(git)))
|
|
190
|
+
continue; // workingDir is no longer a git repo
|
|
191
|
+
try {
|
|
192
|
+
const email = await getAuthorEmail(git);
|
|
193
|
+
const outcome = await classify(git, session, email, now);
|
|
194
|
+
setSessionCorrelation(session.id, outcome, email);
|
|
195
|
+
updated++;
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// One bad repo must never take down the whole correlation pass.
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { updated };
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=correlate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlate.js","sourceRoot":"","sources":["../src/correlate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ParsedSession } from "../scrapers/claudeCode.js";
|
|
2
|
+
export declare const PLANCKSPACE_DIR: string;
|
|
3
|
+
export declare const DB_PATH: string;
|
|
4
|
+
export type SessionRow = {
|
|
5
|
+
id: string;
|
|
6
|
+
tool: string;
|
|
7
|
+
model: string;
|
|
8
|
+
startedAt: string;
|
|
9
|
+
endedAt: string;
|
|
10
|
+
inputTokens: number;
|
|
11
|
+
outputTokens: number;
|
|
12
|
+
cacheReadTokens: number;
|
|
13
|
+
cacheWriteTokens: number;
|
|
14
|
+
costUsd: number;
|
|
15
|
+
repoName: string | null;
|
|
16
|
+
gitBranch: string | null;
|
|
17
|
+
gitAuthorEmail: string | null;
|
|
18
|
+
workingDir: string;
|
|
19
|
+
turnCount: number;
|
|
20
|
+
filesTouchedCount: number;
|
|
21
|
+
outcome: string;
|
|
22
|
+
turnsJson: string;
|
|
23
|
+
scrapedAt: string;
|
|
24
|
+
};
|
|
25
|
+
/** The subset of a session needed to correlate it against its git repo. */
|
|
26
|
+
export type CorrelationSessionRow = Pick<SessionRow, "id" | "repoName" | "gitBranch" | "workingDir" | "startedAt" | "endedAt" | "outcome">;
|
|
27
|
+
export declare function initDb(dbPath?: string): void;
|
|
28
|
+
/** Close the database handle (mainly for tests; releases the file lock on Windows). */
|
|
29
|
+
export declare function closeDb(): void;
|
|
30
|
+
export declare function upsertSessions(sessions: ParsedSession[]): {
|
|
31
|
+
added: number;
|
|
32
|
+
updated: number;
|
|
33
|
+
};
|
|
34
|
+
export declare function getSessions(): SessionRow[];
|
|
35
|
+
export declare function getUnsyncedSessions(): Omit<SessionRow, "turnsJson">[];
|
|
36
|
+
/** Sessions to feed the git correlator — only the fields it needs to classify outcomes. */
|
|
37
|
+
export declare function getSessionsForCorrelation(): CorrelationSessionRow[];
|
|
38
|
+
/** Persist a session's correlated outcome and the git author email it was attributed to. */
|
|
39
|
+
export declare function setSessionCorrelation(id: string, outcome: string, gitAuthorEmail: string | null): void;
|
|
40
|
+
/** Mark sessions as successfully synced (sets syncedAt, clears lastError). */
|
|
41
|
+
export declare function markSessionsSynced(ids: string[]): void;
|
|
42
|
+
/** Record a sync failure; leaves syncedAt NULL so the rows retry next run. */
|
|
43
|
+
export declare function recordSyncError(ids: string[], error: string): void;
|
|
44
|
+
export declare function setMeta(key: string, value: string): void;
|
|
45
|
+
export declare function getMeta(key: string): string | null;
|
|
46
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/db/store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,eAAO,MAAM,eAAe,QAA0C,CAAC;AACvE,eAAO,MAAM,OAAO,QAAyC,CAAC;AAE9D,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,2EAA2E;AAC3E,MAAM,MAAM,qBAAqB,GAAG,IAAI,CACtC,UAAU,EACV,IAAI,GAAG,UAAU,GAAG,WAAW,GAAG,YAAY,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,CACrF,CAAC;AASF,wBAAgB,MAAM,CAAC,MAAM,SAAU,GAAG,IAAI,CAqD7C;AAED,uFAAuF;AACvF,wBAAgB,OAAO,IAAI,IAAI,CAK9B;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAqE5F;AAED,wBAAgB,WAAW,IAAI,UAAU,EAAE,CAI1C;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,EAAE,CAarE;AAED,2FAA2F;AAC3F,wBAAgB,yBAAyB,IAAI,qBAAqB,EAAE,CAOnE;AAED,4FAA4F;AAC5F,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,GAAG,IAAI,GAC5B,IAAI,CAIN;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,CAetD;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAalE;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAOxD;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKlD"}
|
package/dist/db/store.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
export const PLANCKSPACE_DIR = path.join(os.homedir(), ".planckspace");
|
|
6
|
+
export const DB_PATH = path.join(PLANCKSPACE_DIR, "local.db");
|
|
7
|
+
let _db = null;
|
|
8
|
+
function getDb() {
|
|
9
|
+
if (!_db)
|
|
10
|
+
throw new Error("Database not initialized — run `planck init` first");
|
|
11
|
+
return _db;
|
|
12
|
+
}
|
|
13
|
+
export function initDb(dbPath = DB_PATH) {
|
|
14
|
+
if (_db)
|
|
15
|
+
return;
|
|
16
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true, mode: 0o700 });
|
|
17
|
+
const db = new Database(dbPath);
|
|
18
|
+
db.pragma("journal_mode = WAL");
|
|
19
|
+
db.pragma("foreign_keys = ON");
|
|
20
|
+
db.exec(`
|
|
21
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
22
|
+
id TEXT PRIMARY KEY,
|
|
23
|
+
tool TEXT NOT NULL,
|
|
24
|
+
model TEXT NOT NULL,
|
|
25
|
+
startedAt TEXT NOT NULL,
|
|
26
|
+
endedAt TEXT NOT NULL,
|
|
27
|
+
inputTokens INTEGER NOT NULL DEFAULT 0,
|
|
28
|
+
outputTokens INTEGER NOT NULL DEFAULT 0,
|
|
29
|
+
cacheReadTokens INTEGER NOT NULL DEFAULT 0,
|
|
30
|
+
cacheWriteTokens INTEGER NOT NULL DEFAULT 0,
|
|
31
|
+
costUsd REAL NOT NULL DEFAULT 0,
|
|
32
|
+
repoName TEXT,
|
|
33
|
+
gitBranch TEXT,
|
|
34
|
+
gitAuthorEmail TEXT,
|
|
35
|
+
workingDir TEXT NOT NULL DEFAULT '',
|
|
36
|
+
turnCount INTEGER NOT NULL DEFAULT 0,
|
|
37
|
+
filesTouchedCount INTEGER NOT NULL DEFAULT 0,
|
|
38
|
+
outcome TEXT NOT NULL DEFAULT 'unknown',
|
|
39
|
+
turnsJson TEXT NOT NULL DEFAULT '[]',
|
|
40
|
+
scrapedAt TEXT NOT NULL
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
CREATE TABLE IF NOT EXISTS sync_state (
|
|
44
|
+
sessionId TEXT PRIMARY KEY,
|
|
45
|
+
syncedAt TEXT,
|
|
46
|
+
lastError TEXT
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
50
|
+
key TEXT PRIMARY KEY,
|
|
51
|
+
value TEXT NOT NULL
|
|
52
|
+
);
|
|
53
|
+
`);
|
|
54
|
+
// Migration: databases created before git correlation lack gitAuthorEmail.
|
|
55
|
+
const columns = db
|
|
56
|
+
.prepare("PRAGMA table_info(sessions)")
|
|
57
|
+
.all();
|
|
58
|
+
if (!columns.some((c) => c.name === "gitAuthorEmail")) {
|
|
59
|
+
db.exec("ALTER TABLE sessions ADD COLUMN gitAuthorEmail TEXT");
|
|
60
|
+
}
|
|
61
|
+
_db = db;
|
|
62
|
+
}
|
|
63
|
+
/** Close the database handle (mainly for tests; releases the file lock on Windows). */
|
|
64
|
+
export function closeDb() {
|
|
65
|
+
if (_db) {
|
|
66
|
+
_db.close();
|
|
67
|
+
_db = null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function upsertSessions(sessions) {
|
|
71
|
+
const db = getDb();
|
|
72
|
+
let added = 0;
|
|
73
|
+
let updated = 0;
|
|
74
|
+
const now = new Date().toISOString();
|
|
75
|
+
const checkStmt = db.prepare("SELECT id FROM sessions WHERE id = ?");
|
|
76
|
+
const upsertStmt = db.prepare(`
|
|
77
|
+
INSERT INTO sessions (
|
|
78
|
+
id, tool, model, startedAt, endedAt,
|
|
79
|
+
inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, costUsd,
|
|
80
|
+
repoName, gitBranch, workingDir, turnCount, filesTouchedCount,
|
|
81
|
+
outcome, turnsJson, scrapedAt
|
|
82
|
+
) VALUES (
|
|
83
|
+
?, ?, ?, ?, ?,
|
|
84
|
+
?, ?, ?, ?, ?,
|
|
85
|
+
?, ?, ?, ?, ?,
|
|
86
|
+
'unknown', ?, ?
|
|
87
|
+
)
|
|
88
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
89
|
+
tool = excluded.tool,
|
|
90
|
+
model = excluded.model,
|
|
91
|
+
startedAt = excluded.startedAt,
|
|
92
|
+
endedAt = excluded.endedAt,
|
|
93
|
+
inputTokens = excluded.inputTokens,
|
|
94
|
+
outputTokens = excluded.outputTokens,
|
|
95
|
+
cacheReadTokens = excluded.cacheReadTokens,
|
|
96
|
+
cacheWriteTokens = excluded.cacheWriteTokens,
|
|
97
|
+
costUsd = excluded.costUsd,
|
|
98
|
+
repoName = excluded.repoName,
|
|
99
|
+
gitBranch = excluded.gitBranch,
|
|
100
|
+
workingDir = excluded.workingDir,
|
|
101
|
+
turnCount = excluded.turnCount,
|
|
102
|
+
filesTouchedCount = excluded.filesTouchedCount,
|
|
103
|
+
turnsJson = excluded.turnsJson,
|
|
104
|
+
scrapedAt = excluded.scrapedAt
|
|
105
|
+
`);
|
|
106
|
+
const syncStmt = db.prepare(`
|
|
107
|
+
INSERT OR IGNORE INTO sync_state (sessionId, syncedAt, lastError)
|
|
108
|
+
VALUES (?, NULL, NULL)
|
|
109
|
+
`);
|
|
110
|
+
const run = db.transaction(() => {
|
|
111
|
+
for (const session of sessions) {
|
|
112
|
+
const existing = checkStmt.get(session.sessionId);
|
|
113
|
+
if (existing) {
|
|
114
|
+
updated++;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
added++;
|
|
118
|
+
}
|
|
119
|
+
upsertStmt.run(session.sessionId, session.tool, session.model, session.startedAt, session.endedAt, session.inputTokens, session.outputTokens, session.cacheReadTokens, session.cacheWriteTokens, session.costUsd, session.repoName, session.gitBranch, session.workingDir, session.turnCount, session.filesTouchedCount, JSON.stringify(session.turns), now);
|
|
120
|
+
syncStmt.run(session.sessionId);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
run();
|
|
124
|
+
return { added, updated };
|
|
125
|
+
}
|
|
126
|
+
export function getSessions() {
|
|
127
|
+
return getDb()
|
|
128
|
+
.prepare("SELECT * FROM sessions ORDER BY startedAt DESC")
|
|
129
|
+
.all();
|
|
130
|
+
}
|
|
131
|
+
export function getUnsyncedSessions() {
|
|
132
|
+
return getDb()
|
|
133
|
+
.prepare(`
|
|
134
|
+
SELECT s.id, s.tool, s.model, s.startedAt, s.endedAt,
|
|
135
|
+
s.inputTokens, s.outputTokens, s.cacheReadTokens, s.cacheWriteTokens,
|
|
136
|
+
s.costUsd, s.repoName, s.gitBranch, s.gitAuthorEmail, s.workingDir,
|
|
137
|
+
s.turnCount, s.filesTouchedCount, s.outcome, s.scrapedAt
|
|
138
|
+
FROM sessions s
|
|
139
|
+
LEFT JOIN sync_state ss ON s.id = ss.sessionId
|
|
140
|
+
WHERE ss.syncedAt IS NULL
|
|
141
|
+
ORDER BY s.startedAt DESC
|
|
142
|
+
`)
|
|
143
|
+
.all();
|
|
144
|
+
}
|
|
145
|
+
/** Sessions to feed the git correlator — only the fields it needs to classify outcomes. */
|
|
146
|
+
export function getSessionsForCorrelation() {
|
|
147
|
+
return getDb()
|
|
148
|
+
.prepare(`
|
|
149
|
+
SELECT id, repoName, gitBranch, workingDir, startedAt, endedAt, outcome
|
|
150
|
+
FROM sessions
|
|
151
|
+
`)
|
|
152
|
+
.all();
|
|
153
|
+
}
|
|
154
|
+
/** Persist a session's correlated outcome and the git author email it was attributed to. */
|
|
155
|
+
export function setSessionCorrelation(id, outcome, gitAuthorEmail) {
|
|
156
|
+
getDb()
|
|
157
|
+
.prepare("UPDATE sessions SET outcome = ?, gitAuthorEmail = ? WHERE id = ?")
|
|
158
|
+
.run(outcome, gitAuthorEmail, id);
|
|
159
|
+
}
|
|
160
|
+
/** Mark sessions as successfully synced (sets syncedAt, clears lastError). */
|
|
161
|
+
export function markSessionsSynced(ids) {
|
|
162
|
+
if (ids.length === 0)
|
|
163
|
+
return;
|
|
164
|
+
const db = getDb();
|
|
165
|
+
const now = new Date().toISOString();
|
|
166
|
+
const stmt = db.prepare(`
|
|
167
|
+
INSERT INTO sync_state (sessionId, syncedAt, lastError)
|
|
168
|
+
VALUES (?, ?, NULL)
|
|
169
|
+
ON CONFLICT(sessionId) DO UPDATE SET
|
|
170
|
+
syncedAt = excluded.syncedAt,
|
|
171
|
+
lastError = NULL
|
|
172
|
+
`);
|
|
173
|
+
const run = db.transaction(() => {
|
|
174
|
+
for (const id of ids)
|
|
175
|
+
stmt.run(id, now);
|
|
176
|
+
});
|
|
177
|
+
run();
|
|
178
|
+
}
|
|
179
|
+
/** Record a sync failure; leaves syncedAt NULL so the rows retry next run. */
|
|
180
|
+
export function recordSyncError(ids, error) {
|
|
181
|
+
if (ids.length === 0)
|
|
182
|
+
return;
|
|
183
|
+
const db = getDb();
|
|
184
|
+
const stmt = db.prepare(`
|
|
185
|
+
INSERT INTO sync_state (sessionId, syncedAt, lastError)
|
|
186
|
+
VALUES (?, NULL, ?)
|
|
187
|
+
ON CONFLICT(sessionId) DO UPDATE SET
|
|
188
|
+
lastError = excluded.lastError
|
|
189
|
+
`);
|
|
190
|
+
const run = db.transaction(() => {
|
|
191
|
+
for (const id of ids)
|
|
192
|
+
stmt.run(id, error);
|
|
193
|
+
});
|
|
194
|
+
run();
|
|
195
|
+
}
|
|
196
|
+
export function setMeta(key, value) {
|
|
197
|
+
getDb()
|
|
198
|
+
.prepare(`
|
|
199
|
+
INSERT INTO meta (key, value) VALUES (?, ?)
|
|
200
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
201
|
+
`)
|
|
202
|
+
.run(key, value);
|
|
203
|
+
}
|
|
204
|
+
export function getMeta(key) {
|
|
205
|
+
const row = getDb()
|
|
206
|
+
.prepare("SELECT value FROM meta WHERE key = ?")
|
|
207
|
+
.get(key);
|
|
208
|
+
return row?.value ?? null;
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/db/store.ts"],"names":[],"mappings":"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"}
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AAIvB,eAAO,MAAM,GAAG;;;CAGN,CAAC"}
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
// .env is dev-only. At runtime, user tokens come from ~/.planckspace/config.json.
|
|
3
|
+
export const env = {
|
|
4
|
+
apiUrl: process.env.PLANCKSPACE_API_URL ?? "https://api.planckspace.dev",
|
|
5
|
+
testToken: process.env.PLANCKSPACE_TEST_TOKEN ?? null,
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=env.js.map
|
package/dist/env.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AAEvB,kFAAkF;AAElF,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,6BAA6B;IACxE,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,IAAI;CAC7C,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { readFileSync } from "fs";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { dirname, join } from "path";
|
|
7
|
+
import { runInit } from "./commands/init.js";
|
|
8
|
+
import { runScan } from "./commands/scan.js";
|
|
9
|
+
import { runStatus } from "./commands/status.js";
|
|
10
|
+
import { runLogin } from "./commands/login.js";
|
|
11
|
+
import { runLogout } from "./commands/logout.js";
|
|
12
|
+
import { runSync } from "./commands/sync.js";
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
15
|
+
const program = new Command();
|
|
16
|
+
program
|
|
17
|
+
.name("planck")
|
|
18
|
+
.description("PlanckSpace CLI — sync AI token usage to the team ledger")
|
|
19
|
+
.version(pkg.version);
|
|
20
|
+
program
|
|
21
|
+
.command("init")
|
|
22
|
+
.description("Initialize ~/.planckspace workspace and local database")
|
|
23
|
+
.action(() => {
|
|
24
|
+
try {
|
|
25
|
+
runInit();
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
console.error(String(err));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
program
|
|
33
|
+
.command("scan")
|
|
34
|
+
.description("Scan Claude Code logs and save sessions to local database")
|
|
35
|
+
.option("--sync", "Sync to the team ledger after scanning")
|
|
36
|
+
.action(async (options) => {
|
|
37
|
+
try {
|
|
38
|
+
await runScan({ sync: options.sync });
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error(String(err));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
program
|
|
46
|
+
.command("sync")
|
|
47
|
+
.description("Push unsynced session metadata to the team ledger")
|
|
48
|
+
.option("-w, --watch", "Keep running and re-sync on an interval")
|
|
49
|
+
.option("-i, --interval <seconds>", "Watch interval in seconds (default 60)", (v) => parseInt(v, 10))
|
|
50
|
+
.action(async (options) => {
|
|
51
|
+
try {
|
|
52
|
+
await runSync({ watch: options.watch, interval: options.interval });
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
console.error(String(err));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
program
|
|
60
|
+
.command("status")
|
|
61
|
+
.description("Show workspace status and session statistics")
|
|
62
|
+
.action(() => {
|
|
63
|
+
try {
|
|
64
|
+
runStatus();
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.error(String(err));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
program
|
|
72
|
+
.command("login [token]")
|
|
73
|
+
.description("Connect to a PlanckSpace workspace")
|
|
74
|
+
.option("-t, --token <token>", "API token (for non-interactive/script installs)")
|
|
75
|
+
.action(async (tokenArg, options) => {
|
|
76
|
+
try {
|
|
77
|
+
await runLogin(options.token ?? tokenArg);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
console.error(String(err));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
program
|
|
85
|
+
.command("logout")
|
|
86
|
+
.description("Disconnect from PlanckSpace (local data retained)")
|
|
87
|
+
.action(() => {
|
|
88
|
+
try {
|
|
89
|
+
runLogout();
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
console.error(String(err));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
program.parse();
|
|
97
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claudeCode.d.ts","sourceRoot":"","sources":["../../src/scrapers/claudeCode.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAQ,aAAa,EAAE,MAAM,YAAY,CAAC;AAGtD,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAoPtD,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAsCnE"}
|