cc-query 0.2.0 → 0.2.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 ADDED
@@ -0,0 +1,55 @@
1
+ # cc-query
2
+
3
+ SQL REPL for querying Claude Code session data using DuckDB.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g cc-query
9
+ ```
10
+
11
+ Requires Node.js 24+.
12
+
13
+ ## Usage
14
+
15
+ ```bash
16
+ # Query all projects
17
+ cc-query
18
+
19
+ # Query a specific project
20
+ cc-query ~/code/my-project
21
+
22
+ # Filter by session ID prefix
23
+ cc-query -s abc123 .
24
+
25
+ # Pipe queries (like psql)
26
+ echo "SELECT count(*) FROM messages;" | cc-query .
27
+ ```
28
+
29
+ ## Available Views
30
+
31
+ - `messages` - All messages with parsed fields
32
+ - `user_messages` - User messages only
33
+ - `assistant_messages` - Assistant responses only
34
+ - `tool_calls` - Tool invocations from assistant messages
35
+ - `raw_messages` - Unparsed JSONL data
36
+
37
+ ## REPL Commands
38
+
39
+ - `.help` - Show tables and example queries
40
+ - `.schema` - Show table schema
41
+ - `.quit` - Exit
42
+
43
+ ## Skill (experimental)
44
+
45
+ This [skill](https://gist.github.com/dannycoates/b4436fb77c9cfd2763028eee42d1d320) gives claude the ability and slash command `/reflect` to work with claude session history.
46
+
47
+ For example you can ask questions like:
48
+ - Across all projects what bash commands return the most errors?
49
+ - Let's analyze the last session and identify how we might improve the claude.md file
50
+ - Gimme a summary of what we worked on this past week
51
+ - Let's go though our whole session history and identify repeated patterns that we could extract into skills
52
+
53
+ ## License
54
+
55
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-query",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "SQL REPL for querying Claude Code session data",
5
5
  "type": "module",
6
6
  "exports": {
@@ -91,13 +91,13 @@ function formatResults(result) {
91
91
  export class QuerySession {
92
92
  /** @type {import("@duckdb/node-api").DuckDBConnection | undefined} */
93
93
  #connection;
94
- /** @type {string} */
94
+ /** @type {string | string[]} */
95
95
  #filePattern;
96
96
  /** @type {QuerySessionInfo} */
97
97
  #info;
98
98
 
99
99
  /**
100
- * @param {string} filePattern - Glob pattern for JSONL files
100
+ * @param {string | string[]} filePattern - Glob pattern(s) for JSONL files
101
101
  * @param {QuerySessionInfo} info - Session counts
102
102
  */
103
103
  constructor(filePattern, info) {
@@ -105,6 +105,19 @@ export class QuerySession {
105
105
  this.#info = info;
106
106
  }
107
107
 
108
+ /**
109
+ * Format file pattern for use in DuckDB SQL
110
+ * @returns {string} SQL expression for the file pattern
111
+ */
112
+ #formatFilePatternForSql() {
113
+ if (Array.isArray(this.#filePattern)) {
114
+ // DuckDB accepts a list of patterns: ['pattern1', 'pattern2']
115
+ const patterns = this.#filePattern.map((p) => `'${p}'`).join(", ");
116
+ return `[${patterns}]`;
117
+ }
118
+ return `'${this.#filePattern}'`;
119
+ }
120
+
108
121
  /**
109
122
  * Create a QuerySession from a project path
110
123
  * @param {string | null} projectDir - Claude projects dir, or null for all
@@ -290,7 +303,7 @@ export class QuerySession {
290
303
  regexp_extract(filename, '/projects/([^/]+)/', 1) as project,
291
304
  ordinality as rownum
292
305
  FROM read_ndjson(
293
- '${this.#filePattern}',
306
+ ${this.#formatFilePatternForSql()},
294
307
  filename=true,
295
308
  ignore_errors=true,
296
309
  columns={${columnsDef}}
@@ -342,7 +355,7 @@ export class QuerySession {
342
355
  SELECT
343
356
  (json->>'uuid')::UUID as uuid,
344
357
  json as raw
345
- FROM read_ndjson_objects('${this.#filePattern}', ignore_errors=true)
358
+ FROM read_ndjson_objects(${this.#formatFilePatternForSql()}, ignore_errors=true)
346
359
  WHERE json->>'uuid' IS NOT NULL AND length(json->>'uuid') > 0;
347
360
 
348
361
  -- Tool uses: All tool calls with unnested content blocks
@@ -42,7 +42,7 @@ function countSessionsAndAgents(files, sessionFilter = "") {
42
42
  * Get session info and file pattern for querying
43
43
  * @param {string | null} claudeProjectsDir - Path to ~/.claude/projects/{slug}, or null for all projects
44
44
  * @param {string} [sessionFilter] - Optional session ID prefix
45
- * @returns {Promise<{ sessionCount: number, agentCount: number, projectCount: number, filePattern: string }>}
45
+ * @returns {Promise<{ sessionCount: number, agentCount: number, projectCount: number, filePattern: string | string[] }>}
46
46
  */
47
47
  export async function getSessionFiles(claudeProjectsDir, sessionFilter = "") {
48
48
  // If no specific project, use all projects
@@ -72,8 +72,13 @@ export async function getSessionFiles(claudeProjectsDir, sessionFilter = "") {
72
72
  }
73
73
 
74
74
  // Use glob pattern for all projects (** for recursive matching)
75
+ // When session filter is provided, include both the filtered session AND its subagents
76
+ // Subagents are stored in {session_id}/subagents/*.jsonl
75
77
  const filePattern = sessionFilter
76
- ? join(base, "*", `**/${sessionFilter}*.jsonl`)
78
+ ? [
79
+ join(base, "*", `${sessionFilter}*.jsonl`),
80
+ join(base, "*", `${sessionFilter}*/subagents/*.jsonl`),
81
+ ]
77
82
  : join(base, "*", "**/*.jsonl");
78
83
 
79
84
  return {
@@ -93,9 +98,14 @@ export async function getSessionFiles(claudeProjectsDir, sessionFilter = "") {
93
98
  return { sessionCount: 0, agentCount: 0, projectCount: 1, filePattern: "" };
94
99
  }
95
100
 
96
- // Use glob pattern with ** for recursive matching
101
+ // Use glob pattern for matching
102
+ // When session filter is provided, include both the filtered session AND its subagents
103
+ // Subagents are stored in {session_id}/subagents/*.jsonl
97
104
  const filePattern = sessionFilter
98
- ? join(claudeProjectsDir, `**/${sessionFilter}*.jsonl`)
105
+ ? [
106
+ join(claudeProjectsDir, `${sessionFilter}*.jsonl`),
107
+ join(claudeProjectsDir, `${sessionFilter}*/subagents/*.jsonl`),
108
+ ]
99
109
  : join(claudeProjectsDir, "**/*.jsonl");
100
110
 
101
111
  return {