agentrace 0.0.3 → 0.0.5

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,24 +6,29 @@ 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
- const ListPlansSchema = z.object({
26
- git_remote_url: z.string().describe("Git remote URL to filter plans"),
28
+ const SearchPlansSchema = z.object({
29
+ git_remote_url: z.string().optional().describe("Git remote URL to filter by project"),
30
+ status: z.string().optional().describe("Comma-separated statuses to filter (e.g., 'planning,implementation')"),
31
+ description: z.string().optional().describe("Partial match search on plan description"),
27
32
  });
28
33
  const ReadPlanSchema = z.object({
29
34
  id: z.string().describe("Plan document ID"),
@@ -35,19 +40,23 @@ const CreatePlanSchema = z.object({
35
40
  const UpdatePlanSchema = z.object({
36
41
  id: z.string().describe("Plan document ID"),
37
42
  body: z.string().describe("Updated plan content in Markdown format"),
43
+ message: z.string().optional().describe("One-line description of what was changed"),
38
44
  });
39
45
  const SetPlanStatusSchema = z.object({
40
46
  id: z.string().describe("Plan document ID"),
41
- 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"),
42
49
  });
43
50
  // Tool descriptions with usage guidance
44
51
  const TOOL_DESCRIPTIONS = {
45
- list_plans: `List plan documents for a repository.
52
+ search_plans: `Search plan documents with filtering options.
46
53
 
47
54
  WHEN TO USE:
48
55
  - When you need to check existing plans for the current repository
49
56
  - When the user asks about available plans or implementation documents
50
- - Before creating a new plan to avoid duplicates`,
57
+ - Before creating a new plan to avoid duplicates
58
+ - When searching for plans by status (e.g., find all plans in 'scratch' status)
59
+ - When searching for plans by description keyword`,
51
60
  read_plan: `Read a plan document by ID.
52
61
 
53
62
  WHEN TO USE:
@@ -62,7 +71,7 @@ WHEN TO USE:
62
71
  - When the user asks you to save or persist a plan
63
72
  - When planning significant features, refactoring, or architectural changes
64
73
 
65
- 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.
66
75
  The project is automatically determined from the session's git repository.`,
67
76
  update_plan: `Update an existing plan document.
68
77
 
@@ -84,15 +93,17 @@ Available statuses:
84
93
  - draft: Plan not yet fully considered (optional intermediate status)
85
94
  - planning: Plan is being designed/refined through discussion
86
95
  - pending: Waiting for approval or blocked
96
+ - ready: Plan is finalized and ready for implementation (awaiting review/approval to start)
87
97
  - implementation: Active development is in progress
88
98
  - complete: The work described in the plan is finished
89
99
 
90
- BASIC FLOW: scratch → planning → implementation → complete
100
+ BASIC FLOW: scratch → planning → ready → implementation → complete
91
101
  (draft and pending are optional auxiliary statuses)
92
102
 
93
103
  STATUS TRANSITION GUIDELINES:
94
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
95
- - 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
96
107
  - implementation → complete: When all work described in the plan is finished, change status to complete
97
108
 
98
109
  CAUTION:
@@ -102,10 +113,10 @@ export async function mcpServerCommand() {
102
113
  const server = new McpServer({
103
114
  name: "agentrace",
104
115
  version: "1.0.0",
105
- description: `Agentrace Plan Document Management Server.
116
+ description: `AgenTrace Plan Document Management Server.
106
117
 
107
118
  This server provides tools to manage implementation and design plans.
108
- 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.
109
120
 
110
121
  IMPORTANT GUIDELINES:
111
122
  - When you create a design or implementation plan, ALWAYS save it using create_plan
@@ -119,16 +130,20 @@ IMPORTANT GUIDELINES:
119
130
  }
120
131
  return client;
121
132
  }
122
- // list_plans tool
123
- server.tool("list_plans", TOOL_DESCRIPTIONS.list_plans, ListPlansSchema.shape, async (args) => {
133
+ // search_plans tool
134
+ server.tool("search_plans", TOOL_DESCRIPTIONS.search_plans, SearchPlansSchema.shape, async (args) => {
124
135
  try {
125
- const plans = await getClient().listPlans(args.git_remote_url);
136
+ const plans = await getClient().searchPlans({
137
+ gitRemoteUrl: args.git_remote_url,
138
+ status: args.status,
139
+ description: args.description,
140
+ });
126
141
  if (plans.length === 0) {
127
142
  return {
128
143
  content: [
129
144
  {
130
145
  type: "text",
131
- text: "No plans found for this repository.",
146
+ text: "No plans found matching the search criteria.",
132
147
  },
133
148
  ],
134
149
  };
@@ -137,6 +152,7 @@ IMPORTANT GUIDELINES:
137
152
  id: plan.id,
138
153
  description: plan.description,
139
154
  status: plan.status,
155
+ git_remote_url: plan.project?.canonical_git_repository || null,
140
156
  updated_at: plan.updated_at,
141
157
  collaborators: plan.collaborators.map((c) => c.display_name).join(", "),
142
158
  }));
@@ -189,12 +205,13 @@ IMPORTANT GUIDELINES:
189
205
  // create_plan tool
190
206
  server.tool("create_plan", TOOL_DESCRIPTIONS.create_plan, CreatePlanSchema.shape, async (args) => {
191
207
  try {
192
- // Read session_id from file written by PreToolUse hook
193
- const claudeSessionId = getSessionIdFromFile();
208
+ // Read session_id and tool_use_id from file written by PreToolUse hook
209
+ const sessionInfo = getSessionInfoFromFile();
194
210
  const plan = await getClient().createPlan({
195
211
  description: args.description,
196
212
  body: args.body,
197
- claude_session_id: claudeSessionId,
213
+ claude_session_id: sessionInfo.session_id,
214
+ tool_use_id: sessionInfo.tool_use_id,
198
215
  });
199
216
  return {
200
217
  content: [
@@ -220,8 +237,8 @@ IMPORTANT GUIDELINES:
220
237
  // update_plan tool
221
238
  server.tool("update_plan", TOOL_DESCRIPTIONS.update_plan, UpdatePlanSchema.shape, async (args) => {
222
239
  try {
223
- // Read session_id from file written by PreToolUse hook
224
- const claudeSessionId = getSessionIdFromFile();
240
+ // Read session_id and tool_use_id from file written by PreToolUse hook
241
+ const sessionInfo = getSessionInfoFromFile();
225
242
  // Get current plan to compute patch
226
243
  const currentPlan = await getClient().getPlan(args.id);
227
244
  // Compute patch using diff-match-patch
@@ -230,7 +247,9 @@ IMPORTANT GUIDELINES:
230
247
  const plan = await getClient().updatePlan(args.id, {
231
248
  body: args.body,
232
249
  patch: patchText,
233
- claude_session_id: claudeSessionId,
250
+ message: args.message,
251
+ claude_session_id: sessionInfo.session_id,
252
+ tool_use_id: sessionInfo.tool_use_id,
234
253
  });
235
254
  return {
236
255
  content: [
@@ -256,7 +275,7 @@ IMPORTANT GUIDELINES:
256
275
  // set_plan_status tool
257
276
  server.tool("set_plan_status", TOOL_DESCRIPTIONS.set_plan_status, SetPlanStatusSchema.shape, async (args) => {
258
277
  try {
259
- const plan = await getClient().setStatus(args.id, args.status);
278
+ const plan = await getClient().setStatus(args.id, args.status, args.message);
260
279
  return {
261
280
  content: [
262
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,11 +23,17 @@ 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[];
30
31
  }
32
+ export interface SearchPlansParams {
33
+ gitRemoteUrl?: string;
34
+ status?: string;
35
+ description?: string;
36
+ }
31
37
  export interface ListEventsResponse {
32
38
  events: PlanDocumentEvent[];
33
39
  }
@@ -35,23 +41,26 @@ export interface CreatePlanRequest {
35
41
  description: string;
36
42
  body: string;
37
43
  claude_session_id?: string;
44
+ tool_use_id?: string;
38
45
  }
39
46
  export interface UpdatePlanRequest {
40
47
  description?: string;
41
48
  body?: string;
42
49
  patch?: string;
50
+ message?: string;
43
51
  claude_session_id?: string;
52
+ tool_use_id?: string;
44
53
  }
45
54
  export declare class PlanDocumentClient {
46
55
  private serverUrl;
47
56
  private apiKey;
48
57
  constructor();
49
58
  private request;
50
- listPlans(gitRemoteUrl?: string): Promise<PlanDocument[]>;
59
+ searchPlans(params?: SearchPlansParams): Promise<PlanDocument[]>;
51
60
  getPlan(id: string): Promise<PlanDocument>;
52
61
  getPlanEvents(id: string): Promise<PlanDocumentEvent[]>;
53
62
  createPlan(req: CreatePlanRequest): Promise<PlanDocument>;
54
63
  updatePlan(id: string, req: UpdatePlanRequest): Promise<PlanDocument>;
55
64
  deletePlan(id: string): Promise<void>;
56
- setStatus(id: string, status: PlanDocumentStatus): Promise<PlanDocument>;
65
+ setStatus(id: string, status: PlanDocumentStatus, message?: string): Promise<PlanDocument>;
57
66
  }
@@ -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;
@@ -31,11 +31,19 @@ export class PlanDocumentClient {
31
31
  }
32
32
  return response.json();
33
33
  }
34
- async listPlans(gitRemoteUrl) {
35
- let path = "/api/plans";
36
- if (gitRemoteUrl) {
37
- path += `?git_remote_url=${encodeURIComponent(gitRemoteUrl)}`;
34
+ async searchPlans(params = {}) {
35
+ const searchParams = new URLSearchParams();
36
+ if (params.gitRemoteUrl) {
37
+ searchParams.set("git_remote_url", params.gitRemoteUrl);
38
38
  }
39
+ if (params.status) {
40
+ searchParams.set("status", params.status);
41
+ }
42
+ if (params.description) {
43
+ searchParams.set("description", params.description);
44
+ }
45
+ const query = searchParams.toString();
46
+ const path = query ? `/api/plans?${query}` : "/api/plans";
39
47
  const response = await this.request("GET", path);
40
48
  return response.plans;
41
49
  }
@@ -55,7 +63,7 @@ export class PlanDocumentClient {
55
63
  async deletePlan(id) {
56
64
  await this.request("DELETE", `/api/plans/${id}`);
57
65
  }
58
- async setStatus(id, status) {
59
- 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 });
60
68
  }
61
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.3",
3
+ "version": "0.0.5",
4
4
  "description": "CLI for AgenTrace - Claude Code session tracker",
5
5
  "type": "module",
6
6
  "bin": {