@unpolarize/code-sessions 0.3.0 → 0.4.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/dist/{chunk-ON3CPW4C.js → chunk-3VPXOUIE.js} +50 -0
- package/dist/cli.js +5 -1
- package/dist/index.js +3 -1
- package/package.json +2 -2
- package/src/cli.ts +4 -0
- package/src/cliargs.ts +1 -0
- package/src/commands.ts +15 -0
- package/src/index_store/db.test.ts +19 -0
- package/src/index_store/db.ts +57 -0
|
@@ -141,6 +141,7 @@ Commands:
|
|
|
141
141
|
index (Re)build the internal SQLite index from the git store
|
|
142
142
|
query List recent sessions from the index [--limit N] [--agent X]
|
|
143
143
|
usage Aggregated token/cost usage (totals/by-agent/by-day) [--json]
|
|
144
|
+
graph Sessions \xD7 topics graph (nodes + edges) [--json]
|
|
144
145
|
search Full-text search session turns <text> [--limit N]
|
|
145
146
|
fork Fork a session at a turn ("git for sessions") <session-id> --at N [--id X]
|
|
146
147
|
analytics Compute MVP-2 rollups + digest into analytics/
|
|
@@ -2157,6 +2158,41 @@ var SessionIndex = class {
|
|
|
2157
2158
|
).all(like, like, limit);
|
|
2158
2159
|
return rows.map((r) => this.rowToIndex(r));
|
|
2159
2160
|
}
|
|
2161
|
+
/** Graph of sessions clustered by topic (+ fork lineage where known). */
|
|
2162
|
+
graphData() {
|
|
2163
|
+
const rows = this.db.prepare("SELECT session_id, agent, topic, intent, cost_usd, title FROM session ORDER BY started_at DESC").all();
|
|
2164
|
+
const nodes = [];
|
|
2165
|
+
const edges = [];
|
|
2166
|
+
const topicNodes = /* @__PURE__ */ new Map();
|
|
2167
|
+
const topicId = (t) => {
|
|
2168
|
+
let id = topicNodes.get(t);
|
|
2169
|
+
if (!id) {
|
|
2170
|
+
id = `topic:${topicNodes.size}`;
|
|
2171
|
+
topicNodes.set(t, id);
|
|
2172
|
+
nodes.push({ id, kind: "topic", label: t, sessions: 0, cost_usd: 0 });
|
|
2173
|
+
}
|
|
2174
|
+
return id;
|
|
2175
|
+
};
|
|
2176
|
+
for (const r of rows) {
|
|
2177
|
+
const sid = `s:${r.session_id}`;
|
|
2178
|
+
nodes.push({
|
|
2179
|
+
id: sid,
|
|
2180
|
+
kind: "session",
|
|
2181
|
+
label: (r.topic || r.title || r.session_id).slice(0, 48),
|
|
2182
|
+
agent: r.agent,
|
|
2183
|
+
intent: r.intent ?? null,
|
|
2184
|
+
cost_usd: r.cost_usd,
|
|
2185
|
+
sessions: 1
|
|
2186
|
+
});
|
|
2187
|
+
const topic = (r.topic || r.intent || "misc").toString();
|
|
2188
|
+
const tid = topicId(topic);
|
|
2189
|
+
edges.push({ from: sid, to: tid, kind: "has-topic" });
|
|
2190
|
+
const tn = nodes.find((n) => n.id === tid);
|
|
2191
|
+
tn.sessions += 1;
|
|
2192
|
+
tn.cost_usd += r.cost_usd;
|
|
2193
|
+
}
|
|
2194
|
+
return { nodes, edges };
|
|
2195
|
+
}
|
|
2160
2196
|
/** Aggregated usage for a Usage panel: totals, by agent, by day, by project, top cost. */
|
|
2161
2197
|
usageSummary(opts = {}) {
|
|
2162
2198
|
const rows = this.db.prepare(
|
|
@@ -2605,6 +2641,19 @@ function cmdFork(cfg, opts) {
|
|
|
2605
2641
|
return { code: 1, output: `fork failed: ${e instanceof Error ? e.message : String(e)}` };
|
|
2606
2642
|
}
|
|
2607
2643
|
}
|
|
2644
|
+
function cmdGraph(cfg, opts = {}) {
|
|
2645
|
+
syncIndex(cfg);
|
|
2646
|
+
const index = new SessionIndex(cfg.indexPath);
|
|
2647
|
+
try {
|
|
2648
|
+
const g = index.graphData();
|
|
2649
|
+
if (opts.json) return { code: 0, output: JSON.stringify(g) };
|
|
2650
|
+
const topics = g.nodes.filter((n) => n.kind === "topic").length;
|
|
2651
|
+
const sessions = g.nodes.filter((n) => n.kind === "session").length;
|
|
2652
|
+
return { code: 0, output: `graph: ${sessions} sessions across ${topics} topics, ${g.edges.length} edges` };
|
|
2653
|
+
} finally {
|
|
2654
|
+
index.close();
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2608
2657
|
function cmdUsage(cfg, opts = {}) {
|
|
2609
2658
|
syncIndex(cfg);
|
|
2610
2659
|
const index = new SessionIndex(cfg.indexPath);
|
|
@@ -2794,6 +2843,7 @@ export {
|
|
|
2794
2843
|
cmdDoctor,
|
|
2795
2844
|
cmdExport,
|
|
2796
2845
|
cmdFork,
|
|
2846
|
+
cmdGraph,
|
|
2797
2847
|
cmdUsage,
|
|
2798
2848
|
cmdInstallSkills,
|
|
2799
2849
|
cmdIndex,
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
cmdDoctor,
|
|
6
6
|
cmdExport,
|
|
7
7
|
cmdFork,
|
|
8
|
+
cmdGraph,
|
|
8
9
|
cmdIndex,
|
|
9
10
|
cmdInit,
|
|
10
11
|
cmdInstallHooks,
|
|
@@ -23,7 +24,7 @@ import {
|
|
|
23
24
|
parseFlags,
|
|
24
25
|
readStdin,
|
|
25
26
|
startDaemon
|
|
26
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-3VPXOUIE.js";
|
|
27
28
|
|
|
28
29
|
// src/analytics/command.ts
|
|
29
30
|
import { mkdirSync, writeFileSync } from "fs";
|
|
@@ -260,6 +261,9 @@ async function main(argv) {
|
|
|
260
261
|
case "usage":
|
|
261
262
|
emit(cmdUsage(cfg, { json: flags.json === true }));
|
|
262
263
|
break;
|
|
264
|
+
case "graph":
|
|
265
|
+
emit(cmdGraph(cfg, { json: flags.json === true }));
|
|
266
|
+
break;
|
|
263
267
|
case "query":
|
|
264
268
|
emit(
|
|
265
269
|
cmdQuery(cfg, {
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
cmdDoctor,
|
|
21
21
|
cmdExport,
|
|
22
22
|
cmdFork,
|
|
23
|
+
cmdGraph,
|
|
23
24
|
cmdIndex,
|
|
24
25
|
cmdInit,
|
|
25
26
|
cmdInstallHooks,
|
|
@@ -92,7 +93,7 @@ import {
|
|
|
92
93
|
writeBlobFile,
|
|
93
94
|
writeImportedSession,
|
|
94
95
|
writeTurnFile
|
|
95
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-3VPXOUIE.js";
|
|
96
97
|
export {
|
|
97
98
|
CaptureEngine,
|
|
98
99
|
DEFAULT_HOOK_EVENTS,
|
|
@@ -115,6 +116,7 @@ export {
|
|
|
115
116
|
cmdDoctor,
|
|
116
117
|
cmdExport,
|
|
117
118
|
cmdFork,
|
|
119
|
+
cmdGraph,
|
|
118
120
|
cmdIndex,
|
|
119
121
|
cmdInit,
|
|
120
122
|
cmdInstallHooks,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unpolarize/code-sessions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Headless, event-driven cross-agent session capture agent (daemon + CLI)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"build": "tsup src/index.ts src/cli.ts --format esm --clean --out-dir dist"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@unpolarize/code-sessions-schema": "^0.
|
|
28
|
+
"@unpolarize/code-sessions-schema": "^0.4.0",
|
|
29
29
|
"zod": "^3.23.8"
|
|
30
30
|
}
|
|
31
31
|
}
|
package/src/cli.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
cmdDoctor,
|
|
6
6
|
cmdExport,
|
|
7
7
|
cmdFork,
|
|
8
|
+
cmdGraph,
|
|
8
9
|
cmdIndex,
|
|
9
10
|
cmdInit,
|
|
10
11
|
cmdInstallHooks,
|
|
@@ -74,6 +75,9 @@ export async function main(argv: string[]): Promise<void> {
|
|
|
74
75
|
case 'usage':
|
|
75
76
|
emit(cmdUsage(cfg, { json: flags.json === true }));
|
|
76
77
|
break;
|
|
78
|
+
case 'graph':
|
|
79
|
+
emit(cmdGraph(cfg, { json: flags.json === true }));
|
|
80
|
+
break;
|
|
77
81
|
case 'query':
|
|
78
82
|
emit(
|
|
79
83
|
cmdQuery(cfg, {
|
package/src/cliargs.ts
CHANGED
|
@@ -59,6 +59,7 @@ Commands:
|
|
|
59
59
|
index (Re)build the internal SQLite index from the git store
|
|
60
60
|
query List recent sessions from the index [--limit N] [--agent X]
|
|
61
61
|
usage Aggregated token/cost usage (totals/by-agent/by-day) [--json]
|
|
62
|
+
graph Sessions × topics graph (nodes + edges) [--json]
|
|
62
63
|
search Full-text search session turns <text> [--limit N]
|
|
63
64
|
fork Fork a session at a turn ("git for sessions") <session-id> --at N [--id X]
|
|
64
65
|
analytics Compute MVP-2 rollups + digest into analytics/
|
package/src/commands.ts
CHANGED
|
@@ -234,6 +234,21 @@ export function cmdFork(
|
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
/** Sessions × topics graph (nodes + edges) from the CS index. JSON for the graph view. */
|
|
238
|
+
export function cmdGraph(cfg: CodeSessionsConfig, opts: { json?: boolean } = {}): CommandResult {
|
|
239
|
+
syncIndex(cfg);
|
|
240
|
+
const index = new SessionIndex(cfg.indexPath);
|
|
241
|
+
try {
|
|
242
|
+
const g = index.graphData();
|
|
243
|
+
if (opts.json) return { code: 0, output: JSON.stringify(g) };
|
|
244
|
+
const topics = g.nodes.filter((n) => n.kind === 'topic').length;
|
|
245
|
+
const sessions = g.nodes.filter((n) => n.kind === 'session').length;
|
|
246
|
+
return { code: 0, output: `graph: ${sessions} sessions across ${topics} topics, ${g.edges.length} edges` };
|
|
247
|
+
} finally {
|
|
248
|
+
index.close();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
237
252
|
/** Aggregated usage from the CS index (totals/byAgent/byDay/byProject/topByCost). */
|
|
238
253
|
export function cmdUsage(cfg: CodeSessionsConfig, opts: { json?: boolean } = {}): CommandResult {
|
|
239
254
|
syncIndex(cfg); // ensure the index reflects the current store
|
|
@@ -116,6 +116,25 @@ describe('SessionIndex', () => {
|
|
|
116
116
|
}
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
+
it('builds a sessions×topics graph', () => {
|
|
120
|
+
const idx = new SessionIndex(':memory:');
|
|
121
|
+
try {
|
|
122
|
+
idx.upsertSession(env('a', 'claude-code'), { ...src, topic: 'fix parser' });
|
|
123
|
+
idx.upsertSession(env('b', 'grok'), { ...src, source_path: '/s/b.json', topic: 'fix parser' });
|
|
124
|
+
idx.upsertSession(env('c', 'codex'), { ...src, source_path: '/s/c.json', topic: 'add feature' });
|
|
125
|
+
const g = idx.graphData();
|
|
126
|
+
const topics = g.nodes.filter((n) => n.kind === 'topic');
|
|
127
|
+
const sessions = g.nodes.filter((n) => n.kind === 'session');
|
|
128
|
+
expect(sessions).toHaveLength(3);
|
|
129
|
+
expect(topics).toHaveLength(2); // "fix parser" + "add feature"
|
|
130
|
+
expect(g.edges).toHaveLength(3); // each session -> its topic
|
|
131
|
+
const fixParser = topics.find((t) => t.label === 'fix parser')!;
|
|
132
|
+
expect(fixParser.sessions).toBe(2);
|
|
133
|
+
} finally {
|
|
134
|
+
idx.close();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
119
138
|
it('filters by agent and deletes sessions (cascade turns)', () => {
|
|
120
139
|
const idx = new SessionIndex(':memory:');
|
|
121
140
|
try {
|
package/src/index_store/db.ts
CHANGED
|
@@ -51,6 +51,25 @@ export interface UsageBucket {
|
|
|
51
51
|
cost_usd: number;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
export interface GraphNode {
|
|
55
|
+
id: string;
|
|
56
|
+
kind: 'session' | 'topic';
|
|
57
|
+
label: string;
|
|
58
|
+
agent?: string;
|
|
59
|
+
intent?: string | null;
|
|
60
|
+
cost_usd: number;
|
|
61
|
+
sessions: number;
|
|
62
|
+
}
|
|
63
|
+
export interface GraphEdge {
|
|
64
|
+
from: string;
|
|
65
|
+
to: string;
|
|
66
|
+
kind: 'has-topic' | 'forked-from';
|
|
67
|
+
}
|
|
68
|
+
export interface GraphData {
|
|
69
|
+
nodes: GraphNode[];
|
|
70
|
+
edges: GraphEdge[];
|
|
71
|
+
}
|
|
72
|
+
|
|
54
73
|
export interface UsageSummary {
|
|
55
74
|
totals: { sessions: number; input_tokens: number; output_tokens: number; cost_usd: number };
|
|
56
75
|
byAgent: Record<string, UsageBucket>;
|
|
@@ -308,6 +327,44 @@ export class SessionIndex {
|
|
|
308
327
|
return (rows as any[]).map((r) => this.rowToIndex(r));
|
|
309
328
|
}
|
|
310
329
|
|
|
330
|
+
/** Graph of sessions clustered by topic (+ fork lineage where known). */
|
|
331
|
+
graphData(): GraphData {
|
|
332
|
+
const rows = this.db
|
|
333
|
+
.prepare('SELECT session_id, agent, topic, intent, cost_usd, title FROM session ORDER BY started_at DESC')
|
|
334
|
+
.all() as any[];
|
|
335
|
+
const nodes: GraphNode[] = [];
|
|
336
|
+
const edges: GraphEdge[] = [];
|
|
337
|
+
const topicNodes = new Map<string, string>(); // topic -> node id
|
|
338
|
+
const topicId = (t: string) => {
|
|
339
|
+
let id = topicNodes.get(t);
|
|
340
|
+
if (!id) {
|
|
341
|
+
id = `topic:${topicNodes.size}`;
|
|
342
|
+
topicNodes.set(t, id);
|
|
343
|
+
nodes.push({ id, kind: 'topic', label: t, sessions: 0, cost_usd: 0 });
|
|
344
|
+
}
|
|
345
|
+
return id;
|
|
346
|
+
};
|
|
347
|
+
for (const r of rows) {
|
|
348
|
+
const sid = `s:${r.session_id}`;
|
|
349
|
+
nodes.push({
|
|
350
|
+
id: sid,
|
|
351
|
+
kind: 'session',
|
|
352
|
+
label: (r.topic || r.title || r.session_id).slice(0, 48),
|
|
353
|
+
agent: r.agent,
|
|
354
|
+
intent: r.intent ?? null,
|
|
355
|
+
cost_usd: r.cost_usd,
|
|
356
|
+
sessions: 1,
|
|
357
|
+
});
|
|
358
|
+
const topic = (r.topic || r.intent || 'misc').toString();
|
|
359
|
+
const tid = topicId(topic);
|
|
360
|
+
edges.push({ from: sid, to: tid, kind: 'has-topic' });
|
|
361
|
+
const tn = nodes.find((n) => n.id === tid)!;
|
|
362
|
+
tn.sessions += 1;
|
|
363
|
+
tn.cost_usd += r.cost_usd;
|
|
364
|
+
}
|
|
365
|
+
return { nodes, edges };
|
|
366
|
+
}
|
|
367
|
+
|
|
311
368
|
/** Aggregated usage for a Usage panel: totals, by agent, by day, by project, top cost. */
|
|
312
369
|
usageSummary(opts: { days?: number; topN?: number } = {}): UsageSummary {
|
|
313
370
|
const rows = this.db
|