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.
- package/dist/commands/init.js +1 -1
- package/dist/commands/mcp-server.js +45 -26
- package/dist/commands/off.js +1 -1
- package/dist/commands/on.js +1 -1
- package/dist/commands/send.d.ts +11 -0
- package/dist/commands/send.js +106 -41
- package/dist/commands/uninstall.js +1 -1
- package/dist/hooks/installer.js +4 -3
- package/dist/index.js +11 -5
- package/dist/mcp/plan-document-client.d.ts +12 -3
- package/dist/mcp/plan-document-client.js +15 -7
- package/dist/utils/session-finder.d.ts +10 -0
- package/dist/utils/session-finder.js +66 -0
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -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("
|
|
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
|
|
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
|
|
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
|
|
23
|
+
// Ignore errors, return empty object
|
|
21
24
|
}
|
|
22
|
-
return
|
|
25
|
+
return {};
|
|
23
26
|
}
|
|
24
27
|
// Tool schemas
|
|
25
|
-
const
|
|
26
|
-
git_remote_url: z.string().describe("Git remote URL to filter
|
|
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
|
-
|
|
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
|
|
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 →
|
|
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: `
|
|
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
|
|
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
|
-
//
|
|
123
|
-
server.tool("
|
|
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().
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
{
|
package/dist/commands/off.js
CHANGED
|
@@ -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("
|
|
7
|
+
console.log("AgenTrace is not configured. Run 'npx agentrace init' first.");
|
|
8
8
|
return;
|
|
9
9
|
}
|
|
10
10
|
const result = uninstallHooks();
|
package/dist/commands/on.js
CHANGED
|
@@ -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("
|
|
11
|
+
console.log("AgenTrace is not configured. Run 'npx agentrace init' first.");
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
// Determine hook command
|
package/dist/commands/send.d.ts
CHANGED
|
@@ -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>;
|
package/dist/commands/send.js
CHANGED
|
@@ -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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
|
4
|
+
console.log("Uninstalling AgenTrace...\n");
|
|
5
5
|
// Remove hooks
|
|
6
6
|
const hookResult = uninstallHooks();
|
|
7
7
|
if (hookResult.success) {
|
package/dist/hooks/installer.js
CHANGED
|
@@ -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
|
|
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
|
-
.
|
|
30
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
|
35
|
-
|
|
36
|
-
if (gitRemoteUrl) {
|
|
37
|
-
|
|
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
|
+
}
|