agentrace 0.0.4 → 0.0.6

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.
@@ -25,7 +25,7 @@ export async function initCommand(options = {}) {
25
25
  console.error("Error: Invalid URL format");
26
26
  process.exit(1);
27
27
  }
28
- console.log("Agentrace Setup\n");
28
+ console.log("AgenTrace Setup\n");
29
29
  if (options.dev) {
30
30
  console.log("[Dev Mode] Using local CLI for hooks\n");
31
31
  }
@@ -6,20 +6,23 @@ import * as fs from "node:fs";
6
6
  import * as path from "node:path";
7
7
  import * as os from "node:os";
8
8
  import { PlanDocumentClient } from "../mcp/plan-document-client.js";
9
- // Read session_id from file written by PreToolUse hook
10
- function getSessionIdFromFile() {
9
+ // Read session_id and tool_use_id from file written by PreToolUse hook
10
+ function getSessionInfoFromFile() {
11
11
  try {
12
12
  const sessionFile = path.join(os.homedir(), ".agentrace", "current-session.json");
13
13
  if (fs.existsSync(sessionFile)) {
14
14
  const content = fs.readFileSync(sessionFile, "utf-8");
15
15
  const data = JSON.parse(content);
16
- return data.session_id;
16
+ return {
17
+ session_id: data.session_id,
18
+ tool_use_id: data.tool_use_id,
19
+ };
17
20
  }
18
21
  }
19
22
  catch {
20
- // Ignore errors, return undefined
23
+ // Ignore errors, return empty object
21
24
  }
22
- return undefined;
25
+ return {};
23
26
  }
24
27
  // Tool schemas
25
28
  const SearchPlansSchema = z.object({
@@ -37,10 +40,12 @@ const CreatePlanSchema = z.object({
37
40
  const UpdatePlanSchema = z.object({
38
41
  id: z.string().describe("Plan document ID"),
39
42
  body: z.string().describe("Updated plan content in Markdown format"),
43
+ message: z.string().optional().describe("One-line description of what was changed"),
40
44
  });
41
45
  const SetPlanStatusSchema = z.object({
42
46
  id: z.string().describe("Plan document ID"),
43
- status: z.enum(["scratch", "draft", "planning", "pending", "implementation", "complete"]).describe("New status for the plan"),
47
+ status: z.enum(["scratch", "draft", "planning", "pending", "ready", "implementation", "complete"]).describe("New status for the plan"),
48
+ message: z.string().optional().describe("One-line description of why the status is being changed"),
44
49
  });
45
50
  // Tool descriptions with usage guidance
46
51
  const TOOL_DESCRIPTIONS = {
@@ -66,7 +71,7 @@ WHEN TO USE:
66
71
  - When the user asks you to save or persist a plan
67
72
  - When planning significant features, refactoring, or architectural changes
68
73
 
69
- The plan will be saved to Agentrace server and can be reviewed by the team.
74
+ The plan will be saved to AgenTrace server and can be reviewed by the team.
70
75
  The project is automatically determined from the session's git repository.`,
71
76
  update_plan: `Update an existing plan document.
72
77
 
@@ -88,15 +93,17 @@ Available statuses:
88
93
  - draft: Plan not yet fully considered (optional intermediate status)
89
94
  - planning: Plan is being designed/refined through discussion
90
95
  - pending: Waiting for approval or blocked
96
+ - ready: Plan is finalized and ready for implementation (awaiting review/approval to start)
91
97
  - implementation: Active development is in progress
92
98
  - complete: The work described in the plan is finished
93
99
 
94
- BASIC FLOW: scratch → planning → implementation → complete
100
+ BASIC FLOW: scratch → planning → ready → implementation → complete
95
101
  (draft and pending are optional auxiliary statuses)
96
102
 
97
103
  STATUS TRANSITION GUIDELINES:
98
104
  - scratch → planning: When you read a scratch plan (usually written by human), review its content and rewrite it into a more concrete plan, then change status to planning
99
- - planning → implementation: When the plan is finalized after discussion, change status to implementation before starting work
105
+ - planning → ready: When the plan is finalized after discussion, change status to ready
106
+ - ready → implementation: When you start working on the plan, change status to implementation
100
107
  - implementation → complete: When all work described in the plan is finished, change status to complete
101
108
 
102
109
  CAUTION:
@@ -106,10 +113,10 @@ export async function mcpServerCommand() {
106
113
  const server = new McpServer({
107
114
  name: "agentrace",
108
115
  version: "1.0.0",
109
- description: `Agentrace Plan Document Management Server.
116
+ description: `AgenTrace Plan Document Management Server.
110
117
 
111
118
  This server provides tools to manage implementation and design plans.
112
- Plans are stored on the Agentrace server and can be reviewed by the team.
119
+ Plans are stored on the AgenTrace server and can be reviewed by the team.
113
120
 
114
121
  IMPORTANT GUIDELINES:
115
122
  - When you create a design or implementation plan, ALWAYS save it using create_plan
@@ -145,6 +152,7 @@ IMPORTANT GUIDELINES:
145
152
  id: plan.id,
146
153
  description: plan.description,
147
154
  status: plan.status,
155
+ git_remote_url: plan.project?.canonical_git_repository || null,
148
156
  updated_at: plan.updated_at,
149
157
  collaborators: plan.collaborators.map((c) => c.display_name).join(", "),
150
158
  }));
@@ -197,12 +205,13 @@ IMPORTANT GUIDELINES:
197
205
  // create_plan tool
198
206
  server.tool("create_plan", TOOL_DESCRIPTIONS.create_plan, CreatePlanSchema.shape, async (args) => {
199
207
  try {
200
- // Read session_id from file written by PreToolUse hook
201
- const claudeSessionId = getSessionIdFromFile();
208
+ // Read session_id and tool_use_id from file written by PreToolUse hook
209
+ const sessionInfo = getSessionInfoFromFile();
202
210
  const plan = await getClient().createPlan({
203
211
  description: args.description,
204
212
  body: args.body,
205
- claude_session_id: claudeSessionId,
213
+ claude_session_id: sessionInfo.session_id,
214
+ tool_use_id: sessionInfo.tool_use_id,
206
215
  });
207
216
  return {
208
217
  content: [
@@ -228,8 +237,8 @@ IMPORTANT GUIDELINES:
228
237
  // update_plan tool
229
238
  server.tool("update_plan", TOOL_DESCRIPTIONS.update_plan, UpdatePlanSchema.shape, async (args) => {
230
239
  try {
231
- // Read session_id from file written by PreToolUse hook
232
- const claudeSessionId = getSessionIdFromFile();
240
+ // Read session_id and tool_use_id from file written by PreToolUse hook
241
+ const sessionInfo = getSessionInfoFromFile();
233
242
  // Get current plan to compute patch
234
243
  const currentPlan = await getClient().getPlan(args.id);
235
244
  // Compute patch using diff-match-patch
@@ -238,7 +247,9 @@ IMPORTANT GUIDELINES:
238
247
  const plan = await getClient().updatePlan(args.id, {
239
248
  body: args.body,
240
249
  patch: patchText,
241
- claude_session_id: claudeSessionId,
250
+ message: args.message,
251
+ claude_session_id: sessionInfo.session_id,
252
+ tool_use_id: sessionInfo.tool_use_id,
242
253
  });
243
254
  return {
244
255
  content: [
@@ -264,7 +275,7 @@ IMPORTANT GUIDELINES:
264
275
  // set_plan_status tool
265
276
  server.tool("set_plan_status", TOOL_DESCRIPTIONS.set_plan_status, SetPlanStatusSchema.shape, async (args) => {
266
277
  try {
267
- const plan = await getClient().setStatus(args.id, args.status);
278
+ const plan = await getClient().setStatus(args.id, args.status, args.message);
268
279
  return {
269
280
  content: [
270
281
  {
@@ -4,7 +4,7 @@ export async function offCommand() {
4
4
  // Check if config exists
5
5
  const config = loadConfig();
6
6
  if (!config) {
7
- console.log("Agentrace is not configured. Run 'npx agentrace init' first.");
7
+ console.log("AgenTrace is not configured. Run 'npx agentrace init' first.");
8
8
  return;
9
9
  }
10
10
  const result = uninstallHooks();
@@ -8,7 +8,7 @@ export async function onCommand(options = {}) {
8
8
  // Check if config exists
9
9
  const config = loadConfig();
10
10
  if (!config) {
11
- console.log("Agentrace is not configured. Run 'npx agentrace init' first.");
11
+ console.log("AgenTrace is not configured. Run 'npx agentrace init' first.");
12
12
  return;
13
13
  }
14
14
  // Determine hook command
@@ -1 +1,12 @@
1
+ /**
2
+ * Hook-based send command.
3
+ * Reads session info from stdin (provided by Claude Code hooks).
4
+ */
1
5
  export declare function sendCommand(): Promise<void>;
6
+ /**
7
+ * Manual send command.
8
+ * Finds session file by ID and sends to server.
9
+ */
10
+ export declare function sendManualCommand(options: {
11
+ sessionId: string;
12
+ }): Promise<void>;
@@ -2,6 +2,7 @@ import { execSync } from "child_process";
2
2
  import { loadConfig } from "../config/manager.js";
3
3
  import { getNewLines, saveCursor, hasCursor } from "../config/cursor.js";
4
4
  import { sendIngest } from "../utils/http.js";
5
+ import { findSessionFile, extractCwdFromTranscript, } from "../utils/session-finder.js";
5
6
  function getGitRemoteUrl(cwd) {
6
7
  try {
7
8
  const url = execSync("git remote get-url origin", {
@@ -28,6 +29,78 @@ function getGitBranch(cwd) {
28
29
  return null;
29
30
  }
30
31
  }
32
+ /**
33
+ * Core logic for sending transcript data to the server.
34
+ * Shared between hook-based and manual invocations.
35
+ */
36
+ async function sendTranscript(params) {
37
+ const { sessionId, transcriptPath, cwd, isHook } = params;
38
+ const exitWithError = (message) => {
39
+ console.error(message);
40
+ process.exit(isHook ? 0 : 1);
41
+ };
42
+ // Check if config exists
43
+ const config = loadConfig();
44
+ if (!config) {
45
+ exitWithError("[agentrace] Warning: Config not found. Run 'npx agentrace init' first.");
46
+ return;
47
+ }
48
+ // Get new lines from transcript
49
+ const { lines, totalLineCount } = getNewLines(transcriptPath, sessionId);
50
+ if (lines.length === 0) {
51
+ if (!isHook) {
52
+ console.log("[agentrace] No new lines to send.");
53
+ }
54
+ process.exit(0);
55
+ }
56
+ // Parse JSONL lines
57
+ const transcriptLines = [];
58
+ for (const line of lines) {
59
+ try {
60
+ transcriptLines.push(JSON.parse(line));
61
+ }
62
+ catch {
63
+ // Skip invalid JSON lines
64
+ }
65
+ }
66
+ if (transcriptLines.length === 0) {
67
+ if (!isHook) {
68
+ console.log("[agentrace] No valid transcript lines to send.");
69
+ }
70
+ process.exit(0);
71
+ }
72
+ // Extract git info only on first send (when cursor doesn't exist yet)
73
+ let gitRemoteUrl;
74
+ let gitBranch;
75
+ if (cwd && !hasCursor(sessionId)) {
76
+ gitRemoteUrl = getGitRemoteUrl(cwd) ?? undefined;
77
+ gitBranch = getGitBranch(cwd) ?? undefined;
78
+ }
79
+ // Send to server
80
+ const result = await sendIngest({
81
+ session_id: sessionId,
82
+ transcript_lines: transcriptLines,
83
+ cwd: cwd,
84
+ git_remote_url: gitRemoteUrl,
85
+ git_branch: gitBranch,
86
+ });
87
+ if (result.ok) {
88
+ // Update cursor on success
89
+ saveCursor(sessionId, totalLineCount);
90
+ if (!isHook) {
91
+ console.log(`[agentrace] Sent ${transcriptLines.length} lines for session ${sessionId}`);
92
+ }
93
+ }
94
+ else {
95
+ exitWithError(`[agentrace] Warning: ${result.error}`);
96
+ return;
97
+ }
98
+ process.exit(0);
99
+ }
100
+ /**
101
+ * Hook-based send command.
102
+ * Reads session info from stdin (provided by Claude Code hooks).
103
+ */
31
104
  export async function sendCommand() {
32
105
  // Check if config exists
33
106
  const config = loadConfig();
@@ -68,51 +141,43 @@ export async function sendCommand() {
68
141
  if (data.hook_event_name === "UserPromptSubmit") {
69
142
  await sleep(10000);
70
143
  }
71
- // Get new lines from transcript
72
- const { lines, totalLineCount } = getNewLines(transcriptPath, sessionId);
73
- if (lines.length === 0) {
74
- // No new lines to send
75
- process.exit(0);
76
- }
77
- // Parse JSONL lines
78
- const transcriptLines = [];
79
- for (const line of lines) {
80
- try {
81
- transcriptLines.push(JSON.parse(line));
82
- }
83
- catch {
84
- // Skip invalid JSON lines
85
- }
86
- }
87
- if (transcriptLines.length === 0) {
88
- process.exit(0);
89
- }
90
144
  // Use CLAUDE_PROJECT_DIR (stable project root) instead of cwd (can change during builds)
91
145
  const projectDir = process.env.CLAUDE_PROJECT_DIR || data.cwd;
92
- // Extract git info only on first send (when cursor doesn't exist yet)
93
- let gitRemoteUrl;
94
- let gitBranch;
95
- if (projectDir && !hasCursor(sessionId)) {
96
- gitRemoteUrl = getGitRemoteUrl(projectDir) ?? undefined;
97
- gitBranch = getGitBranch(projectDir) ?? undefined;
98
- }
99
- // Send to server
100
- const result = await sendIngest({
101
- session_id: sessionId,
102
- transcript_lines: transcriptLines,
146
+ await sendTranscript({
147
+ sessionId,
148
+ transcriptPath,
103
149
  cwd: projectDir,
104
- git_remote_url: gitRemoteUrl,
105
- git_branch: gitBranch,
150
+ isHook: true,
151
+ });
152
+ }
153
+ /**
154
+ * Manual send command.
155
+ * Finds session file by ID and sends to server.
156
+ */
157
+ export async function sendManualCommand(options) {
158
+ const { sessionId } = options;
159
+ // Check if config exists
160
+ const config = loadConfig();
161
+ if (!config) {
162
+ console.error("[agentrace] Error: Config not found. Run 'npx agentrace init' first.");
163
+ process.exit(1);
164
+ }
165
+ // Find session file
166
+ const transcriptPath = findSessionFile(sessionId);
167
+ if (!transcriptPath) {
168
+ console.error(`[agentrace] Error: Session file not found for ID: ${sessionId}`);
169
+ console.error(" Searched in: ~/.claude/projects/");
170
+ process.exit(1);
171
+ }
172
+ // Extract cwd from transcript
173
+ const cwd = extractCwdFromTranscript(transcriptPath) ?? undefined;
174
+ console.log(`[agentrace] Found session file: ${transcriptPath}`);
175
+ await sendTranscript({
176
+ sessionId,
177
+ transcriptPath,
178
+ cwd,
179
+ isHook: false,
106
180
  });
107
- if (result.ok) {
108
- // Update cursor on success
109
- saveCursor(sessionId, totalLineCount);
110
- }
111
- else {
112
- console.error(`[agentrace] Warning: ${result.error}`);
113
- }
114
- // Always exit 0 to not block hooks
115
- process.exit(0);
116
181
  }
117
182
  function sleep(ms) {
118
183
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -1,7 +1,7 @@
1
1
  import { deleteConfig } from "../config/manager.js";
2
2
  import { uninstallHooks, uninstallMcpServer, uninstallPreToolUseHook } from "../hooks/installer.js";
3
3
  export async function uninstallCommand() {
4
- console.log("Uninstalling Agentrace...\n");
4
+ console.log("Uninstalling AgenTrace...\n");
5
5
  // Remove hooks
6
6
  const hookResult = uninstallHooks();
7
7
  if (hookResult.success) {
@@ -236,7 +236,7 @@ export function checkMcpServerInstalled() {
236
236
  }
237
237
  // PreToolUse hook for injecting session_id into agentrace MCP tools
238
238
  const SESSION_ID_HOOK_SCRIPT = `#!/usr/bin/env node
239
- // Agentrace PreToolUse hook: Writes session_id to file for MCP tools
239
+ // Agentrace PreToolUse hook: Writes session_id and tool_use_id to file for MCP tools
240
240
  // This hook is called before agentrace MCP tools (create_plan, update_plan)
241
241
 
242
242
  const fs = require('fs');
@@ -252,9 +252,10 @@ process.stdin.on('end', () => {
252
252
  try {
253
253
  const data = JSON.parse(input);
254
254
  const sessionId = data.session_id;
255
+ const toolUseId = data.tool_use_id;
255
256
 
256
- // Write session_id to file for MCP server to read
257
- fs.writeFileSync(sessionFile, JSON.stringify({ session_id: sessionId }));
257
+ // Write session_id and tool_use_id to file for MCP server to read
258
+ fs.writeFileSync(sessionFile, JSON.stringify({ session_id: sessionId, tool_use_id: toolUseId }));
258
259
 
259
260
  // Allow the tool to proceed
260
261
  const output = {
package/dist/index.js CHANGED
@@ -2,13 +2,13 @@
2
2
  import { Command } from "commander";
3
3
  import { initCommand } from "./commands/init.js";
4
4
  import { loginCommand } from "./commands/login.js";
5
- import { sendCommand } from "./commands/send.js";
5
+ import { sendCommand, sendManualCommand } from "./commands/send.js";
6
6
  import { uninstallCommand } from "./commands/uninstall.js";
7
7
  import { onCommand } from "./commands/on.js";
8
8
  import { offCommand } from "./commands/off.js";
9
9
  import { mcpServerCommand } from "./commands/mcp-server.js";
10
10
  const program = new Command();
11
- program.name("agentrace").description("CLI for Agentrace").version("0.1.0");
11
+ program.name("agentrace").description("CLI for AgenTrace").version("0.1.0");
12
12
  program
13
13
  .command("init")
14
14
  .description("Initialize agentrace configuration and hooks")
@@ -25,9 +25,15 @@ program
25
25
  });
26
26
  program
27
27
  .command("send")
28
- .description("Send event to server (used by hooks)")
29
- .action(async () => {
30
- await sendCommand();
28
+ .description("Send event to server (used by hooks, or manually with --claude-session-id)")
29
+ .option("--claude-session-id <id>", "Send existing Claude session by ID")
30
+ .action(async (options) => {
31
+ if (options.claudeSessionId) {
32
+ await sendManualCommand({ sessionId: options.claudeSessionId });
33
+ }
34
+ else {
35
+ await sendCommand();
36
+ }
31
37
  });
32
38
  program
33
39
  .command("uninstall")
@@ -1,4 +1,4 @@
1
- export type PlanDocumentStatus = "scratch" | "draft" | "planning" | "pending" | "implementation" | "complete";
1
+ export type PlanDocumentStatus = "scratch" | "draft" | "planning" | "pending" | "ready" | "implementation" | "complete";
2
2
  export interface Project {
3
3
  id: string;
4
4
  canonical_git_repository: string;
@@ -23,10 +23,12 @@ export interface PlanDocumentEvent {
23
23
  user_id: string | null;
24
24
  user_name: string | null;
25
25
  patch: string;
26
+ message: string;
26
27
  created_at: string;
27
28
  }
28
29
  export interface ListPlansResponse {
29
30
  plans: PlanDocument[];
31
+ next_cursor?: string;
30
32
  }
31
33
  export interface SearchPlansParams {
32
34
  gitRemoteUrl?: string;
@@ -40,12 +42,15 @@ export interface CreatePlanRequest {
40
42
  description: string;
41
43
  body: string;
42
44
  claude_session_id?: string;
45
+ tool_use_id?: string;
43
46
  }
44
47
  export interface UpdatePlanRequest {
45
48
  description?: string;
46
49
  body?: string;
47
50
  patch?: string;
51
+ message?: string;
48
52
  claude_session_id?: string;
53
+ tool_use_id?: string;
49
54
  }
50
55
  export declare class PlanDocumentClient {
51
56
  private serverUrl;
@@ -58,5 +63,5 @@ export declare class PlanDocumentClient {
58
63
  createPlan(req: CreatePlanRequest): Promise<PlanDocument>;
59
64
  updatePlan(id: string, req: UpdatePlanRequest): Promise<PlanDocument>;
60
65
  deletePlan(id: string): Promise<void>;
61
- setStatus(id: string, status: PlanDocumentStatus): Promise<PlanDocument>;
66
+ setStatus(id: string, status: PlanDocumentStatus, message?: string): Promise<PlanDocument>;
62
67
  }
@@ -5,7 +5,7 @@ export class PlanDocumentClient {
5
5
  constructor() {
6
6
  const config = loadConfig();
7
7
  if (!config) {
8
- throw new Error("Agentrace is not configured. Run 'npx agentrace init' first.");
8
+ throw new Error("AgenTrace is not configured. Run 'npx agentrace init' first.");
9
9
  }
10
10
  this.serverUrl = config.server_url;
11
11
  this.apiKey = config.api_key;
@@ -63,7 +63,7 @@ export class PlanDocumentClient {
63
63
  async deletePlan(id) {
64
64
  await this.request("DELETE", `/api/plans/${id}`);
65
65
  }
66
- async setStatus(id, status) {
67
- return this.request("PATCH", `/api/plans/${id}/status`, { status });
66
+ async setStatus(id, status, message) {
67
+ return this.request("PATCH", `/api/plans/${id}/status`, { status, message });
68
68
  }
69
69
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Find a Claude session JSONL file by session ID.
3
+ * Searches recursively in ~/.claude/projects/
4
+ */
5
+ export declare function findSessionFile(sessionId: string): string | null;
6
+ /**
7
+ * Extract the cwd from a transcript JSONL file.
8
+ * Looks for the first entry with type="user" that has a cwd field.
9
+ */
10
+ export declare function extractCwdFromTranscript(transcriptPath: string): string | null;
@@ -0,0 +1,66 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as os from "node:os";
4
+ const CLAUDE_PROJECTS_DIR = path.join(os.homedir(), ".claude", "projects");
5
+ /**
6
+ * Find a Claude session JSONL file by session ID.
7
+ * Searches recursively in ~/.claude/projects/
8
+ */
9
+ export function findSessionFile(sessionId) {
10
+ const targetFileName = `${sessionId}.jsonl`;
11
+ if (!fs.existsSync(CLAUDE_PROJECTS_DIR)) {
12
+ return null;
13
+ }
14
+ // Recursively search for the session file
15
+ const result = searchDirectory(CLAUDE_PROJECTS_DIR, targetFileName);
16
+ return result;
17
+ }
18
+ function searchDirectory(dir, targetFileName) {
19
+ try {
20
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
21
+ for (const entry of entries) {
22
+ const fullPath = path.join(dir, entry.name);
23
+ if (entry.isDirectory()) {
24
+ const result = searchDirectory(fullPath, targetFileName);
25
+ if (result) {
26
+ return result;
27
+ }
28
+ }
29
+ else if (entry.isFile() && entry.name === targetFileName) {
30
+ return fullPath;
31
+ }
32
+ }
33
+ }
34
+ catch {
35
+ // Skip directories we can't read
36
+ }
37
+ return null;
38
+ }
39
+ /**
40
+ * Extract the cwd from a transcript JSONL file.
41
+ * Looks for the first entry with type="user" that has a cwd field.
42
+ */
43
+ export function extractCwdFromTranscript(transcriptPath) {
44
+ try {
45
+ if (!fs.existsSync(transcriptPath)) {
46
+ return null;
47
+ }
48
+ const content = fs.readFileSync(transcriptPath, "utf-8");
49
+ const lines = content.split("\n").filter((line) => line.trim() !== "");
50
+ for (const line of lines) {
51
+ try {
52
+ const entry = JSON.parse(line);
53
+ if (entry.type === "user" && entry.cwd) {
54
+ return entry.cwd;
55
+ }
56
+ }
57
+ catch {
58
+ // Skip invalid JSON lines
59
+ }
60
+ }
61
+ }
62
+ catch {
63
+ // File read error
64
+ }
65
+ return null;
66
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentrace",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "CLI for AgenTrace - Claude Code session tracker",
5
5
  "type": "module",
6
6
  "bin": {