@artyfacts/claude 1.0.0 → 1.1.1
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/README.md +159 -78
- package/bin/artyfacts-claude.js +38 -0
- package/dist/chunk-365PEWTO.mjs +567 -0
- package/dist/chunk-4RRGLYN6.mjs +567 -0
- package/dist/chunk-QKDOZSBI.mjs +571 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +791 -0
- package/dist/cli.mjs +189 -726
- package/dist/index.d.mts +178 -249
- package/dist/index.d.ts +178 -249
- package/dist/index.js +472 -486
- package/dist/index.mjs +22 -584
- package/package.json +27 -16
- package/src/auth.ts +350 -0
- package/src/cli.ts +344 -0
- package/src/executor.ts +293 -0
- package/src/index.ts +86 -0
- package/src/listener.ts +313 -0
- package/tsconfig.json +20 -0
- package/bin/cli.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
package/dist/cli.js
ADDED
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/cli.ts
|
|
27
|
+
var import_commander = require("commander");
|
|
28
|
+
|
|
29
|
+
// src/auth.ts
|
|
30
|
+
var fs = __toESM(require("fs"));
|
|
31
|
+
var path = __toESM(require("path"));
|
|
32
|
+
var os = __toESM(require("os"));
|
|
33
|
+
var readline = __toESM(require("readline"));
|
|
34
|
+
var CREDENTIALS_DIR = path.join(os.homedir(), ".artyfacts");
|
|
35
|
+
var CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
|
|
36
|
+
var DEFAULT_BASE_URL = "https://artyfacts.dev/api/v1";
|
|
37
|
+
function loadCredentials() {
|
|
38
|
+
try {
|
|
39
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const data = fs.readFileSync(CREDENTIALS_FILE, "utf-8");
|
|
43
|
+
const credentials = JSON.parse(data);
|
|
44
|
+
if (credentials.expiresAt) {
|
|
45
|
+
const expiresAt = new Date(credentials.expiresAt);
|
|
46
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
47
|
+
console.log("\u26A0\uFE0F Credentials have expired");
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return credentials;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error("Failed to load credentials:", error);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function saveCredentials(credentials) {
|
|
58
|
+
try {
|
|
59
|
+
if (!fs.existsSync(CREDENTIALS_DIR)) {
|
|
60
|
+
fs.mkdirSync(CREDENTIALS_DIR, { mode: 448, recursive: true });
|
|
61
|
+
}
|
|
62
|
+
fs.writeFileSync(
|
|
63
|
+
CREDENTIALS_FILE,
|
|
64
|
+
JSON.stringify(credentials, null, 2),
|
|
65
|
+
{ mode: 384 }
|
|
66
|
+
);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
throw new Error(`Failed to save credentials: ${error}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function clearCredentials() {
|
|
72
|
+
try {
|
|
73
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
74
|
+
fs.unlinkSync(CREDENTIALS_FILE);
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error("Failed to clear credentials:", error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function runDeviceAuth(baseUrl = DEFAULT_BASE_URL) {
|
|
81
|
+
console.log("\u{1F510} Starting device authentication...\n");
|
|
82
|
+
const deviceAuth = await requestDeviceCode(baseUrl);
|
|
83
|
+
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
84
|
+
console.log("\u{1F4CB} To authenticate, visit:");
|
|
85
|
+
console.log(` ${deviceAuth.verificationUri}`);
|
|
86
|
+
console.log("");
|
|
87
|
+
console.log("\u{1F511} Enter this code:");
|
|
88
|
+
console.log(` ${deviceAuth.userCode}`);
|
|
89
|
+
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
|
|
90
|
+
console.log("\u23F3 Waiting for authentication...\n");
|
|
91
|
+
const credentials = await pollForToken(
|
|
92
|
+
baseUrl,
|
|
93
|
+
deviceAuth.deviceCode,
|
|
94
|
+
deviceAuth.interval,
|
|
95
|
+
deviceAuth.expiresIn
|
|
96
|
+
);
|
|
97
|
+
saveCredentials(credentials);
|
|
98
|
+
console.log("\u2705 Authentication successful!");
|
|
99
|
+
console.log(` Agent ID: ${credentials.agentId}`);
|
|
100
|
+
if (credentials.agentName) {
|
|
101
|
+
console.log(` Agent Name: ${credentials.agentName}`);
|
|
102
|
+
}
|
|
103
|
+
console.log("");
|
|
104
|
+
return credentials;
|
|
105
|
+
}
|
|
106
|
+
async function requestDeviceCode(baseUrl) {
|
|
107
|
+
const response = await fetch(`${baseUrl}/auth/device`, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: {
|
|
110
|
+
"Content-Type": "application/json"
|
|
111
|
+
},
|
|
112
|
+
body: JSON.stringify({
|
|
113
|
+
client_id: "artyfacts-claude",
|
|
114
|
+
scope: "agent:execute"
|
|
115
|
+
})
|
|
116
|
+
});
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
const error = await response.text();
|
|
119
|
+
throw new Error(`Failed to start device auth: ${error}`);
|
|
120
|
+
}
|
|
121
|
+
const data = await response.json();
|
|
122
|
+
return {
|
|
123
|
+
deviceCode: data.deviceCode || data.device_code || "",
|
|
124
|
+
userCode: data.userCode || data.user_code || "",
|
|
125
|
+
verificationUri: data.verificationUri || data.verification_uri || `https://artyfacts.dev/auth/device`,
|
|
126
|
+
expiresIn: data.expiresIn || data.expires_in || 600,
|
|
127
|
+
interval: data.interval || 5
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async function pollForToken(baseUrl, deviceCode, interval, expiresIn) {
|
|
131
|
+
const startTime = Date.now();
|
|
132
|
+
const timeoutMs = expiresIn * 1e3;
|
|
133
|
+
while (true) {
|
|
134
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
135
|
+
throw new Error("Device authentication timed out");
|
|
136
|
+
}
|
|
137
|
+
await sleep(interval * 1e3);
|
|
138
|
+
const response = await fetch(`${baseUrl}/auth/device/token`, {
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers: {
|
|
141
|
+
"Content-Type": "application/json"
|
|
142
|
+
},
|
|
143
|
+
body: JSON.stringify({
|
|
144
|
+
device_code: deviceCode,
|
|
145
|
+
client_id: "artyfacts-claude"
|
|
146
|
+
})
|
|
147
|
+
});
|
|
148
|
+
if (response.ok) {
|
|
149
|
+
const data = await response.json();
|
|
150
|
+
return {
|
|
151
|
+
apiKey: data.apiKey,
|
|
152
|
+
agentId: data.agentId,
|
|
153
|
+
agentName: data.agentName,
|
|
154
|
+
expiresAt: data.expiresAt
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const errorData = await response.json().catch(() => ({}));
|
|
158
|
+
const errorCode = errorData.error || errorData.code;
|
|
159
|
+
if (errorCode === "authorization_pending") {
|
|
160
|
+
process.stdout.write(".");
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (errorCode === "slow_down") {
|
|
164
|
+
interval = Math.min(interval * 2, 30);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (errorCode === "expired_token") {
|
|
168
|
+
throw new Error("Device code expired. Please try again.");
|
|
169
|
+
}
|
|
170
|
+
if (errorCode === "access_denied") {
|
|
171
|
+
throw new Error("Authorization was denied.");
|
|
172
|
+
}
|
|
173
|
+
throw new Error(`Authentication failed: ${errorData.message || errorCode || response.statusText}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function promptForApiKey() {
|
|
177
|
+
const rl = readline.createInterface({
|
|
178
|
+
input: process.stdin,
|
|
179
|
+
output: process.stdout
|
|
180
|
+
});
|
|
181
|
+
const question = (prompt) => {
|
|
182
|
+
return new Promise((resolve) => {
|
|
183
|
+
rl.question(prompt, resolve);
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
console.log("\u{1F511} Manual Configuration\n");
|
|
187
|
+
console.log("Enter your Artyfacts credentials:\n");
|
|
188
|
+
const apiKey = await question("API Key: ");
|
|
189
|
+
const agentId = await question("Agent ID: ");
|
|
190
|
+
const agentName = await question("Agent Name (optional): ");
|
|
191
|
+
rl.close();
|
|
192
|
+
if (!apiKey || !agentId) {
|
|
193
|
+
throw new Error("API Key and Agent ID are required");
|
|
194
|
+
}
|
|
195
|
+
const credentials = {
|
|
196
|
+
apiKey: apiKey.trim(),
|
|
197
|
+
agentId: agentId.trim(),
|
|
198
|
+
agentName: agentName.trim() || void 0
|
|
199
|
+
};
|
|
200
|
+
saveCredentials(credentials);
|
|
201
|
+
console.log("\n\u2705 Credentials saved!");
|
|
202
|
+
return credentials;
|
|
203
|
+
}
|
|
204
|
+
function sleep(ms) {
|
|
205
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
206
|
+
}
|
|
207
|
+
async function getCredentials(options) {
|
|
208
|
+
if (!options?.forceAuth) {
|
|
209
|
+
const existing = loadCredentials();
|
|
210
|
+
if (existing) {
|
|
211
|
+
return existing;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return runDeviceAuth(options?.baseUrl);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/executor.ts
|
|
218
|
+
var import_child_process = require("child_process");
|
|
219
|
+
var DEFAULT_TIMEOUT = 5 * 60 * 1e3;
|
|
220
|
+
var DEFAULT_SYSTEM_PROMPT = `You are an AI agent working within the Artyfacts task management system.
|
|
221
|
+
|
|
222
|
+
Your job is to complete tasks assigned to you. For each task:
|
|
223
|
+
1. Understand the requirements from the task heading and content
|
|
224
|
+
2. Complete the task to the best of your ability
|
|
225
|
+
3. Provide a clear, actionable output
|
|
226
|
+
|
|
227
|
+
Guidelines:
|
|
228
|
+
- Be thorough but concise
|
|
229
|
+
- If the task requires code, provide working code
|
|
230
|
+
- If the task requires analysis, provide structured findings
|
|
231
|
+
- If the task requires a decision, explain your reasoning
|
|
232
|
+
- If you cannot complete the task, explain why
|
|
233
|
+
|
|
234
|
+
Format your response as follows:
|
|
235
|
+
1. First, provide your main output (the task deliverable)
|
|
236
|
+
2. End with a brief summary line starting with "SUMMARY:"`;
|
|
237
|
+
var ClaudeExecutor = class {
|
|
238
|
+
config;
|
|
239
|
+
constructor(config = {}) {
|
|
240
|
+
this.config = {
|
|
241
|
+
...config,
|
|
242
|
+
timeout: config.timeout || DEFAULT_TIMEOUT,
|
|
243
|
+
claudePath: config.claudePath || "claude"
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Execute a task using Claude Code CLI
|
|
248
|
+
*/
|
|
249
|
+
async execute(task) {
|
|
250
|
+
try {
|
|
251
|
+
const prompt = this.buildTaskPrompt(task);
|
|
252
|
+
const output = await this.runClaude(prompt);
|
|
253
|
+
const { content, summary } = this.parseResponse(output, task.heading);
|
|
254
|
+
return {
|
|
255
|
+
success: true,
|
|
256
|
+
output: content,
|
|
257
|
+
summary
|
|
258
|
+
};
|
|
259
|
+
} catch (error) {
|
|
260
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
261
|
+
return {
|
|
262
|
+
success: false,
|
|
263
|
+
output: "",
|
|
264
|
+
summary: `Failed: ${errorMessage}`,
|
|
265
|
+
error: errorMessage
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Run Claude Code CLI with the given prompt
|
|
271
|
+
*/
|
|
272
|
+
runClaude(prompt) {
|
|
273
|
+
return new Promise((resolve, reject) => {
|
|
274
|
+
const claudePath = this.config.claudePath || "claude";
|
|
275
|
+
const proc = (0, import_child_process.spawn)(claudePath, ["--print"], {
|
|
276
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
277
|
+
timeout: this.config.timeout
|
|
278
|
+
});
|
|
279
|
+
let stdout = "";
|
|
280
|
+
let stderr = "";
|
|
281
|
+
proc.stdout.on("data", (data) => {
|
|
282
|
+
stdout += data.toString();
|
|
283
|
+
});
|
|
284
|
+
proc.stderr.on("data", (data) => {
|
|
285
|
+
stderr += data.toString();
|
|
286
|
+
});
|
|
287
|
+
proc.on("close", (code) => {
|
|
288
|
+
if (code === 0) {
|
|
289
|
+
resolve(stdout.trim());
|
|
290
|
+
} else {
|
|
291
|
+
reject(new Error(stderr || `Claude exited with code ${code}`));
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
proc.on("error", (err) => {
|
|
295
|
+
if (err.code === "ENOENT") {
|
|
296
|
+
reject(new Error(
|
|
297
|
+
"Claude Code CLI not found. Please install it:\n npm install -g @anthropic-ai/claude-code"
|
|
298
|
+
));
|
|
299
|
+
} else {
|
|
300
|
+
reject(err);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
proc.stdin.write(prompt);
|
|
304
|
+
proc.stdin.end();
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Build the task prompt
|
|
309
|
+
*/
|
|
310
|
+
buildTaskPrompt(task) {
|
|
311
|
+
const parts = [];
|
|
312
|
+
const systemPrompt = this.config.systemPromptPrefix ? `${this.config.systemPromptPrefix}
|
|
313
|
+
|
|
314
|
+
${DEFAULT_SYSTEM_PROMPT}` : DEFAULT_SYSTEM_PROMPT;
|
|
315
|
+
parts.push(systemPrompt);
|
|
316
|
+
parts.push("");
|
|
317
|
+
parts.push("---");
|
|
318
|
+
parts.push("");
|
|
319
|
+
parts.push(`# Task: ${task.heading}`);
|
|
320
|
+
parts.push("");
|
|
321
|
+
if (task.artifactTitle) {
|
|
322
|
+
parts.push(`**Artifact:** ${task.artifactTitle}`);
|
|
323
|
+
}
|
|
324
|
+
if (task.priority) {
|
|
325
|
+
const priorityLabels = ["High", "Medium", "Low"];
|
|
326
|
+
parts.push(`**Priority:** ${priorityLabels[task.priority - 1] || "Medium"}`);
|
|
327
|
+
}
|
|
328
|
+
parts.push("");
|
|
329
|
+
parts.push("## Description");
|
|
330
|
+
parts.push(task.content || "No additional description provided.");
|
|
331
|
+
parts.push("");
|
|
332
|
+
if (task.context && Object.keys(task.context).length > 0) {
|
|
333
|
+
parts.push("## Additional Context");
|
|
334
|
+
parts.push("```json");
|
|
335
|
+
parts.push(JSON.stringify(task.context, null, 2));
|
|
336
|
+
parts.push("```");
|
|
337
|
+
parts.push("");
|
|
338
|
+
}
|
|
339
|
+
parts.push("## Instructions");
|
|
340
|
+
parts.push("Complete this task and provide your output below.");
|
|
341
|
+
return parts.join("\n");
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Parse the response to extract output and summary
|
|
345
|
+
*/
|
|
346
|
+
parseResponse(fullOutput, taskHeading) {
|
|
347
|
+
const summaryMatch = fullOutput.match(/SUMMARY:\s*(.+?)(?:\n|$)/i);
|
|
348
|
+
if (summaryMatch) {
|
|
349
|
+
const summary2 = summaryMatch[1].trim();
|
|
350
|
+
const content = fullOutput.replace(/SUMMARY:\s*.+?(?:\n|$)/i, "").trim();
|
|
351
|
+
return { content, summary: summary2 };
|
|
352
|
+
}
|
|
353
|
+
const lines = fullOutput.split("\n").filter((l) => l.trim());
|
|
354
|
+
const firstLine = lines[0] || "";
|
|
355
|
+
const summary = firstLine.length > 100 ? `${firstLine.substring(0, 97)}...` : firstLine || `Completed: ${taskHeading}`;
|
|
356
|
+
return { content: fullOutput, summary };
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Test that Claude Code CLI is available and working
|
|
360
|
+
*/
|
|
361
|
+
async testConnection() {
|
|
362
|
+
try {
|
|
363
|
+
const output = await this.runClaude('Say "connected" and nothing else.');
|
|
364
|
+
return output.toLowerCase().includes("connected");
|
|
365
|
+
} catch {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Check if Claude Code CLI is installed
|
|
371
|
+
*/
|
|
372
|
+
async isInstalled() {
|
|
373
|
+
return new Promise((resolve) => {
|
|
374
|
+
const proc = (0, import_child_process.spawn)(this.config.claudePath || "claude", ["--version"], {
|
|
375
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
376
|
+
});
|
|
377
|
+
proc.on("close", (code) => {
|
|
378
|
+
resolve(code === 0);
|
|
379
|
+
});
|
|
380
|
+
proc.on("error", () => {
|
|
381
|
+
resolve(false);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
function createExecutor(config) {
|
|
387
|
+
return new ClaudeExecutor(config);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/listener.ts
|
|
391
|
+
var import_eventsource = __toESM(require("eventsource"));
|
|
392
|
+
var DEFAULT_BASE_URL2 = "https://artyfacts.dev/api/v1";
|
|
393
|
+
var EVENT_TYPES = [
|
|
394
|
+
"connected",
|
|
395
|
+
"heartbeat",
|
|
396
|
+
"task_assigned",
|
|
397
|
+
"task_unblocked",
|
|
398
|
+
"blocker_resolved",
|
|
399
|
+
"notification"
|
|
400
|
+
];
|
|
401
|
+
var ArtyfactsListener = class {
|
|
402
|
+
config;
|
|
403
|
+
eventSource = null;
|
|
404
|
+
callbacks = /* @__PURE__ */ new Map();
|
|
405
|
+
allCallbacks = /* @__PURE__ */ new Set();
|
|
406
|
+
state = "disconnected";
|
|
407
|
+
reconnectAttempts = 0;
|
|
408
|
+
maxReconnectAttempts = 10;
|
|
409
|
+
reconnectDelay = 1e3;
|
|
410
|
+
constructor(config) {
|
|
411
|
+
if (!config.apiKey) {
|
|
412
|
+
throw new Error("API key is required");
|
|
413
|
+
}
|
|
414
|
+
if (!config.agentId) {
|
|
415
|
+
throw new Error("Agent ID is required");
|
|
416
|
+
}
|
|
417
|
+
this.config = {
|
|
418
|
+
...config,
|
|
419
|
+
baseUrl: config.baseUrl || DEFAULT_BASE_URL2
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Get current connection state
|
|
424
|
+
*/
|
|
425
|
+
get connectionState() {
|
|
426
|
+
return this.state;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Check if connected
|
|
430
|
+
*/
|
|
431
|
+
get isConnected() {
|
|
432
|
+
return this.state === "connected";
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Subscribe to all events
|
|
436
|
+
*/
|
|
437
|
+
subscribe(callback) {
|
|
438
|
+
this.allCallbacks.add(callback);
|
|
439
|
+
return () => {
|
|
440
|
+
this.allCallbacks.delete(callback);
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Subscribe to a specific event type
|
|
445
|
+
*/
|
|
446
|
+
on(type, callback) {
|
|
447
|
+
if (!this.callbacks.has(type)) {
|
|
448
|
+
this.callbacks.set(type, /* @__PURE__ */ new Set());
|
|
449
|
+
}
|
|
450
|
+
this.callbacks.get(type).add(callback);
|
|
451
|
+
return () => {
|
|
452
|
+
const typeCallbacks = this.callbacks.get(type);
|
|
453
|
+
if (typeCallbacks) {
|
|
454
|
+
typeCallbacks.delete(callback);
|
|
455
|
+
if (typeCallbacks.size === 0) {
|
|
456
|
+
this.callbacks.delete(type);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Connect to the SSE stream
|
|
463
|
+
*/
|
|
464
|
+
connect() {
|
|
465
|
+
if (this.eventSource) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
this.setState("connecting");
|
|
469
|
+
const url = new URL(`${this.config.baseUrl}/events/stream`);
|
|
470
|
+
url.searchParams.set("apiKey", this.config.apiKey);
|
|
471
|
+
url.searchParams.set("agentId", this.config.agentId);
|
|
472
|
+
this.eventSource = new import_eventsource.default(url.toString(), {
|
|
473
|
+
headers: {
|
|
474
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
this.eventSource.onopen = () => {
|
|
478
|
+
this.reconnectAttempts = 0;
|
|
479
|
+
this.reconnectDelay = 1e3;
|
|
480
|
+
this.setState("connected");
|
|
481
|
+
};
|
|
482
|
+
this.eventSource.onmessage = (event) => {
|
|
483
|
+
this.handleMessage(event);
|
|
484
|
+
};
|
|
485
|
+
this.eventSource.onerror = (event) => {
|
|
486
|
+
this.handleError(event);
|
|
487
|
+
};
|
|
488
|
+
for (const eventType of EVENT_TYPES) {
|
|
489
|
+
this.eventSource.addEventListener(eventType, (event) => {
|
|
490
|
+
this.handleMessage(event, eventType);
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Disconnect from the SSE stream
|
|
496
|
+
*/
|
|
497
|
+
disconnect() {
|
|
498
|
+
if (this.eventSource) {
|
|
499
|
+
this.eventSource.close();
|
|
500
|
+
this.eventSource = null;
|
|
501
|
+
}
|
|
502
|
+
this.setState("disconnected");
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Reconnect to the SSE stream
|
|
506
|
+
*/
|
|
507
|
+
reconnect() {
|
|
508
|
+
this.disconnect();
|
|
509
|
+
this.connect();
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Handle incoming SSE message
|
|
513
|
+
*/
|
|
514
|
+
handleMessage(event, eventType) {
|
|
515
|
+
try {
|
|
516
|
+
const data = JSON.parse(event.data);
|
|
517
|
+
const artyfactsEvent = {
|
|
518
|
+
type: eventType || data.type || "unknown",
|
|
519
|
+
timestamp: data.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
520
|
+
data: data.data || data
|
|
521
|
+
};
|
|
522
|
+
const typeCallbacks = this.callbacks.get(artyfactsEvent.type);
|
|
523
|
+
if (typeCallbacks) {
|
|
524
|
+
for (const callback of typeCallbacks) {
|
|
525
|
+
this.safeCallCallback(callback, artyfactsEvent);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
for (const callback of this.allCallbacks) {
|
|
529
|
+
this.safeCallCallback(callback, artyfactsEvent);
|
|
530
|
+
}
|
|
531
|
+
} catch (err) {
|
|
532
|
+
console.error("[Listener] Failed to parse SSE message:", event.data, err);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Safely call a callback, handling async and errors
|
|
537
|
+
*/
|
|
538
|
+
async safeCallCallback(callback, event) {
|
|
539
|
+
try {
|
|
540
|
+
await callback(event);
|
|
541
|
+
} catch (err) {
|
|
542
|
+
console.error(`[Listener] Error in event callback for '${event.type}':`, err);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Handle SSE error
|
|
547
|
+
*/
|
|
548
|
+
handleError(event) {
|
|
549
|
+
if (this.eventSource?.readyState === import_eventsource.default.CONNECTING) {
|
|
550
|
+
this.setState("reconnecting");
|
|
551
|
+
} else if (this.eventSource?.readyState === import_eventsource.default.CLOSED) {
|
|
552
|
+
this.setState("disconnected");
|
|
553
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
554
|
+
this.reconnectAttempts++;
|
|
555
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, 3e4);
|
|
556
|
+
console.log(
|
|
557
|
+
`[Listener] Connection lost, reconnecting in ${this.reconnectDelay / 1e3}s (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
|
|
558
|
+
);
|
|
559
|
+
setTimeout(() => {
|
|
560
|
+
if (this.state === "disconnected") {
|
|
561
|
+
this.connect();
|
|
562
|
+
}
|
|
563
|
+
}, this.reconnectDelay);
|
|
564
|
+
} else {
|
|
565
|
+
const error = new Error("Max reconnection attempts reached");
|
|
566
|
+
this.config.onError?.(error);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Update connection state
|
|
572
|
+
*/
|
|
573
|
+
setState(state) {
|
|
574
|
+
if (this.state !== state) {
|
|
575
|
+
this.state = state;
|
|
576
|
+
this.config.onStateChange?.(state);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
function createListener(config) {
|
|
581
|
+
return new ArtyfactsListener(config);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// src/cli.ts
|
|
585
|
+
var VERSION = "0.1.0";
|
|
586
|
+
var DEFAULT_BASE_URL3 = "https://artyfacts.dev/api/v1";
|
|
587
|
+
var program = new import_commander.Command();
|
|
588
|
+
program.name("artyfacts-claude").description("Claude adapter for Artyfacts - Execute tasks using Claude API").version(VERSION);
|
|
589
|
+
program.command("run", { isDefault: true }).description("Start listening for and executing tasks").option("--base-url <url>", "Artyfacts API base URL", DEFAULT_BASE_URL3).option("--dry-run", "Print tasks but do not execute", false).action(async (options) => {
|
|
590
|
+
await runAgent(options);
|
|
591
|
+
});
|
|
592
|
+
program.command("login").description("Authenticate with Artyfacts").option("--manual", "Enter credentials manually instead of device auth").option("--base-url <url>", "Artyfacts API base URL", DEFAULT_BASE_URL3).action(async (options) => {
|
|
593
|
+
try {
|
|
594
|
+
if (options.manual) {
|
|
595
|
+
await promptForApiKey();
|
|
596
|
+
} else {
|
|
597
|
+
await getCredentials({ baseUrl: options.baseUrl, forceAuth: true });
|
|
598
|
+
}
|
|
599
|
+
} catch (error) {
|
|
600
|
+
console.error("\u274C Authentication failed:", error instanceof Error ? error.message : error);
|
|
601
|
+
process.exit(1);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
program.command("logout").description("Clear stored credentials").action(() => {
|
|
605
|
+
clearCredentials();
|
|
606
|
+
console.log("\u2705 Credentials cleared");
|
|
607
|
+
});
|
|
608
|
+
program.command("status").description("Check authentication and connection status").action(async () => {
|
|
609
|
+
const credentials = loadCredentials();
|
|
610
|
+
if (!credentials) {
|
|
611
|
+
console.log("\u274C Not authenticated");
|
|
612
|
+
console.log(" Run: npx @artyfacts/claude login");
|
|
613
|
+
process.exit(1);
|
|
614
|
+
}
|
|
615
|
+
console.log("\u2705 Authenticated");
|
|
616
|
+
console.log(` Agent ID: ${credentials.agentId}`);
|
|
617
|
+
if (credentials.agentName) {
|
|
618
|
+
console.log(` Agent Name: ${credentials.agentName}`);
|
|
619
|
+
}
|
|
620
|
+
const executor = createExecutor();
|
|
621
|
+
const installed = await executor.isInstalled();
|
|
622
|
+
if (installed) {
|
|
623
|
+
console.log("\u2705 Claude Code CLI installed");
|
|
624
|
+
try {
|
|
625
|
+
const connected = await executor.testConnection();
|
|
626
|
+
if (connected) {
|
|
627
|
+
console.log("\u2705 Claude Code authenticated and working");
|
|
628
|
+
} else {
|
|
629
|
+
console.log("\u26A0\uFE0F Claude Code not authenticated");
|
|
630
|
+
console.log(" Run: claude login");
|
|
631
|
+
}
|
|
632
|
+
} catch (error) {
|
|
633
|
+
console.log("\u26A0\uFE0F Claude Code error:", error instanceof Error ? error.message : error);
|
|
634
|
+
}
|
|
635
|
+
} else {
|
|
636
|
+
console.log("\u274C Claude Code CLI not installed");
|
|
637
|
+
console.log(" Install: npm install -g @anthropic-ai/claude-code");
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
async function runAgent(options) {
|
|
641
|
+
console.log("\u{1F517} Connecting to Artyfacts...");
|
|
642
|
+
let credentials;
|
|
643
|
+
try {
|
|
644
|
+
credentials = await getCredentials({ baseUrl: options.baseUrl });
|
|
645
|
+
} catch (error) {
|
|
646
|
+
console.error("\u274C Authentication failed:", error instanceof Error ? error.message : error);
|
|
647
|
+
process.exit(1);
|
|
648
|
+
}
|
|
649
|
+
let executor = null;
|
|
650
|
+
if (!options.dryRun) {
|
|
651
|
+
executor = createExecutor();
|
|
652
|
+
const installed = await executor.isInstalled();
|
|
653
|
+
if (!installed) {
|
|
654
|
+
console.error("\u274C Claude Code CLI not found");
|
|
655
|
+
console.error(" Install it with: npm install -g @anthropic-ai/claude-code");
|
|
656
|
+
console.error(" Then authenticate with: claude login");
|
|
657
|
+
process.exit(1);
|
|
658
|
+
}
|
|
659
|
+
console.log("\u{1F504} Testing Claude Code connection...");
|
|
660
|
+
const connected = await executor.testConnection();
|
|
661
|
+
if (!connected) {
|
|
662
|
+
console.error("\u274C Claude Code not authenticated");
|
|
663
|
+
console.error(" Run: claude login");
|
|
664
|
+
process.exit(1);
|
|
665
|
+
}
|
|
666
|
+
console.log("\u2705 Claude Code connected");
|
|
667
|
+
}
|
|
668
|
+
const listener = createListener({
|
|
669
|
+
apiKey: credentials.apiKey,
|
|
670
|
+
agentId: credentials.agentId,
|
|
671
|
+
baseUrl: options.baseUrl,
|
|
672
|
+
onStateChange: (state) => {
|
|
673
|
+
switch (state) {
|
|
674
|
+
case "connecting":
|
|
675
|
+
console.log("\u{1F504} Connecting...");
|
|
676
|
+
break;
|
|
677
|
+
case "connected":
|
|
678
|
+
console.log(`\u2705 Connected as ${credentials.agentId}`);
|
|
679
|
+
console.log("\u{1F442} Listening for tasks...\n");
|
|
680
|
+
break;
|
|
681
|
+
case "reconnecting":
|
|
682
|
+
console.log("\u{1F504} Reconnecting...");
|
|
683
|
+
break;
|
|
684
|
+
case "disconnected":
|
|
685
|
+
console.log("\u26A0\uFE0F Disconnected");
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
onError: (error) => {
|
|
690
|
+
console.error("\u274C Connection error:", error.message);
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
const activeTasks = /* @__PURE__ */ new Set();
|
|
694
|
+
listener.on("task_assigned", async (event) => {
|
|
695
|
+
const task = event.data;
|
|
696
|
+
if (activeTasks.has(task.taskId)) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
activeTasks.add(task.taskId);
|
|
700
|
+
console.log(`
|
|
701
|
+
[Task received] ${task.heading}`);
|
|
702
|
+
if (options.dryRun) {
|
|
703
|
+
console.log(" \u{1F4CB} Dry run - not executing");
|
|
704
|
+
console.log(` \u{1F4C4} Artifact: ${task.artifactTitle || task.artifactId}`);
|
|
705
|
+
console.log(` \u{1F4DD} Content: ${task.content.substring(0, 100)}...`);
|
|
706
|
+
activeTasks.delete(task.taskId);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
console.log(" \u2192 Executing with Claude...");
|
|
710
|
+
try {
|
|
711
|
+
const taskContext = {
|
|
712
|
+
taskId: task.taskId,
|
|
713
|
+
heading: task.heading,
|
|
714
|
+
content: task.content,
|
|
715
|
+
artifactId: task.artifactId,
|
|
716
|
+
artifactTitle: task.artifactTitle,
|
|
717
|
+
priority: task.priority
|
|
718
|
+
};
|
|
719
|
+
const result = await executor.execute(taskContext);
|
|
720
|
+
if (result.success) {
|
|
721
|
+
await completeTask({
|
|
722
|
+
baseUrl: options.baseUrl,
|
|
723
|
+
apiKey: credentials.apiKey,
|
|
724
|
+
taskId: task.taskId,
|
|
725
|
+
output: result.output,
|
|
726
|
+
summary: result.summary
|
|
727
|
+
});
|
|
728
|
+
console.log(` \u2192 \u2705 Completed! ${result.summary}`);
|
|
729
|
+
} else {
|
|
730
|
+
console.log(` \u2192 \u274C Failed: ${result.error}`);
|
|
731
|
+
await blockTask({
|
|
732
|
+
baseUrl: options.baseUrl,
|
|
733
|
+
apiKey: credentials.apiKey,
|
|
734
|
+
taskId: task.taskId,
|
|
735
|
+
reason: result.error || "Execution failed"
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
} catch (error) {
|
|
739
|
+
console.error(` \u2192 \u274C Error:`, error instanceof Error ? error.message : error);
|
|
740
|
+
} finally {
|
|
741
|
+
activeTasks.delete(task.taskId);
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
listener.connect();
|
|
745
|
+
const shutdown = () => {
|
|
746
|
+
console.log("\n\u{1F44B} Disconnecting...");
|
|
747
|
+
listener.disconnect();
|
|
748
|
+
process.exit(0);
|
|
749
|
+
};
|
|
750
|
+
process.on("SIGINT", shutdown);
|
|
751
|
+
process.on("SIGTERM", shutdown);
|
|
752
|
+
await new Promise(() => {
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
async function completeTask(options) {
|
|
756
|
+
const response = await fetch(`${options.baseUrl}/tasks/${options.taskId}/complete`, {
|
|
757
|
+
method: "POST",
|
|
758
|
+
headers: {
|
|
759
|
+
"Authorization": `Bearer ${options.apiKey}`,
|
|
760
|
+
"Content-Type": "application/json"
|
|
761
|
+
},
|
|
762
|
+
body: JSON.stringify({
|
|
763
|
+
task_status: "done",
|
|
764
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
765
|
+
summary: options.summary,
|
|
766
|
+
output: options.output
|
|
767
|
+
})
|
|
768
|
+
});
|
|
769
|
+
if (!response.ok) {
|
|
770
|
+
const error = await response.text();
|
|
771
|
+
throw new Error(`Failed to complete task: ${error}`);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
async function blockTask(options) {
|
|
775
|
+
const response = await fetch(`${options.baseUrl}/tasks/${options.taskId}/block`, {
|
|
776
|
+
method: "POST",
|
|
777
|
+
headers: {
|
|
778
|
+
"Authorization": `Bearer ${options.apiKey}`,
|
|
779
|
+
"Content-Type": "application/json"
|
|
780
|
+
},
|
|
781
|
+
body: JSON.stringify({
|
|
782
|
+
task_status: "blocked",
|
|
783
|
+
blocked_reason: options.reason
|
|
784
|
+
})
|
|
785
|
+
});
|
|
786
|
+
if (!response.ok) {
|
|
787
|
+
const error = await response.text();
|
|
788
|
+
throw new Error(`Failed to block task: ${error}`);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
program.parse();
|