kartman-dev 1.0.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-config/_claude-plugin/plugin.json +11 -0
- package/dist/claude-config/_mcp.json +38 -0
- package/dist/claude-config/commands/linear-issue.md +59 -0
- package/dist/claude-config/commands/linear-project.md +70 -0
- package/dist/claude-config/hooks/hooks.json +14 -0
- package/dist/claude-config/scripts/block-secrets.sh +71 -0
- package/dist/claude-config/skills/doc-drift-check/SKILL.md +140 -0
- package/dist/claude-config/skills/doc-drift-check/references/severity-guide.md +174 -0
- package/dist/claude-config/worktrees/polymorphic-cooking-goose/.claude/.claude-plugin/plugin.json +11 -0
- package/dist/claude-config/worktrees/polymorphic-cooking-goose/.claude/.mcp.json +23 -0
- package/dist/claude-config/worktrees/polymorphic-cooking-goose/.claude/settings.json +13 -0
- package/dist/claude-config/worktrees/polymorphic-cooking-goose/.claude/skills/doc-drift-check/SKILL.md +140 -0
- package/dist/claude-config/worktrees/polymorphic-cooking-goose/.claude/skills/doc-drift-check/references/severity-guide.md +174 -0
- package/dist/claude-config/worktrees/valiant-sparking-hare/.claude/.claude-plugin/plugin.json +11 -0
- package/dist/claude-config/worktrees/valiant-sparking-hare/.claude/.mcp.json +23 -0
- package/dist/claude-config/worktrees/valiant-sparking-hare/.claude/commands/linear-issue.md +59 -0
- package/dist/claude-config/worktrees/valiant-sparking-hare/.claude/commands/linear-project.md +70 -0
- package/dist/claude-config/worktrees/valiant-sparking-hare/.claude/settings.json +13 -0
- package/dist/claude-config/worktrees/valiant-sparking-hare/.claude/skills/doc-drift-check/SKILL.md +140 -0
- package/dist/claude-config/worktrees/valiant-sparking-hare/.claude/skills/doc-drift-check/references/severity-guide.md +174 -0
- package/dist/claude-config/worktrees/valiant-sparking-hare/.github/workflows/claude-code-review.yml +32 -0
- package/dist/claude-config/worktrees/valiant-sparking-hare/.github/workflows/claude.yml +37 -0
- package/dist/claude-config/worktrees/valiant-sparking-hare/.nvmrc +1 -0
- package/dist/claude-config/worktrees/valiant-sparking-hare/.prettierignore +11 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +653 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command as Command6 } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/start.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
|
|
9
|
+
// src/linear/agent.ts
|
|
10
|
+
import { LinearClient } from "@linear/sdk";
|
|
11
|
+
|
|
12
|
+
// src/agent.ts
|
|
13
|
+
import * as fs from "fs";
|
|
14
|
+
import * as path from "path";
|
|
15
|
+
import {
|
|
16
|
+
query
|
|
17
|
+
} from "@anthropic-ai/claude-agent-sdk";
|
|
18
|
+
|
|
19
|
+
// src/config.ts
|
|
20
|
+
import { appendFileSync, cpSync, existsSync, mkdirSync, readFileSync, renameSync } from "fs";
|
|
21
|
+
import { dirname, resolve, join } from "path";
|
|
22
|
+
import { fileURLToPath } from "url";
|
|
23
|
+
var PLUGIN_DIRNAME = "agent-plugin";
|
|
24
|
+
function getBundledConfigPath() {
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const __dirname = dirname(__filename);
|
|
27
|
+
return resolve(__dirname, "claude-config");
|
|
28
|
+
}
|
|
29
|
+
function getPluginPath(cwd = process.cwd()) {
|
|
30
|
+
return join(cwd, PLUGIN_DIRNAME);
|
|
31
|
+
}
|
|
32
|
+
function installClaudeConfig(cwd = process.cwd()) {
|
|
33
|
+
const bundledPath = getBundledConfigPath();
|
|
34
|
+
if (!existsSync(bundledPath)) {
|
|
35
|
+
console.warn("Warning: Bundled claude-config not found. Skipping plugin installation.");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const pluginDir = getPluginPath(cwd);
|
|
39
|
+
mkdirSync(pluginDir, { recursive: true });
|
|
40
|
+
cpSync(bundledPath, pluginDir, { recursive: true, force: true });
|
|
41
|
+
const underscorePlugin = join(pluginDir, "_claude-plugin");
|
|
42
|
+
const dotPlugin = join(pluginDir, ".claude-plugin");
|
|
43
|
+
if (existsSync(underscorePlugin)) {
|
|
44
|
+
renameSync(underscorePlugin, dotPlugin);
|
|
45
|
+
}
|
|
46
|
+
const underscoreMcp = join(pluginDir, "_mcp.json");
|
|
47
|
+
const dotMcp = join(pluginDir, ".mcp.json");
|
|
48
|
+
if (existsSync(underscoreMcp)) {
|
|
49
|
+
renameSync(underscoreMcp, dotMcp);
|
|
50
|
+
}
|
|
51
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
52
|
+
if (existsSync(gitignorePath)) {
|
|
53
|
+
const content = readFileSync(gitignorePath, "utf-8");
|
|
54
|
+
if (!content.includes(PLUGIN_DIRNAME)) {
|
|
55
|
+
appendFileSync(gitignorePath, `
|
|
56
|
+
# Kartman agent plugin (installed at runtime)
|
|
57
|
+
${PLUGIN_DIRNAME}/
|
|
58
|
+
`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
console.log(`Installed agent plugin to ${pluginDir}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/agent.ts
|
|
65
|
+
var Agent = class {
|
|
66
|
+
currentSessionId;
|
|
67
|
+
isRunning = false;
|
|
68
|
+
externalSessionId;
|
|
69
|
+
activeQuery;
|
|
70
|
+
pendingToolUses = /* @__PURE__ */ new Map();
|
|
71
|
+
constructor(externalSessionId) {
|
|
72
|
+
this.externalSessionId = externalSessionId;
|
|
73
|
+
}
|
|
74
|
+
// ============================================
|
|
75
|
+
// Interrupt mechanism (private)
|
|
76
|
+
// ============================================
|
|
77
|
+
get interruptPath() {
|
|
78
|
+
return path.join("/tmp", `agent-${this.externalSessionId}.interrupt`);
|
|
79
|
+
}
|
|
80
|
+
get isInterrupted() {
|
|
81
|
+
try {
|
|
82
|
+
if (fs.existsSync(this.interruptPath)) {
|
|
83
|
+
return fs.readFileSync(this.interruptPath, "utf-8").trim() === "true";
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
requestInterrupt() {
|
|
91
|
+
fs.writeFileSync(this.interruptPath, "true", "utf-8");
|
|
92
|
+
}
|
|
93
|
+
clearInterrupt() {
|
|
94
|
+
try {
|
|
95
|
+
if (fs.existsSync(this.interruptPath)) {
|
|
96
|
+
fs.unlinkSync(this.interruptPath);
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// ============================================
|
|
102
|
+
// Event methods - Override in subclasses
|
|
103
|
+
// ============================================
|
|
104
|
+
/**
|
|
105
|
+
* Called when agent starts processing
|
|
106
|
+
*/
|
|
107
|
+
async onStart() {
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Called when agent emits a thinking/reasoning text
|
|
111
|
+
*/
|
|
112
|
+
async onThinking(block, isSubagent) {
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Called when agent starts using a tool (before result is available)
|
|
116
|
+
*/
|
|
117
|
+
async onToolUse(block, isSubagent) {
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Called when a tool completes with its result
|
|
121
|
+
*/
|
|
122
|
+
async onToolResult(block, result, isSubagent) {
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Called when agent updates its plan (via TodoWrite)
|
|
126
|
+
*/
|
|
127
|
+
async onPlanUpdate(todos, isSubagent) {
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Called when agent completes successfully
|
|
131
|
+
*/
|
|
132
|
+
async onComplete(message) {
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Called when agent encounters an error
|
|
136
|
+
*/
|
|
137
|
+
async onError(error) {
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Called when agent is stopped via stop command
|
|
141
|
+
*/
|
|
142
|
+
async onStop() {
|
|
143
|
+
}
|
|
144
|
+
// ============================================
|
|
145
|
+
// Main execution logic
|
|
146
|
+
// ============================================
|
|
147
|
+
/**
|
|
148
|
+
* Run the agent with the given prompt
|
|
149
|
+
*/
|
|
150
|
+
async run(runOptions) {
|
|
151
|
+
const {
|
|
152
|
+
prompt,
|
|
153
|
+
sessionId,
|
|
154
|
+
resumeSession = false,
|
|
155
|
+
forkSession = false,
|
|
156
|
+
cwd = process.cwd(),
|
|
157
|
+
options: additionalOptions = {}
|
|
158
|
+
} = runOptions;
|
|
159
|
+
this.isRunning = true;
|
|
160
|
+
let resultSessionId = sessionId ?? "";
|
|
161
|
+
let finalResult;
|
|
162
|
+
let wasInterrupted = false;
|
|
163
|
+
this.clearInterrupt();
|
|
164
|
+
try {
|
|
165
|
+
await this.onStart();
|
|
166
|
+
process.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
|
|
167
|
+
const sdkOptions = {
|
|
168
|
+
settingSources: ["project"],
|
|
169
|
+
permissionMode: "bypassPermissions",
|
|
170
|
+
allowDangerouslySkipPermissions: true,
|
|
171
|
+
plugins: [{ type: "local", path: getPluginPath(cwd) }],
|
|
172
|
+
systemPrompt: {
|
|
173
|
+
preset: "claude_code",
|
|
174
|
+
type: "preset"
|
|
175
|
+
},
|
|
176
|
+
cwd,
|
|
177
|
+
...additionalOptions
|
|
178
|
+
};
|
|
179
|
+
if (resumeSession && sessionId) {
|
|
180
|
+
sdkOptions.resume = sessionId;
|
|
181
|
+
sdkOptions.forkSession = forkSession;
|
|
182
|
+
}
|
|
183
|
+
console.log(`[Agent.run] resume=${resumeSession} | sdkResume=${sdkOptions.resume ?? "none"} | pluginPath=${getPluginPath(cwd)}`);
|
|
184
|
+
console.log(`[Agent.run] prompt=${prompt.substring(0, 300)}...`);
|
|
185
|
+
this.activeQuery = query({ prompt, options: sdkOptions });
|
|
186
|
+
for await (const message of this.activeQuery) {
|
|
187
|
+
if (this.isInterrupted) {
|
|
188
|
+
console.log("Interrupt flag detected, stopping agent...");
|
|
189
|
+
await this.activeQuery.interrupt();
|
|
190
|
+
wasInterrupted = true;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
if (message.type === "system") {
|
|
194
|
+
const sysMsg = message;
|
|
195
|
+
if (sysMsg.subtype === "init") {
|
|
196
|
+
resultSessionId = sysMsg.session_id;
|
|
197
|
+
this.currentSessionId = sysMsg.session_id;
|
|
198
|
+
console.log(`[Agent.run] SDK session initialized: ${sysMsg.session_id}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (message.type === "assistant") {
|
|
202
|
+
await this.processAssistantMessage(message);
|
|
203
|
+
}
|
|
204
|
+
if (message.type === "user") {
|
|
205
|
+
await this.processUserMessage(message);
|
|
206
|
+
}
|
|
207
|
+
if (message.type === "result") {
|
|
208
|
+
const resultMsg = message;
|
|
209
|
+
if (resultMsg.subtype === "success" && resultMsg.result) {
|
|
210
|
+
finalResult = resultMsg.result;
|
|
211
|
+
}
|
|
212
|
+
await this.onComplete(resultMsg);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
sessionId: resultSessionId,
|
|
217
|
+
result: finalResult,
|
|
218
|
+
interrupted: wasInterrupted
|
|
219
|
+
};
|
|
220
|
+
} catch (error) {
|
|
221
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
222
|
+
await this.onError(err);
|
|
223
|
+
return {
|
|
224
|
+
sessionId: resultSessionId,
|
|
225
|
+
error: err.message,
|
|
226
|
+
interrupted: wasInterrupted
|
|
227
|
+
};
|
|
228
|
+
} finally {
|
|
229
|
+
this.isRunning = false;
|
|
230
|
+
this.activeQuery = void 0;
|
|
231
|
+
this.clearInterrupt();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Stop the agent session
|
|
236
|
+
* Sets interrupt flag and calls onStop callback
|
|
237
|
+
*/
|
|
238
|
+
async stop() {
|
|
239
|
+
this.requestInterrupt();
|
|
240
|
+
await this.onStop();
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Process an assistant message: emit thinking, buffer tool uses for later matching
|
|
244
|
+
*/
|
|
245
|
+
async processAssistantMessage(message) {
|
|
246
|
+
const content = message.message.content;
|
|
247
|
+
const isSubagent = message.parent_tool_use_id !== null;
|
|
248
|
+
for (const block of content) {
|
|
249
|
+
if (block.type === "text") {
|
|
250
|
+
await this.onThinking(block, isSubagent);
|
|
251
|
+
}
|
|
252
|
+
if (block.type === "tool_use") {
|
|
253
|
+
if (block.name === "TodoWrite") {
|
|
254
|
+
const input = block.input;
|
|
255
|
+
if (input.todos) {
|
|
256
|
+
await this.onPlanUpdate(input.todos, isSubagent);
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
await this.onToolUse(block, isSubagent);
|
|
260
|
+
this.pendingToolUses.set(block.id, { block, isSubagent });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Process a user message: match tool results to buffered tool uses
|
|
267
|
+
*/
|
|
268
|
+
async processUserMessage(message) {
|
|
269
|
+
const content = message.message.content;
|
|
270
|
+
if (typeof content === "string") return;
|
|
271
|
+
for (const block of content) {
|
|
272
|
+
if (block.type !== "tool_result") continue;
|
|
273
|
+
const resultBlock = block;
|
|
274
|
+
const pending = this.pendingToolUses.get(resultBlock.tool_use_id);
|
|
275
|
+
if (!pending) continue;
|
|
276
|
+
this.pendingToolUses.delete(resultBlock.tool_use_id);
|
|
277
|
+
let result = "";
|
|
278
|
+
if (typeof resultBlock.content === "string") {
|
|
279
|
+
result = resultBlock.content;
|
|
280
|
+
} else if (Array.isArray(resultBlock.content)) {
|
|
281
|
+
result = resultBlock.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
282
|
+
}
|
|
283
|
+
await this.onToolResult(pending.block, result, pending.isSubagent);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// src/linear/agent.ts
|
|
289
|
+
var LinearAgent = class _LinearAgent extends Agent {
|
|
290
|
+
linearClient;
|
|
291
|
+
linearSessionId;
|
|
292
|
+
lastThoughtTime = 0;
|
|
293
|
+
thoughtDebounceMs = 1e3;
|
|
294
|
+
isCompleted = false;
|
|
295
|
+
constructor(config) {
|
|
296
|
+
super(config.linearSessionId);
|
|
297
|
+
this.linearClient = new LinearClient({ accessToken: config.linearAccessToken });
|
|
298
|
+
this.linearSessionId = config.linearSessionId;
|
|
299
|
+
}
|
|
300
|
+
async onStart() {
|
|
301
|
+
await this.sendActivity("thought" /* Thought */, "Starting work...", true);
|
|
302
|
+
}
|
|
303
|
+
async onThinking(block, isSubagent) {
|
|
304
|
+
if (this.isCompleted) return;
|
|
305
|
+
const now = Date.now();
|
|
306
|
+
if (now - this.lastThoughtTime < this.thoughtDebounceMs) return;
|
|
307
|
+
this.lastThoughtTime = now;
|
|
308
|
+
const content = block.text;
|
|
309
|
+
if (content.length < 20) return;
|
|
310
|
+
await this.sendActivity("thought" /* Thought */, content, false);
|
|
311
|
+
}
|
|
312
|
+
/** Tools that should show immediately on use (not wait for result) */
|
|
313
|
+
static SHOW_ON_USE = /* @__PURE__ */ new Set(["Task", "Read", "Grep", "Glob", "WebSearch", "WebFetch"]);
|
|
314
|
+
async onToolUse(block, isSubagent) {
|
|
315
|
+
if (this.isCompleted) return;
|
|
316
|
+
if (!_LinearAgent.SHOW_ON_USE.has(block.name)) return;
|
|
317
|
+
if (block.name === "Task") {
|
|
318
|
+
const subagentType = String(block.input.subagent_type ?? "subagent");
|
|
319
|
+
await this.sendActionActivity("Delegating", subagentType, void 0, false);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const { action, parameter } = this.formatToolAction(block);
|
|
323
|
+
await this.sendActionActivity(action, parameter, void 0, isSubagent);
|
|
324
|
+
}
|
|
325
|
+
async onToolResult(block, result, isSubagent) {
|
|
326
|
+
if (this.isCompleted) return;
|
|
327
|
+
if (_LinearAgent.SHOW_ON_USE.has(block.name)) return;
|
|
328
|
+
const { action, parameter } = this.formatToolAction(block);
|
|
329
|
+
const truncatedResult = result.length > 1e3 ? result.substring(0, 1e3) + "..." : result;
|
|
330
|
+
await this.sendActionActivity(action, parameter, truncatedResult || void 0, isSubagent);
|
|
331
|
+
}
|
|
332
|
+
async onPlanUpdate(steps, isSubagent) {
|
|
333
|
+
if (steps.length === 0) return;
|
|
334
|
+
const linearSteps = steps.map((step) => ({
|
|
335
|
+
content: step.content,
|
|
336
|
+
status: this.mapStatusToLinear(step.status)
|
|
337
|
+
}));
|
|
338
|
+
try {
|
|
339
|
+
await this.linearClient.updateAgentSession(this.linearSessionId, {
|
|
340
|
+
plan: linearSteps
|
|
341
|
+
});
|
|
342
|
+
} catch (error) {
|
|
343
|
+
console.error("Failed to update Linear plan:", error);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async onComplete(message) {
|
|
347
|
+
this.isCompleted = true;
|
|
348
|
+
const text = message.subtype === "success" && message.result ? message.result : "Task completed successfully.";
|
|
349
|
+
await this.sendActivity("response" /* Response */, text, false);
|
|
350
|
+
}
|
|
351
|
+
async onError(error) {
|
|
352
|
+
this.isCompleted = true;
|
|
353
|
+
await this.sendActivity("error" /* Error */, `Error: ${error.message}`, false);
|
|
354
|
+
}
|
|
355
|
+
async onStop() {
|
|
356
|
+
this.isCompleted = true;
|
|
357
|
+
await this.sendActivity("response" /* Response */, "Session stopped", false);
|
|
358
|
+
}
|
|
359
|
+
// ============================================
|
|
360
|
+
// Helper methods
|
|
361
|
+
// ============================================
|
|
362
|
+
async sendActivity(type, body, ephemeral) {
|
|
363
|
+
try {
|
|
364
|
+
await this.linearClient.createAgentActivity({
|
|
365
|
+
agentSessionId: this.linearSessionId,
|
|
366
|
+
content: { type, body },
|
|
367
|
+
ephemeral
|
|
368
|
+
});
|
|
369
|
+
} catch (error) {
|
|
370
|
+
console.error("Failed to send Linear activity:", error);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
async sendActionActivity(action, parameter, result, ephemeral) {
|
|
374
|
+
try {
|
|
375
|
+
const content = {
|
|
376
|
+
type: "action" /* Action */,
|
|
377
|
+
action,
|
|
378
|
+
parameter
|
|
379
|
+
};
|
|
380
|
+
if (result) content.result = result;
|
|
381
|
+
await this.linearClient.createAgentActivity({
|
|
382
|
+
agentSessionId: this.linearSessionId,
|
|
383
|
+
content,
|
|
384
|
+
ephemeral
|
|
385
|
+
});
|
|
386
|
+
} catch (error) {
|
|
387
|
+
console.error("Failed to send Linear activity:", error);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
formatToolAction(block) {
|
|
391
|
+
const { name, input } = block;
|
|
392
|
+
const params = input;
|
|
393
|
+
switch (name) {
|
|
394
|
+
case "Write":
|
|
395
|
+
return { action: "Creating file", parameter: String(params.file_path ?? "") };
|
|
396
|
+
case "Edit":
|
|
397
|
+
return { action: "Editing file", parameter: String(params.file_path ?? "") };
|
|
398
|
+
case "Read":
|
|
399
|
+
return { action: "Reading file", parameter: String(params.file_path ?? "") };
|
|
400
|
+
case "Bash": {
|
|
401
|
+
const cmd = String(params.command ?? "");
|
|
402
|
+
return { action: "Running command", parameter: cmd.length > 80 ? cmd.substring(0, 80) + "..." : cmd };
|
|
403
|
+
}
|
|
404
|
+
case "Grep":
|
|
405
|
+
return { action: "Searching", parameter: String(params.pattern ?? "") };
|
|
406
|
+
case "Glob":
|
|
407
|
+
return { action: "Finding files", parameter: String(params.pattern ?? "") };
|
|
408
|
+
case "Task":
|
|
409
|
+
return { action: "Delegating", parameter: String(params.subagent_type ?? "subagent") };
|
|
410
|
+
case "Skill":
|
|
411
|
+
return { action: "Using skill", parameter: String(params.skill ?? "") };
|
|
412
|
+
default:
|
|
413
|
+
return { action: name, parameter: JSON.stringify(params).substring(0, 100) };
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
mapStatusToLinear(status) {
|
|
417
|
+
switch (status) {
|
|
418
|
+
case "in_progress":
|
|
419
|
+
return "inProgress";
|
|
420
|
+
case "completed":
|
|
421
|
+
return "completed";
|
|
422
|
+
case "canceled":
|
|
423
|
+
return "canceled";
|
|
424
|
+
case "pending":
|
|
425
|
+
default:
|
|
426
|
+
return "pending";
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
var createLinearAgent = (sessionId) => {
|
|
431
|
+
const linearAccessToken = process.env.LINEAR_ACCESS_TOKEN;
|
|
432
|
+
if (!linearAccessToken) {
|
|
433
|
+
throw new Error("Linear access token is missing");
|
|
434
|
+
}
|
|
435
|
+
return new LinearAgent({ linearAccessToken, linearSessionId: sessionId });
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
// src/factory.ts
|
|
439
|
+
function createAgent(platform, sessionId) {
|
|
440
|
+
switch (platform) {
|
|
441
|
+
case "linear" /* Linear */:
|
|
442
|
+
return createLinearAgent(sessionId);
|
|
443
|
+
default:
|
|
444
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/callback.ts
|
|
449
|
+
async function sendSessionUpdate(callbackUrl, callbackSecret, update) {
|
|
450
|
+
try {
|
|
451
|
+
const response = await fetch(callbackUrl, {
|
|
452
|
+
method: "POST",
|
|
453
|
+
headers: {
|
|
454
|
+
"Content-Type": "application/json",
|
|
455
|
+
"Authorization": `Bearer ${callbackSecret}`
|
|
456
|
+
},
|
|
457
|
+
body: JSON.stringify(update)
|
|
458
|
+
});
|
|
459
|
+
if (!response.ok) {
|
|
460
|
+
const body = await response.text().catch(() => "");
|
|
461
|
+
console.error(`[callback] Failed: ${response.status} ${response.statusText} - ${body}`);
|
|
462
|
+
}
|
|
463
|
+
} catch (error) {
|
|
464
|
+
console.error("[callback] Failed to send session update:", error);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
async function reportResult(callbackUrl, externalSessionId, result) {
|
|
468
|
+
const callbackSecret = process.env.CLAUDE_LINEAR_CALLBACK_SECRET;
|
|
469
|
+
if (!callbackUrl || !callbackSecret) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (result.sessionId) {
|
|
473
|
+
await sendSessionUpdate(callbackUrl, callbackSecret, {
|
|
474
|
+
externalSessionId,
|
|
475
|
+
claudeSessionId: result.sessionId,
|
|
476
|
+
state: "running" /* Running */
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
if (result.error) {
|
|
480
|
+
await sendSessionUpdate(callbackUrl, callbackSecret, {
|
|
481
|
+
externalSessionId,
|
|
482
|
+
state: "error" /* Error */,
|
|
483
|
+
error: result.error
|
|
484
|
+
});
|
|
485
|
+
} else if (!result.interrupted) {
|
|
486
|
+
await sendSessionUpdate(callbackUrl, callbackSecret, {
|
|
487
|
+
externalSessionId,
|
|
488
|
+
claudeSessionId: result.sessionId,
|
|
489
|
+
state: "completed" /* Completed */
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/commands/start.ts
|
|
495
|
+
var startCommand = new Command("start").description("Start a new agent session").requiredOption("--platform <platform>", "Platform type (linear)").requiredOption("--session-id <id>", "External session ID (e.g., Linear session ID)").requiredOption("--prompt <prompt>", "Initial prompt for the agent").option("--callback-url <url>", "Server callback URL for session updates").option("--working-dir <dir>", "Working directory", process.cwd()).action(async (options) => {
|
|
496
|
+
const { platform, sessionId, prompt, callbackUrl, workingDir } = options;
|
|
497
|
+
try {
|
|
498
|
+
console.log(`[CLI.start] sessionId=${sessionId} | cwd=${workingDir}`);
|
|
499
|
+
console.log(`[CLI.start] prompt=${prompt.substring(0, 300)}...`);
|
|
500
|
+
const agent = createAgent(platform, sessionId);
|
|
501
|
+
const result = await agent.run({
|
|
502
|
+
prompt,
|
|
503
|
+
cwd: workingDir
|
|
504
|
+
});
|
|
505
|
+
await reportResult(callbackUrl, sessionId, result);
|
|
506
|
+
console.log(JSON.stringify({
|
|
507
|
+
status: result.error ? "error" : result.interrupted ? "interrupted" : "completed",
|
|
508
|
+
claudeSessionId: result.sessionId,
|
|
509
|
+
result: result.result,
|
|
510
|
+
error: result.error
|
|
511
|
+
}));
|
|
512
|
+
process.exit(result.error ? 1 : 0);
|
|
513
|
+
} catch (error) {
|
|
514
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
515
|
+
await reportResult(callbackUrl, sessionId, { sessionId: "", error: errorMessage });
|
|
516
|
+
console.error(JSON.stringify({ status: "error", error: errorMessage }));
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
var start_default = startCommand;
|
|
521
|
+
|
|
522
|
+
// src/commands/stop.ts
|
|
523
|
+
import { Command as Command2 } from "commander";
|
|
524
|
+
var stopCommand = new Command2("stop").description("Stop an agent session gracefully").requiredOption("--platform <platform>", "Platform type (linear)").requiredOption("--session-id <id>", "External session ID (e.g., Linear session ID)").action(async (options) => {
|
|
525
|
+
const { platform, sessionId } = options;
|
|
526
|
+
try {
|
|
527
|
+
const agent = createAgent(platform, sessionId);
|
|
528
|
+
await agent.stop();
|
|
529
|
+
console.log(JSON.stringify({
|
|
530
|
+
status: "stopped",
|
|
531
|
+
sessionId
|
|
532
|
+
}));
|
|
533
|
+
process.exit(0);
|
|
534
|
+
} catch (error) {
|
|
535
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
536
|
+
console.error(JSON.stringify({
|
|
537
|
+
status: "error",
|
|
538
|
+
error: errorMessage
|
|
539
|
+
}));
|
|
540
|
+
process.exit(1);
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// src/commands/resume.ts
|
|
545
|
+
import { Command as Command3 } from "commander";
|
|
546
|
+
var resumeCommand = new Command3("resume").description("Resume an existing agent session").requiredOption("--platform <platform>", "Platform type (linear)").requiredOption("--session-id <id>", "External session ID (e.g., Linear session ID)").requiredOption("--claude-session-id <id>", "Claude SDK session ID to resume").requiredOption("--prompt <prompt>", "New prompt/user input").option("--callback-url <url>", "Server callback URL for session updates").option("--working-dir <dir>", "Working directory", process.cwd()).action(async (options) => {
|
|
547
|
+
const { platform, sessionId, claudeSessionId, prompt, callbackUrl, workingDir } = options;
|
|
548
|
+
const agent = createAgent(platform, sessionId);
|
|
549
|
+
try {
|
|
550
|
+
console.log(`[CLI.resume] sessionId=${sessionId} | claudeSessionId=${claudeSessionId} | cwd=${workingDir}`);
|
|
551
|
+
const result = await agent.run({
|
|
552
|
+
prompt,
|
|
553
|
+
sessionId: claudeSessionId,
|
|
554
|
+
resumeSession: true,
|
|
555
|
+
forkSession: false,
|
|
556
|
+
cwd: workingDir
|
|
557
|
+
});
|
|
558
|
+
await reportResult(callbackUrl, sessionId, result);
|
|
559
|
+
console.log(JSON.stringify({
|
|
560
|
+
status: result.error ? "error" : result.interrupted ? "interrupted" : "completed",
|
|
561
|
+
claudeSessionId: result.sessionId,
|
|
562
|
+
result: result.result,
|
|
563
|
+
error: result.error
|
|
564
|
+
}));
|
|
565
|
+
process.exit(result.error ? 1 : 0);
|
|
566
|
+
} catch (error) {
|
|
567
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
568
|
+
console.error(JSON.stringify({ status: "error", error: errorMessage }));
|
|
569
|
+
process.exit(1);
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// src/commands/init.ts
|
|
574
|
+
import { Command as Command4 } from "commander";
|
|
575
|
+
var initCommand = new Command4("init").description("Install agent plugin (MCP servers, skills)").action(() => {
|
|
576
|
+
installClaudeConfig();
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// src/commands/update.ts
|
|
580
|
+
import { Command as Command5 } from "commander";
|
|
581
|
+
import { execSync, execFileSync } from "child_process";
|
|
582
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
583
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
584
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
585
|
+
function getCurrentPackageName() {
|
|
586
|
+
try {
|
|
587
|
+
const __filename = fileURLToPath2(import.meta.url);
|
|
588
|
+
const __dirname = dirname2(__filename);
|
|
589
|
+
const packageJsonPath = join3(__dirname, "../../package.json");
|
|
590
|
+
const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
|
|
591
|
+
return packageJson.name;
|
|
592
|
+
} catch (error) {
|
|
593
|
+
console.warn("Could not determine package name, defaulting to kartman");
|
|
594
|
+
return "kartman";
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
function buildInstallArgs(cliPackage, cliVersion) {
|
|
598
|
+
if (!["kartman", "kartman-dev"].includes(cliPackage)) {
|
|
599
|
+
throw new Error(`Invalid package name: ${cliPackage}. Must be 'kartman' or 'kartman-dev'`);
|
|
600
|
+
}
|
|
601
|
+
if (cliVersion && !/^[\w.-]+$/.test(cliVersion)) {
|
|
602
|
+
throw new Error(`Invalid version format: ${cliVersion}`);
|
|
603
|
+
}
|
|
604
|
+
if (!cliVersion) {
|
|
605
|
+
return ["pnpm", "add", "-g", `${cliPackage}@latest`];
|
|
606
|
+
}
|
|
607
|
+
if (cliVersion.match(/^\d+\.\d+\.\d+/)) {
|
|
608
|
+
return ["pnpm", "add", "-g", `${cliPackage}@${cliVersion}`];
|
|
609
|
+
}
|
|
610
|
+
if (cliPackage === "kartman-dev") {
|
|
611
|
+
try {
|
|
612
|
+
const currentVersion = execSync("kartman --version", { encoding: "utf-8" }).trim();
|
|
613
|
+
const baseVersion = currentVersion.replace(/-dev\..+$/, "");
|
|
614
|
+
return ["pnpm", "add", "-g", `${cliPackage}@${baseVersion}-dev.${cliVersion}`];
|
|
615
|
+
} catch {
|
|
616
|
+
return ["pnpm", "add", "-g", `${cliPackage}@latest`];
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return ["pnpm", "add", "-g", `${cliPackage}@${cliVersion}`];
|
|
620
|
+
}
|
|
621
|
+
var updateCommand = new Command5().name("update").description("Update kartman CLI to the latest version").option("--version <version>", 'Specific version to install (e.g., "KAR-14" for kartman-dev@0.2.10-dev.KAR-14)').action(async (options) => {
|
|
622
|
+
const { version } = options;
|
|
623
|
+
const packageName = getCurrentPackageName();
|
|
624
|
+
try {
|
|
625
|
+
const installArgs = buildInstallArgs(packageName, version);
|
|
626
|
+
console.log(`Updating CLI: ${installArgs.join(" ")}`);
|
|
627
|
+
const [cmd, ...args] = installArgs;
|
|
628
|
+
if (!cmd) {
|
|
629
|
+
throw new Error("Invalid install command");
|
|
630
|
+
}
|
|
631
|
+
execFileSync(cmd, args, {
|
|
632
|
+
stdio: "inherit",
|
|
633
|
+
encoding: "utf-8"
|
|
634
|
+
});
|
|
635
|
+
const newVersion = execSync("kartman --version", { encoding: "utf-8" }).trim();
|
|
636
|
+
console.log(`Current version: ${newVersion}`);
|
|
637
|
+
console.log("\u2713 CLI updated successfully");
|
|
638
|
+
} catch (error) {
|
|
639
|
+
console.error("Failed to update CLI:", error instanceof Error ? error.message : String(error));
|
|
640
|
+
process.exit(1);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
// src/index.ts
|
|
645
|
+
var program = new Command6();
|
|
646
|
+
program.name("kartman").description("CLI for running Claude agents in Vercel sandboxes").version("0.1.0");
|
|
647
|
+
program.addCommand(initCommand);
|
|
648
|
+
program.addCommand(start_default);
|
|
649
|
+
program.addCommand(stopCommand);
|
|
650
|
+
program.addCommand(resumeCommand);
|
|
651
|
+
program.addCommand(updateCommand);
|
|
652
|
+
program.parse();
|
|
653
|
+
//# sourceMappingURL=index.js.map
|