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 +55 -0
- package/package.json +1 -1
- package/src/query-session.js +17 -4
- package/src/session-loader.js +14 -4
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
package/src/query-session.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
package/src/session-loader.js
CHANGED
|
@@ -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
|
-
?
|
|
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
|
|
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
|
-
?
|
|
105
|
+
? [
|
|
106
|
+
join(claudeProjectsDir, `${sessionFilter}*.jsonl`),
|
|
107
|
+
join(claudeProjectsDir, `${sessionFilter}*/subagents/*.jsonl`),
|
|
108
|
+
]
|
|
99
109
|
: join(claudeProjectsDir, "**/*.jsonl");
|
|
100
110
|
|
|
101
111
|
return {
|