cc-query 0.1.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-query",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "SQL REPL for querying Claude Code session data",
5
5
  "type": "module",
6
6
  "exports": {
@@ -15,7 +15,7 @@
15
15
  "node": ">=24"
16
16
  },
17
17
  "bin": {
18
- "cc-query": "./bin/cc-query.js"
18
+ "cc-query": "bin/cc-query.js"
19
19
  },
20
20
  "files": [
21
21
  "index.js",
@@ -39,6 +39,16 @@ function formatResults(result) {
39
39
  const ms = Number(val.micros) / 1000;
40
40
  return new Date(ms).toISOString().replace("T", " ").replace("Z", "");
41
41
  }
42
+ // Handle DuckDB UUID objects (returned as {hugeint: string})
43
+ if ("hugeint" in val) {
44
+ // Convert 128-bit signed decimal to UUID hex string
45
+ // DuckDB XORs the high bit for sorting, so flip it back
46
+ let n = BigInt(val.hugeint);
47
+ if (n < 0n) n += 1n << 128n; // Convert from signed to unsigned
48
+ n ^= 1n << 127n; // Flip high bit (undo DuckDB's sort optimization)
49
+ const hex = n.toString(16).padStart(32, "0");
50
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
51
+ }
42
52
  return JSON.stringify(val, (_, v) =>
43
53
  typeof v === "bigint" ? v.toString() : v,
44
54
  );
@@ -334,6 +344,103 @@ export class QuerySession {
334
344
  json as raw
335
345
  FROM read_ndjson_objects('${this.#filePattern}', ignore_errors=true)
336
346
  WHERE json->>'uuid' IS NOT NULL AND length(json->>'uuid') > 0;
347
+
348
+ -- Tool uses: All tool calls with unnested content blocks
349
+ CREATE OR REPLACE VIEW tool_uses AS
350
+ SELECT
351
+ m.uuid,
352
+ m.timestamp,
353
+ m.sessionId,
354
+ m.isAgent,
355
+ m.agentId,
356
+ m.project,
357
+ m.rownum,
358
+ block->>'name' as tool_name,
359
+ block->>'id' as tool_id,
360
+ block->'input' as tool_input,
361
+ row_number() OVER (PARTITION BY m.uuid ORDER BY (SELECT NULL)) - 1 as block_index
362
+ FROM assistant_messages m,
363
+ LATERAL UNNEST(CAST(message->'content' AS JSON[])) as t(block)
364
+ WHERE block->>'type' = 'tool_use';
365
+
366
+ -- Tool results: All tool results with duration
367
+ CREATE OR REPLACE VIEW tool_results AS
368
+ WITH array_messages AS (
369
+ SELECT * FROM user_messages
370
+ WHERE json_type(message->'content') = 'ARRAY'
371
+ )
372
+ SELECT
373
+ m.uuid,
374
+ m.timestamp,
375
+ m.sessionId,
376
+ m.isAgent,
377
+ m.agentId,
378
+ m.project,
379
+ m.rownum,
380
+ block->>'tool_use_id' as tool_use_id,
381
+ CAST(block->>'is_error' AS BOOLEAN) as is_error,
382
+ block->>'content' as result_content,
383
+ CAST(m.toolUseResult->>'durationMs' AS INTEGER) as duration_ms,
384
+ m.sourceToolAssistantUUID
385
+ FROM array_messages m,
386
+ LATERAL UNNEST(CAST(message->'content' AS JSON[])) as t(block)
387
+ WHERE block->>'type' = 'tool_result';
388
+
389
+ -- Token usage: Pre-cast token counts
390
+ CREATE OR REPLACE VIEW token_usage AS
391
+ SELECT
392
+ uuid,
393
+ timestamp,
394
+ sessionId,
395
+ isAgent,
396
+ agentId,
397
+ project,
398
+ message->>'model' as model,
399
+ message->>'stop_reason' as stop_reason,
400
+ CAST(message->'usage'->>'input_tokens' AS BIGINT) as input_tokens,
401
+ CAST(message->'usage'->>'output_tokens' AS BIGINT) as output_tokens,
402
+ CAST(message->'usage'->>'cache_read_input_tokens' AS BIGINT) as cache_read_tokens,
403
+ CAST(message->'usage'->>'cache_creation_input_tokens' AS BIGINT) as cache_creation_tokens
404
+ FROM assistant_messages
405
+ WHERE (message->'usage') IS NOT NULL;
406
+
407
+ -- Bash commands: Bash tool uses with extracted command
408
+ CREATE OR REPLACE VIEW bash_commands AS
409
+ SELECT
410
+ uuid,
411
+ timestamp,
412
+ sessionId,
413
+ isAgent,
414
+ agentId,
415
+ project,
416
+ rownum,
417
+ tool_id,
418
+ tool_input->>'command' as command,
419
+ tool_input->>'description' as description,
420
+ CAST(tool_input->>'timeout' AS INTEGER) as timeout,
421
+ CAST(tool_input->>'run_in_background' AS BOOLEAN) as run_in_background
422
+ FROM tool_uses
423
+ WHERE tool_name = 'Bash';
424
+
425
+ -- File operations: Read/Write/Edit/Glob/Grep with extracted paths
426
+ CREATE OR REPLACE VIEW file_operations AS
427
+ SELECT
428
+ uuid,
429
+ timestamp,
430
+ sessionId,
431
+ isAgent,
432
+ agentId,
433
+ project,
434
+ rownum,
435
+ tool_id,
436
+ tool_name,
437
+ COALESCE(
438
+ tool_input->>'file_path',
439
+ tool_input->>'path'
440
+ ) as file_path,
441
+ tool_input->>'pattern' as pattern
442
+ FROM tool_uses
443
+ WHERE tool_name IN ('Read', 'Write', 'Edit', 'Glob', 'Grep');
337
444
  `;
338
445
  }
339
446
  }
package/src/repl.js CHANGED
@@ -52,15 +52,22 @@ function getHelpText() {
52
52
  return `
53
53
  Commands:
54
54
  .help, .h Show this help
55
- .schema, .s Show table schema (runs DESCRIBE messages)
55
+ .schema, .s Show schemas for all views
56
56
  .schema <view> Show schema for a specific view
57
57
  .quit, .q Exit
58
58
 
59
59
  Views:
60
60
  messages All messages (user, assistant, system)
61
61
  user_messages User messages with user-specific fields
62
+ human_messages Human-typed messages (excludes tool results)
62
63
  assistant_messages Assistant messages with error, requestId, etc.
63
64
  system_messages System messages with hooks, retry info, etc.
65
+ raw_messages Raw JSON for each message by uuid
66
+ tool_uses All tool calls with unnested content blocks
67
+ tool_results Tool results with duration and error status
68
+ token_usage Token counts per assistant message
69
+ bash_commands Bash tool calls with extracted command
70
+ file_operations Read/Write/Edit/Glob/Grep with file paths
64
71
 
65
72
  Example queries:
66
73
  -- Count messages by type
@@ -137,7 +144,23 @@ async function handleDotCommand(command, qs) {
137
144
  }
138
145
 
139
146
  if (cmd === ".schema" || cmd === ".s") {
140
- await executeQuery(qs, "DESCRIBE messages");
147
+ const views = [
148
+ "messages",
149
+ "user_messages",
150
+ "human_messages",
151
+ "assistant_messages",
152
+ "system_messages",
153
+ "raw_messages",
154
+ "tool_uses",
155
+ "tool_results",
156
+ "token_usage",
157
+ "bash_commands",
158
+ "file_operations",
159
+ ];
160
+ for (const view of views) {
161
+ console.log(`\n=== ${view} ===`);
162
+ await executeQuery(qs, `DESCRIBE ${view}`);
163
+ }
141
164
  return false;
142
165
  }
143
166