agentrace 0.0.2 → 0.0.3
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 +25 -1
- package/dist/commands/mcp-server.d.ts +1 -0
- package/dist/commands/mcp-server.js +284 -0
- package/dist/commands/off.js +16 -1
- package/dist/commands/on.js +25 -1
- package/dist/commands/send.js +8 -0
- package/dist/commands/uninstall.js +17 -1
- package/dist/hooks/installer.d.ts +22 -0
- package/dist/hooks/installer.js +278 -6
- package/dist/index.js +7 -0
- package/dist/mcp/plan-document-client.d.ts +57 -0
- package/dist/mcp/plan-document-client.js +61 -0
- package/package.json +5 -2
package/dist/commands/init.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { saveConfig, getConfigPath } from "../config/manager.js";
|
|
4
|
-
import { installHooks } from "../hooks/installer.js";
|
|
4
|
+
import { installHooks, installMcpServer, installPreToolUseHook } from "../hooks/installer.js";
|
|
5
5
|
import { startCallbackServer, getRandomPort, generateToken, } from "../utils/callback-server.js";
|
|
6
6
|
import { openBrowser, buildSetupUrl } from "../utils/browser.js";
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -80,6 +80,30 @@ export async function initCommand(options = {}) {
|
|
|
80
80
|
else {
|
|
81
81
|
console.error(`✗ ${hookResult.message}`);
|
|
82
82
|
}
|
|
83
|
+
// Install MCP server
|
|
84
|
+
let mcpCommand;
|
|
85
|
+
let mcpArgs;
|
|
86
|
+
if (options.dev) {
|
|
87
|
+
const cliRoot = path.resolve(__dirname, "../..");
|
|
88
|
+
const indexPath = path.join(cliRoot, "src/index.ts");
|
|
89
|
+
mcpCommand = "npx";
|
|
90
|
+
mcpArgs = ["tsx", indexPath, "mcp-server"];
|
|
91
|
+
}
|
|
92
|
+
const mcpResult = installMcpServer({ command: mcpCommand, args: mcpArgs });
|
|
93
|
+
if (mcpResult.success) {
|
|
94
|
+
console.log(`✓ ${mcpResult.message}`);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
console.error(`✗ ${mcpResult.message}`);
|
|
98
|
+
}
|
|
99
|
+
// Install PreToolUse hook for session_id injection
|
|
100
|
+
const preToolUseResult = installPreToolUseHook();
|
|
101
|
+
if (preToolUseResult.success) {
|
|
102
|
+
console.log(`✓ ${preToolUseResult.message}`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
console.error(`✗ ${preToolUseResult.message}`);
|
|
106
|
+
}
|
|
83
107
|
console.log("\n✓ Setup complete!");
|
|
84
108
|
}
|
|
85
109
|
catch (error) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function mcpServerCommand(): Promise<void>;
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { patchMake, patchToText } from "diff-match-patch-es";
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import * as os from "node:os";
|
|
8
|
+
import { PlanDocumentClient } from "../mcp/plan-document-client.js";
|
|
9
|
+
// Read session_id from file written by PreToolUse hook
|
|
10
|
+
function getSessionIdFromFile() {
|
|
11
|
+
try {
|
|
12
|
+
const sessionFile = path.join(os.homedir(), ".agentrace", "current-session.json");
|
|
13
|
+
if (fs.existsSync(sessionFile)) {
|
|
14
|
+
const content = fs.readFileSync(sessionFile, "utf-8");
|
|
15
|
+
const data = JSON.parse(content);
|
|
16
|
+
return data.session_id;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Ignore errors, return undefined
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
// Tool schemas
|
|
25
|
+
const ListPlansSchema = z.object({
|
|
26
|
+
git_remote_url: z.string().describe("Git remote URL to filter plans"),
|
|
27
|
+
});
|
|
28
|
+
const ReadPlanSchema = z.object({
|
|
29
|
+
id: z.string().describe("Plan document ID"),
|
|
30
|
+
});
|
|
31
|
+
const CreatePlanSchema = z.object({
|
|
32
|
+
description: z.string().describe("Short description of the plan"),
|
|
33
|
+
body: z.string().describe("Plan content in Markdown format"),
|
|
34
|
+
});
|
|
35
|
+
const UpdatePlanSchema = z.object({
|
|
36
|
+
id: z.string().describe("Plan document ID"),
|
|
37
|
+
body: z.string().describe("Updated plan content in Markdown format"),
|
|
38
|
+
});
|
|
39
|
+
const SetPlanStatusSchema = z.object({
|
|
40
|
+
id: z.string().describe("Plan document ID"),
|
|
41
|
+
status: z.enum(["scratch", "draft", "planning", "pending", "implementation", "complete"]).describe("New status for the plan"),
|
|
42
|
+
});
|
|
43
|
+
// Tool descriptions with usage guidance
|
|
44
|
+
const TOOL_DESCRIPTIONS = {
|
|
45
|
+
list_plans: `List plan documents for a repository.
|
|
46
|
+
|
|
47
|
+
WHEN TO USE:
|
|
48
|
+
- When you need to check existing plans for the current repository
|
|
49
|
+
- When the user asks about available plans or implementation documents
|
|
50
|
+
- Before creating a new plan to avoid duplicates`,
|
|
51
|
+
read_plan: `Read a plan document by ID.
|
|
52
|
+
|
|
53
|
+
WHEN TO USE:
|
|
54
|
+
- When the user asks you to check or review a specific plan by ID
|
|
55
|
+
- When you need to understand an existing plan before making changes
|
|
56
|
+
- When the user references a plan ID in their request`,
|
|
57
|
+
create_plan: `Create a new plan document to record implementation or design plans.
|
|
58
|
+
|
|
59
|
+
WHEN TO USE:
|
|
60
|
+
- ALWAYS use this when you create a design or implementation plan
|
|
61
|
+
- When entering plan mode and documenting your approach
|
|
62
|
+
- When the user asks you to save or persist a plan
|
|
63
|
+
- When planning significant features, refactoring, or architectural changes
|
|
64
|
+
|
|
65
|
+
The plan will be saved to Agentrace server and can be reviewed by the team.
|
|
66
|
+
The project is automatically determined from the session's git repository.`,
|
|
67
|
+
update_plan: `Update an existing plan document.
|
|
68
|
+
|
|
69
|
+
WHEN TO USE:
|
|
70
|
+
- When the user asks you to modify a specific plan by ID
|
|
71
|
+
- When implementation details change and the plan needs updating
|
|
72
|
+
- When you need to add progress notes or completion status to a plan
|
|
73
|
+
|
|
74
|
+
Changes are tracked with diff patches for history.`,
|
|
75
|
+
set_plan_status: `Set the status of a plan document.
|
|
76
|
+
|
|
77
|
+
WHEN TO USE:
|
|
78
|
+
- When transitioning a plan from planning to implementation phase
|
|
79
|
+
- When marking a plan as complete after finishing the work
|
|
80
|
+
- When the user explicitly asks to change the status of a plan
|
|
81
|
+
|
|
82
|
+
Available statuses:
|
|
83
|
+
- scratch: Initial rough notes, starting point for discussion with AI
|
|
84
|
+
- draft: Plan not yet fully considered (optional intermediate status)
|
|
85
|
+
- planning: Plan is being designed/refined through discussion
|
|
86
|
+
- pending: Waiting for approval or blocked
|
|
87
|
+
- implementation: Active development is in progress
|
|
88
|
+
- complete: The work described in the plan is finished
|
|
89
|
+
|
|
90
|
+
BASIC FLOW: scratch → planning → implementation → complete
|
|
91
|
+
(draft and pending are optional auxiliary statuses)
|
|
92
|
+
|
|
93
|
+
STATUS TRANSITION GUIDELINES:
|
|
94
|
+
- 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
|
|
96
|
+
- implementation → complete: When all work described in the plan is finished, change status to complete
|
|
97
|
+
|
|
98
|
+
CAUTION:
|
|
99
|
+
- When a plan is in "implementation" status, someone else might already be working on it. Check with the team before starting work on such plans.`,
|
|
100
|
+
};
|
|
101
|
+
export async function mcpServerCommand() {
|
|
102
|
+
const server = new McpServer({
|
|
103
|
+
name: "agentrace",
|
|
104
|
+
version: "1.0.0",
|
|
105
|
+
description: `Agentrace Plan Document Management Server.
|
|
106
|
+
|
|
107
|
+
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.
|
|
109
|
+
|
|
110
|
+
IMPORTANT GUIDELINES:
|
|
111
|
+
- When you create a design or implementation plan, ALWAYS save it using create_plan
|
|
112
|
+
- When the user asks you to check or modify a plan by ID, use the appropriate tool
|
|
113
|
+
- Plans help track what you're working on and enable team collaboration`,
|
|
114
|
+
});
|
|
115
|
+
let client = null;
|
|
116
|
+
function getClient() {
|
|
117
|
+
if (!client) {
|
|
118
|
+
client = new PlanDocumentClient();
|
|
119
|
+
}
|
|
120
|
+
return client;
|
|
121
|
+
}
|
|
122
|
+
// list_plans tool
|
|
123
|
+
server.tool("list_plans", TOOL_DESCRIPTIONS.list_plans, ListPlansSchema.shape, async (args) => {
|
|
124
|
+
try {
|
|
125
|
+
const plans = await getClient().listPlans(args.git_remote_url);
|
|
126
|
+
if (plans.length === 0) {
|
|
127
|
+
return {
|
|
128
|
+
content: [
|
|
129
|
+
{
|
|
130
|
+
type: "text",
|
|
131
|
+
text: "No plans found for this repository.",
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const planList = plans.map((plan) => ({
|
|
137
|
+
id: plan.id,
|
|
138
|
+
description: plan.description,
|
|
139
|
+
status: plan.status,
|
|
140
|
+
updated_at: plan.updated_at,
|
|
141
|
+
collaborators: plan.collaborators.map((c) => c.display_name).join(", "),
|
|
142
|
+
}));
|
|
143
|
+
return {
|
|
144
|
+
content: [
|
|
145
|
+
{
|
|
146
|
+
type: "text",
|
|
147
|
+
text: JSON.stringify(planList, null, 2),
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return {
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: "text",
|
|
157
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
isError: true,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
// read_plan tool
|
|
165
|
+
server.tool("read_plan", TOOL_DESCRIPTIONS.read_plan, ReadPlanSchema.shape, async (args) => {
|
|
166
|
+
try {
|
|
167
|
+
const plan = await getClient().getPlan(args.id);
|
|
168
|
+
return {
|
|
169
|
+
content: [
|
|
170
|
+
{
|
|
171
|
+
type: "text",
|
|
172
|
+
text: `# ${plan.description}\n\nStatus: ${plan.status}\n\n${plan.body}`,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
return {
|
|
179
|
+
content: [
|
|
180
|
+
{
|
|
181
|
+
type: "text",
|
|
182
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
isError: true,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
// create_plan tool
|
|
190
|
+
server.tool("create_plan", TOOL_DESCRIPTIONS.create_plan, CreatePlanSchema.shape, async (args) => {
|
|
191
|
+
try {
|
|
192
|
+
// Read session_id from file written by PreToolUse hook
|
|
193
|
+
const claudeSessionId = getSessionIdFromFile();
|
|
194
|
+
const plan = await getClient().createPlan({
|
|
195
|
+
description: args.description,
|
|
196
|
+
body: args.body,
|
|
197
|
+
claude_session_id: claudeSessionId,
|
|
198
|
+
});
|
|
199
|
+
return {
|
|
200
|
+
content: [
|
|
201
|
+
{
|
|
202
|
+
type: "text",
|
|
203
|
+
text: `Plan created successfully.\n\nID: ${plan.id}\nDescription: ${plan.description}`,
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
return {
|
|
210
|
+
content: [
|
|
211
|
+
{
|
|
212
|
+
type: "text",
|
|
213
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
isError: true,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
// update_plan tool
|
|
221
|
+
server.tool("update_plan", TOOL_DESCRIPTIONS.update_plan, UpdatePlanSchema.shape, async (args) => {
|
|
222
|
+
try {
|
|
223
|
+
// Read session_id from file written by PreToolUse hook
|
|
224
|
+
const claudeSessionId = getSessionIdFromFile();
|
|
225
|
+
// Get current plan to compute patch
|
|
226
|
+
const currentPlan = await getClient().getPlan(args.id);
|
|
227
|
+
// Compute patch using diff-match-patch
|
|
228
|
+
const patches = patchMake(currentPlan.body, args.body);
|
|
229
|
+
const patchText = patchToText(patches);
|
|
230
|
+
const plan = await getClient().updatePlan(args.id, {
|
|
231
|
+
body: args.body,
|
|
232
|
+
patch: patchText,
|
|
233
|
+
claude_session_id: claudeSessionId,
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
content: [
|
|
237
|
+
{
|
|
238
|
+
type: "text",
|
|
239
|
+
text: `Plan updated successfully.\n\nID: ${plan.id}\nDescription: ${plan.description}`,
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
return {
|
|
246
|
+
content: [
|
|
247
|
+
{
|
|
248
|
+
type: "text",
|
|
249
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
isError: true,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
// set_plan_status tool
|
|
257
|
+
server.tool("set_plan_status", TOOL_DESCRIPTIONS.set_plan_status, SetPlanStatusSchema.shape, async (args) => {
|
|
258
|
+
try {
|
|
259
|
+
const plan = await getClient().setStatus(args.id, args.status);
|
|
260
|
+
return {
|
|
261
|
+
content: [
|
|
262
|
+
{
|
|
263
|
+
type: "text",
|
|
264
|
+
text: `Plan status updated successfully.\n\nID: ${plan.id}\nDescription: ${plan.description}\nStatus: ${plan.status}`,
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
return {
|
|
271
|
+
content: [
|
|
272
|
+
{
|
|
273
|
+
type: "text",
|
|
274
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
isError: true,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
// Start the server with stdio transport
|
|
282
|
+
const transport = new StdioServerTransport();
|
|
283
|
+
await server.connect(transport);
|
|
284
|
+
}
|
package/dist/commands/off.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { uninstallHooks } from "../hooks/installer.js";
|
|
1
|
+
import { uninstallHooks, uninstallMcpServer, uninstallPreToolUseHook } from "../hooks/installer.js";
|
|
2
2
|
import { loadConfig } from "../config/manager.js";
|
|
3
3
|
export async function offCommand() {
|
|
4
4
|
// Check if config exists
|
|
@@ -15,4 +15,19 @@ export async function offCommand() {
|
|
|
15
15
|
else {
|
|
16
16
|
console.error(`✗ ${result.message}`);
|
|
17
17
|
}
|
|
18
|
+
// Remove PreToolUse hook
|
|
19
|
+
const preToolUseResult = uninstallPreToolUseHook();
|
|
20
|
+
if (preToolUseResult.success) {
|
|
21
|
+
console.log(`✓ ${preToolUseResult.message}`);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.error(`✗ ${preToolUseResult.message}`);
|
|
25
|
+
}
|
|
26
|
+
const mcpResult = uninstallMcpServer();
|
|
27
|
+
if (mcpResult.success) {
|
|
28
|
+
console.log(`✓ ${mcpResult.message}`);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.error(`✗ ${mcpResult.message}`);
|
|
32
|
+
}
|
|
18
33
|
}
|
package/dist/commands/on.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { installHooks } from "../hooks/installer.js";
|
|
3
|
+
import { installHooks, installMcpServer, installPreToolUseHook } from "../hooks/installer.js";
|
|
4
4
|
import { loadConfig } from "../config/manager.js";
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
@@ -26,4 +26,28 @@ export async function onCommand(options = {}) {
|
|
|
26
26
|
else {
|
|
27
27
|
console.error(`✗ ${result.message}`);
|
|
28
28
|
}
|
|
29
|
+
// Install MCP server
|
|
30
|
+
let mcpCommand;
|
|
31
|
+
let mcpArgs;
|
|
32
|
+
if (options.dev) {
|
|
33
|
+
const cliRoot = path.resolve(__dirname, "../..");
|
|
34
|
+
const indexPath = path.join(cliRoot, "src/index.ts");
|
|
35
|
+
mcpCommand = "npx";
|
|
36
|
+
mcpArgs = ["tsx", indexPath, "mcp-server"];
|
|
37
|
+
}
|
|
38
|
+
const mcpResult = installMcpServer({ command: mcpCommand, args: mcpArgs });
|
|
39
|
+
if (mcpResult.success) {
|
|
40
|
+
console.log(`✓ ${mcpResult.message}`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.error(`✗ ${mcpResult.message}`);
|
|
44
|
+
}
|
|
45
|
+
// Install PreToolUse hook for session_id injection
|
|
46
|
+
const preToolUseResult = installPreToolUseHook();
|
|
47
|
+
if (preToolUseResult.success) {
|
|
48
|
+
console.log(`✓ ${preToolUseResult.message}`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.error(`✗ ${preToolUseResult.message}`);
|
|
52
|
+
}
|
|
29
53
|
}
|
package/dist/commands/send.js
CHANGED
|
@@ -63,6 +63,11 @@ export async function sendCommand() {
|
|
|
63
63
|
console.error("[agentrace] Warning: Missing session_id or transcript_path");
|
|
64
64
|
process.exit(0);
|
|
65
65
|
}
|
|
66
|
+
// For UserPromptSubmit, wait for transcript to be written
|
|
67
|
+
// (Claude hasn't started processing yet, so transcript may not be updated)
|
|
68
|
+
if (data.hook_event_name === "UserPromptSubmit") {
|
|
69
|
+
await sleep(10000);
|
|
70
|
+
}
|
|
66
71
|
// Get new lines from transcript
|
|
67
72
|
const { lines, totalLineCount } = getNewLines(transcriptPath, sessionId);
|
|
68
73
|
if (lines.length === 0) {
|
|
@@ -109,6 +114,9 @@ export async function sendCommand() {
|
|
|
109
114
|
// Always exit 0 to not block hooks
|
|
110
115
|
process.exit(0);
|
|
111
116
|
}
|
|
117
|
+
function sleep(ms) {
|
|
118
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
119
|
+
}
|
|
112
120
|
function readStdin() {
|
|
113
121
|
return new Promise((resolve, reject) => {
|
|
114
122
|
let data = "";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { deleteConfig } from "../config/manager.js";
|
|
2
|
-
import { uninstallHooks } from "../hooks/installer.js";
|
|
2
|
+
import { uninstallHooks, uninstallMcpServer, uninstallPreToolUseHook } from "../hooks/installer.js";
|
|
3
3
|
export async function uninstallCommand() {
|
|
4
4
|
console.log("Uninstalling Agentrace...\n");
|
|
5
5
|
// Remove hooks
|
|
@@ -10,6 +10,22 @@ export async function uninstallCommand() {
|
|
|
10
10
|
else {
|
|
11
11
|
console.error(`✗ ${hookResult.message}`);
|
|
12
12
|
}
|
|
13
|
+
// Remove PreToolUse hook
|
|
14
|
+
const preToolUseResult = uninstallPreToolUseHook();
|
|
15
|
+
if (preToolUseResult.success) {
|
|
16
|
+
console.log(`✓ ${preToolUseResult.message}`);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
console.error(`✗ ${preToolUseResult.message}`);
|
|
20
|
+
}
|
|
21
|
+
// Remove MCP server
|
|
22
|
+
const mcpResult = uninstallMcpServer();
|
|
23
|
+
if (mcpResult.success) {
|
|
24
|
+
console.log(`✓ ${mcpResult.message}`);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.error(`✗ ${mcpResult.message}`);
|
|
28
|
+
}
|
|
13
29
|
// Remove config
|
|
14
30
|
const configRemoved = deleteConfig();
|
|
15
31
|
if (configRemoved) {
|
|
@@ -10,3 +10,25 @@ export declare function uninstallHooks(): {
|
|
|
10
10
|
message: string;
|
|
11
11
|
};
|
|
12
12
|
export declare function checkHooksInstalled(): boolean;
|
|
13
|
+
export interface InstallMcpServerOptions {
|
|
14
|
+
command?: string;
|
|
15
|
+
args?: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function installMcpServer(options?: InstallMcpServerOptions): {
|
|
18
|
+
success: boolean;
|
|
19
|
+
message: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function uninstallMcpServer(): {
|
|
22
|
+
success: boolean;
|
|
23
|
+
message: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function checkMcpServerInstalled(): boolean;
|
|
26
|
+
export declare function installPreToolUseHook(): {
|
|
27
|
+
success: boolean;
|
|
28
|
+
message: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function uninstallPreToolUseHook(): {
|
|
31
|
+
success: boolean;
|
|
32
|
+
message: string;
|
|
33
|
+
};
|
|
34
|
+
export declare function checkPreToolUseHookInstalled(): boolean;
|
package/dist/hooks/installer.js
CHANGED
|
@@ -2,6 +2,11 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), ".claude", "settings.json");
|
|
5
|
+
// MCP servers are configured in ~/.claude.json, NOT in settings.json
|
|
6
|
+
const CLAUDE_CONFIG_PATH = path.join(os.homedir(), ".claude.json");
|
|
7
|
+
// Agentrace hooks directory
|
|
8
|
+
const AGENTRACE_HOOKS_DIR = path.join(os.homedir(), ".agentrace", "hooks");
|
|
9
|
+
const SESSION_ID_HOOK_PATH = path.join(AGENTRACE_HOOKS_DIR, "inject-session-id.js");
|
|
5
10
|
const DEFAULT_COMMAND = "npx agentrace send";
|
|
6
11
|
function createAgentraceHook(command) {
|
|
7
12
|
return {
|
|
@@ -27,17 +32,49 @@ export function installHooks(options = {}) {
|
|
|
27
32
|
if (!settings.hooks) {
|
|
28
33
|
settings.hooks = {};
|
|
29
34
|
}
|
|
30
|
-
// Add Stop hook
|
|
35
|
+
// Add Stop hook (transcript diff is sent on each Stop)
|
|
31
36
|
if (!settings.hooks.Stop) {
|
|
32
37
|
settings.hooks.Stop = [];
|
|
33
38
|
}
|
|
39
|
+
// Add UserPromptSubmit hook (transcript is sent when user sends a message)
|
|
40
|
+
if (!settings.hooks.UserPromptSubmit) {
|
|
41
|
+
settings.hooks.UserPromptSubmit = [];
|
|
42
|
+
}
|
|
43
|
+
// Add SubagentStop hook (transcript is sent when a subagent task completes)
|
|
44
|
+
if (!settings.hooks.SubagentStop) {
|
|
45
|
+
settings.hooks.SubagentStop = [];
|
|
46
|
+
}
|
|
47
|
+
// Add PostToolUse hook (transcript is sent after each tool use for real-time updates)
|
|
48
|
+
if (!settings.hooks.PostToolUse) {
|
|
49
|
+
settings.hooks.PostToolUse = [];
|
|
50
|
+
}
|
|
34
51
|
const hasStopHook = settings.hooks.Stop.some((matcher) => matcher.hooks?.some(isAgentraceHook));
|
|
35
|
-
|
|
52
|
+
const hasUserPromptSubmitHook = settings.hooks.UserPromptSubmit.some((matcher) => matcher.hooks?.some(isAgentraceHook));
|
|
53
|
+
const hasSubagentStopHook = settings.hooks.SubagentStop.some((matcher) => matcher.hooks?.some(isAgentraceHook));
|
|
54
|
+
const hasPostToolUseHook = settings.hooks.PostToolUse.some((matcher) => matcher.hooks?.some(isAgentraceHook));
|
|
55
|
+
if (hasStopHook && hasUserPromptSubmitHook && hasSubagentStopHook && hasPostToolUseHook) {
|
|
36
56
|
return { success: true, message: "Hooks already installed (skipped)" };
|
|
37
57
|
}
|
|
38
|
-
|
|
39
|
-
hooks
|
|
40
|
-
|
|
58
|
+
if (!hasStopHook) {
|
|
59
|
+
settings.hooks.Stop.push({
|
|
60
|
+
hooks: [agentraceHook],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (!hasUserPromptSubmitHook) {
|
|
64
|
+
settings.hooks.UserPromptSubmit.push({
|
|
65
|
+
hooks: [agentraceHook],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (!hasSubagentStopHook) {
|
|
69
|
+
settings.hooks.SubagentStop.push({
|
|
70
|
+
hooks: [agentraceHook],
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (!hasPostToolUseHook) {
|
|
74
|
+
settings.hooks.PostToolUse.push({
|
|
75
|
+
hooks: [agentraceHook],
|
|
76
|
+
});
|
|
77
|
+
}
|
|
41
78
|
// Ensure directory exists
|
|
42
79
|
const dir = path.dirname(CLAUDE_SETTINGS_PATH);
|
|
43
80
|
if (!fs.existsSync(dir)) {
|
|
@@ -69,6 +106,27 @@ export function uninstallHooks() {
|
|
|
69
106
|
delete settings.hooks.Stop;
|
|
70
107
|
}
|
|
71
108
|
}
|
|
109
|
+
// Remove agentrace hooks from UserPromptSubmit
|
|
110
|
+
if (settings.hooks.UserPromptSubmit) {
|
|
111
|
+
settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter((matcher) => !matcher.hooks?.some(isAgentraceHook));
|
|
112
|
+
if (settings.hooks.UserPromptSubmit.length === 0) {
|
|
113
|
+
delete settings.hooks.UserPromptSubmit;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Remove agentrace hooks from SubagentStop
|
|
117
|
+
if (settings.hooks.SubagentStop) {
|
|
118
|
+
settings.hooks.SubagentStop = settings.hooks.SubagentStop.filter((matcher) => !matcher.hooks?.some(isAgentraceHook));
|
|
119
|
+
if (settings.hooks.SubagentStop.length === 0) {
|
|
120
|
+
delete settings.hooks.SubagentStop;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Remove agentrace hooks from PostToolUse
|
|
124
|
+
if (settings.hooks.PostToolUse) {
|
|
125
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter((matcher) => !matcher.hooks?.some(isAgentraceHook));
|
|
126
|
+
if (settings.hooks.PostToolUse.length === 0) {
|
|
127
|
+
delete settings.hooks.PostToolUse;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
72
130
|
// Clean up empty hooks object
|
|
73
131
|
if (Object.keys(settings.hooks).length === 0) {
|
|
74
132
|
delete settings.hooks;
|
|
@@ -92,7 +150,221 @@ export function checkHooksInstalled() {
|
|
|
92
150
|
const content = fs.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8");
|
|
93
151
|
const settings = JSON.parse(content);
|
|
94
152
|
const hasStopHook = settings.hooks?.Stop?.some((matcher) => matcher.hooks?.some(isAgentraceHook));
|
|
95
|
-
|
|
153
|
+
const hasUserPromptSubmitHook = settings.hooks?.UserPromptSubmit?.some((matcher) => matcher.hooks?.some(isAgentraceHook));
|
|
154
|
+
const hasSubagentStopHook = settings.hooks?.SubagentStop?.some((matcher) => matcher.hooks?.some(isAgentraceHook));
|
|
155
|
+
const hasPostToolUseHook = settings.hooks?.PostToolUse?.some((matcher) => matcher.hooks?.some(isAgentraceHook));
|
|
156
|
+
return !!hasStopHook && !!hasUserPromptSubmitHook && !!hasSubagentStopHook && !!hasPostToolUseHook;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// MCP Server installer functions
|
|
163
|
+
const MCP_SERVER_NAME = "agentrace";
|
|
164
|
+
export function installMcpServer(options = {}) {
|
|
165
|
+
const command = options.command || "npx";
|
|
166
|
+
const args = options.args || ["agentrace", "mcp-server"];
|
|
167
|
+
try {
|
|
168
|
+
let config = {};
|
|
169
|
+
// Load existing config if file exists
|
|
170
|
+
// MCP servers are configured in ~/.claude.json (NOT settings.json)
|
|
171
|
+
if (fs.existsSync(CLAUDE_CONFIG_PATH)) {
|
|
172
|
+
const content = fs.readFileSync(CLAUDE_CONFIG_PATH, "utf-8");
|
|
173
|
+
config = JSON.parse(content);
|
|
174
|
+
}
|
|
175
|
+
// Initialize mcpServers structure if not present
|
|
176
|
+
if (!config.mcpServers) {
|
|
177
|
+
config.mcpServers = {};
|
|
178
|
+
}
|
|
179
|
+
// Check if already installed
|
|
180
|
+
if (config.mcpServers[MCP_SERVER_NAME]) {
|
|
181
|
+
// Update existing config
|
|
182
|
+
config.mcpServers[MCP_SERVER_NAME] = { command, args };
|
|
183
|
+
fs.writeFileSync(CLAUDE_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
184
|
+
return { success: true, message: "MCP server config updated" };
|
|
185
|
+
}
|
|
186
|
+
// Add MCP server config
|
|
187
|
+
config.mcpServers[MCP_SERVER_NAME] = { command, args };
|
|
188
|
+
// Write config
|
|
189
|
+
fs.writeFileSync(CLAUDE_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
190
|
+
return { success: true, message: `MCP server added to ${CLAUDE_CONFIG_PATH}` };
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
194
|
+
return { success: false, message: `Failed to install MCP server: ${message}` };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export function uninstallMcpServer() {
|
|
198
|
+
try {
|
|
199
|
+
if (!fs.existsSync(CLAUDE_CONFIG_PATH)) {
|
|
200
|
+
return { success: true, message: "No config file found" };
|
|
201
|
+
}
|
|
202
|
+
const content = fs.readFileSync(CLAUDE_CONFIG_PATH, "utf-8");
|
|
203
|
+
const config = JSON.parse(content);
|
|
204
|
+
if (!config.mcpServers || !config.mcpServers[MCP_SERVER_NAME]) {
|
|
205
|
+
return { success: true, message: "MCP server not configured" };
|
|
206
|
+
}
|
|
207
|
+
// Remove agentrace MCP server
|
|
208
|
+
delete config.mcpServers[MCP_SERVER_NAME];
|
|
209
|
+
// Clean up empty mcpServers object
|
|
210
|
+
if (Object.keys(config.mcpServers).length === 0) {
|
|
211
|
+
delete config.mcpServers;
|
|
212
|
+
}
|
|
213
|
+
fs.writeFileSync(CLAUDE_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
214
|
+
return {
|
|
215
|
+
success: true,
|
|
216
|
+
message: `Removed MCP server from ${CLAUDE_CONFIG_PATH}`,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
221
|
+
return { success: false, message: `Failed to uninstall MCP server: ${message}` };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
export function checkMcpServerInstalled() {
|
|
225
|
+
try {
|
|
226
|
+
if (!fs.existsSync(CLAUDE_CONFIG_PATH)) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
const content = fs.readFileSync(CLAUDE_CONFIG_PATH, "utf-8");
|
|
230
|
+
const config = JSON.parse(content);
|
|
231
|
+
return !!config.mcpServers?.[MCP_SERVER_NAME];
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// PreToolUse hook for injecting session_id into agentrace MCP tools
|
|
238
|
+
const SESSION_ID_HOOK_SCRIPT = `#!/usr/bin/env node
|
|
239
|
+
// Agentrace PreToolUse hook: Writes session_id to file for MCP tools
|
|
240
|
+
// This hook is called before agentrace MCP tools (create_plan, update_plan)
|
|
241
|
+
|
|
242
|
+
const fs = require('fs');
|
|
243
|
+
const os = require('os');
|
|
244
|
+
const path = require('path');
|
|
245
|
+
|
|
246
|
+
const sessionFile = path.join(os.homedir(), '.agentrace', 'current-session.json');
|
|
247
|
+
|
|
248
|
+
let input = '';
|
|
249
|
+
process.stdin.setEncoding('utf8');
|
|
250
|
+
process.stdin.on('data', chunk => { input += chunk; });
|
|
251
|
+
process.stdin.on('end', () => {
|
|
252
|
+
try {
|
|
253
|
+
const data = JSON.parse(input);
|
|
254
|
+
const sessionId = data.session_id;
|
|
255
|
+
|
|
256
|
+
// Write session_id to file for MCP server to read
|
|
257
|
+
fs.writeFileSync(sessionFile, JSON.stringify({ session_id: sessionId }));
|
|
258
|
+
|
|
259
|
+
// Allow the tool to proceed
|
|
260
|
+
const output = {
|
|
261
|
+
hookSpecificOutput: {
|
|
262
|
+
hookEventName: "PreToolUse",
|
|
263
|
+
permissionDecision: "allow"
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
console.log(JSON.stringify(output));
|
|
267
|
+
} catch (e) {
|
|
268
|
+
process.stderr.write('Error: ' + e.message);
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
`;
|
|
273
|
+
const AGENTRACE_MCP_TOOLS_MATCHER = "mcp__agentrace__create_plan|mcp__agentrace__update_plan";
|
|
274
|
+
function isAgentracePreToolUseHook(matcher) {
|
|
275
|
+
return matcher.matcher === AGENTRACE_MCP_TOOLS_MATCHER &&
|
|
276
|
+
matcher.hooks?.some(h => h.command?.includes("inject-session-id"));
|
|
277
|
+
}
|
|
278
|
+
export function installPreToolUseHook() {
|
|
279
|
+
try {
|
|
280
|
+
// Create hooks directory if not exists
|
|
281
|
+
if (!fs.existsSync(AGENTRACE_HOOKS_DIR)) {
|
|
282
|
+
fs.mkdirSync(AGENTRACE_HOOKS_DIR, { recursive: true });
|
|
283
|
+
}
|
|
284
|
+
// Write hook script
|
|
285
|
+
fs.writeFileSync(SESSION_ID_HOOK_PATH, SESSION_ID_HOOK_SCRIPT, { mode: 0o755 });
|
|
286
|
+
// Load existing settings
|
|
287
|
+
let settings = {};
|
|
288
|
+
if (fs.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
289
|
+
const content = fs.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8");
|
|
290
|
+
settings = JSON.parse(content);
|
|
291
|
+
}
|
|
292
|
+
// Initialize hooks structure if not present
|
|
293
|
+
if (!settings.hooks) {
|
|
294
|
+
settings.hooks = {};
|
|
295
|
+
}
|
|
296
|
+
if (!settings.hooks.PreToolUse) {
|
|
297
|
+
settings.hooks.PreToolUse = [];
|
|
298
|
+
}
|
|
299
|
+
// Check if already installed
|
|
300
|
+
const hasPreToolUseHook = settings.hooks.PreToolUse.some(isAgentracePreToolUseHook);
|
|
301
|
+
if (hasPreToolUseHook) {
|
|
302
|
+
return { success: true, message: "PreToolUse hook already installed (skipped)" };
|
|
303
|
+
}
|
|
304
|
+
// Add PreToolUse hook
|
|
305
|
+
settings.hooks.PreToolUse.push({
|
|
306
|
+
matcher: AGENTRACE_MCP_TOOLS_MATCHER,
|
|
307
|
+
hooks: [
|
|
308
|
+
{
|
|
309
|
+
type: "command",
|
|
310
|
+
command: SESSION_ID_HOOK_PATH,
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
});
|
|
314
|
+
// Ensure directory exists
|
|
315
|
+
const dir = path.dirname(CLAUDE_SETTINGS_PATH);
|
|
316
|
+
if (!fs.existsSync(dir)) {
|
|
317
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
318
|
+
}
|
|
319
|
+
// Write settings
|
|
320
|
+
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
321
|
+
return { success: true, message: `PreToolUse hook installed to ${SESSION_ID_HOOK_PATH}` };
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
325
|
+
return { success: false, message: `Failed to install PreToolUse hook: ${message}` };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
export function uninstallPreToolUseHook() {
|
|
329
|
+
try {
|
|
330
|
+
// Remove hook script
|
|
331
|
+
if (fs.existsSync(SESSION_ID_HOOK_PATH)) {
|
|
332
|
+
fs.unlinkSync(SESSION_ID_HOOK_PATH);
|
|
333
|
+
}
|
|
334
|
+
// Remove from settings
|
|
335
|
+
if (!fs.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
336
|
+
return { success: true, message: "No settings file found" };
|
|
337
|
+
}
|
|
338
|
+
const content = fs.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8");
|
|
339
|
+
const settings = JSON.parse(content);
|
|
340
|
+
if (!settings.hooks?.PreToolUse) {
|
|
341
|
+
return { success: true, message: "No PreToolUse hooks configured" };
|
|
342
|
+
}
|
|
343
|
+
// Remove agentrace PreToolUse hooks
|
|
344
|
+
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter((matcher) => !isAgentracePreToolUseHook(matcher));
|
|
345
|
+
if (settings.hooks.PreToolUse.length === 0) {
|
|
346
|
+
delete settings.hooks.PreToolUse;
|
|
347
|
+
}
|
|
348
|
+
// Clean up empty hooks object
|
|
349
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
350
|
+
delete settings.hooks;
|
|
351
|
+
}
|
|
352
|
+
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
353
|
+
return { success: true, message: "PreToolUse hook removed" };
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
357
|
+
return { success: false, message: `Failed to uninstall PreToolUse hook: ${message}` };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
export function checkPreToolUseHookInstalled() {
|
|
361
|
+
try {
|
|
362
|
+
if (!fs.existsSync(CLAUDE_SETTINGS_PATH) || !fs.existsSync(SESSION_ID_HOOK_PATH)) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
const content = fs.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8");
|
|
366
|
+
const settings = JSON.parse(content);
|
|
367
|
+
return settings.hooks?.PreToolUse?.some(isAgentracePreToolUseHook) ?? false;
|
|
96
368
|
}
|
|
97
369
|
catch {
|
|
98
370
|
return false;
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { sendCommand } 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
|
+
import { mcpServerCommand } from "./commands/mcp-server.js";
|
|
9
10
|
const program = new Command();
|
|
10
11
|
program.name("agentrace").description("CLI for Agentrace").version("0.1.0");
|
|
11
12
|
program
|
|
@@ -47,4 +48,10 @@ program
|
|
|
47
48
|
.action(async () => {
|
|
48
49
|
await offCommand();
|
|
49
50
|
});
|
|
51
|
+
program
|
|
52
|
+
.command("mcp-server")
|
|
53
|
+
.description("Run MCP server for Claude Code integration (stdio)")
|
|
54
|
+
.action(async () => {
|
|
55
|
+
await mcpServerCommand();
|
|
56
|
+
});
|
|
50
57
|
program.parse();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type PlanDocumentStatus = "scratch" | "draft" | "planning" | "pending" | "implementation" | "complete";
|
|
2
|
+
export interface Project {
|
|
3
|
+
id: string;
|
|
4
|
+
canonical_git_repository: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PlanDocument {
|
|
7
|
+
id: string;
|
|
8
|
+
description: string;
|
|
9
|
+
body: string;
|
|
10
|
+
project: Project | null;
|
|
11
|
+
status: PlanDocumentStatus;
|
|
12
|
+
collaborators: {
|
|
13
|
+
id: string;
|
|
14
|
+
display_name: string;
|
|
15
|
+
}[];
|
|
16
|
+
created_at: string;
|
|
17
|
+
updated_at: string;
|
|
18
|
+
}
|
|
19
|
+
export interface PlanDocumentEvent {
|
|
20
|
+
id: string;
|
|
21
|
+
plan_document_id: string;
|
|
22
|
+
claude_session_id: string | null;
|
|
23
|
+
user_id: string | null;
|
|
24
|
+
user_name: string | null;
|
|
25
|
+
patch: string;
|
|
26
|
+
created_at: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ListPlansResponse {
|
|
29
|
+
plans: PlanDocument[];
|
|
30
|
+
}
|
|
31
|
+
export interface ListEventsResponse {
|
|
32
|
+
events: PlanDocumentEvent[];
|
|
33
|
+
}
|
|
34
|
+
export interface CreatePlanRequest {
|
|
35
|
+
description: string;
|
|
36
|
+
body: string;
|
|
37
|
+
claude_session_id?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface UpdatePlanRequest {
|
|
40
|
+
description?: string;
|
|
41
|
+
body?: string;
|
|
42
|
+
patch?: string;
|
|
43
|
+
claude_session_id?: string;
|
|
44
|
+
}
|
|
45
|
+
export declare class PlanDocumentClient {
|
|
46
|
+
private serverUrl;
|
|
47
|
+
private apiKey;
|
|
48
|
+
constructor();
|
|
49
|
+
private request;
|
|
50
|
+
listPlans(gitRemoteUrl?: string): Promise<PlanDocument[]>;
|
|
51
|
+
getPlan(id: string): Promise<PlanDocument>;
|
|
52
|
+
getPlanEvents(id: string): Promise<PlanDocumentEvent[]>;
|
|
53
|
+
createPlan(req: CreatePlanRequest): Promise<PlanDocument>;
|
|
54
|
+
updatePlan(id: string, req: UpdatePlanRequest): Promise<PlanDocument>;
|
|
55
|
+
deletePlan(id: string): Promise<void>;
|
|
56
|
+
setStatus(id: string, status: PlanDocumentStatus): Promise<PlanDocument>;
|
|
57
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { loadConfig } from "../config/manager.js";
|
|
2
|
+
export class PlanDocumentClient {
|
|
3
|
+
serverUrl;
|
|
4
|
+
apiKey;
|
|
5
|
+
constructor() {
|
|
6
|
+
const config = loadConfig();
|
|
7
|
+
if (!config) {
|
|
8
|
+
throw new Error("Agentrace is not configured. Run 'npx agentrace init' first.");
|
|
9
|
+
}
|
|
10
|
+
this.serverUrl = config.server_url;
|
|
11
|
+
this.apiKey = config.api_key;
|
|
12
|
+
}
|
|
13
|
+
async request(method, path, body) {
|
|
14
|
+
const url = `${this.serverUrl}${path}`;
|
|
15
|
+
const headers = {
|
|
16
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
17
|
+
"Content-Type": "application/json",
|
|
18
|
+
};
|
|
19
|
+
const response = await fetch(url, {
|
|
20
|
+
method,
|
|
21
|
+
headers,
|
|
22
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
const errorText = await response.text();
|
|
26
|
+
throw new Error(`API request failed: ${response.status} ${errorText}`);
|
|
27
|
+
}
|
|
28
|
+
// Handle 204 No Content
|
|
29
|
+
if (response.status === 204) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return response.json();
|
|
33
|
+
}
|
|
34
|
+
async listPlans(gitRemoteUrl) {
|
|
35
|
+
let path = "/api/plans";
|
|
36
|
+
if (gitRemoteUrl) {
|
|
37
|
+
path += `?git_remote_url=${encodeURIComponent(gitRemoteUrl)}`;
|
|
38
|
+
}
|
|
39
|
+
const response = await this.request("GET", path);
|
|
40
|
+
return response.plans;
|
|
41
|
+
}
|
|
42
|
+
async getPlan(id) {
|
|
43
|
+
return this.request("GET", `/api/plans/${id}`);
|
|
44
|
+
}
|
|
45
|
+
async getPlanEvents(id) {
|
|
46
|
+
const response = await this.request("GET", `/api/plans/${id}/events`);
|
|
47
|
+
return response.events;
|
|
48
|
+
}
|
|
49
|
+
async createPlan(req) {
|
|
50
|
+
return this.request("POST", "/api/plans", req);
|
|
51
|
+
}
|
|
52
|
+
async updatePlan(id, req) {
|
|
53
|
+
return this.request("PATCH", `/api/plans/${id}`, req);
|
|
54
|
+
}
|
|
55
|
+
async deletePlan(id) {
|
|
56
|
+
await this.request("DELETE", `/api/plans/${id}`);
|
|
57
|
+
}
|
|
58
|
+
async setStatus(id, status) {
|
|
59
|
+
return this.request("PATCH", `/api/plans/${id}/status`, { status });
|
|
60
|
+
}
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentrace",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "CLI for AgenTrace - Claude Code session tracker",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -35,7 +35,10 @@
|
|
|
35
35
|
"url": "https://github.com/satetsu888/agentrace/issues"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
39
|
+
"commander": "^12.0.0",
|
|
40
|
+
"diff-match-patch-es": "^1.0.0",
|
|
41
|
+
"zod": "^3.25.0"
|
|
39
42
|
},
|
|
40
43
|
"devDependencies": {
|
|
41
44
|
"@types/node": "^20.0.0",
|