@artyfacts/claude 1.2.2 → 1.2.6

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.
@@ -0,0 +1,755 @@
1
+ // src/auth.ts
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import * as os from "os";
5
+ import * as readline from "readline";
6
+ var CREDENTIALS_DIR = path.join(os.homedir(), ".artyfacts");
7
+ var CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
8
+ var DEFAULT_BASE_URL = "https://artyfacts.dev/api/v1";
9
+ function loadCredentials() {
10
+ try {
11
+ if (!fs.existsSync(CREDENTIALS_FILE)) {
12
+ return null;
13
+ }
14
+ const data = fs.readFileSync(CREDENTIALS_FILE, "utf-8");
15
+ const credentials = JSON.parse(data);
16
+ if (credentials.expiresAt) {
17
+ const expiresAt = new Date(credentials.expiresAt);
18
+ if (expiresAt < /* @__PURE__ */ new Date()) {
19
+ console.log("\u26A0\uFE0F Credentials have expired");
20
+ return null;
21
+ }
22
+ }
23
+ return credentials;
24
+ } catch (error) {
25
+ console.error("Failed to load credentials:", error);
26
+ return null;
27
+ }
28
+ }
29
+ function saveCredentials(credentials) {
30
+ try {
31
+ if (!fs.existsSync(CREDENTIALS_DIR)) {
32
+ fs.mkdirSync(CREDENTIALS_DIR, { mode: 448, recursive: true });
33
+ }
34
+ fs.writeFileSync(
35
+ CREDENTIALS_FILE,
36
+ JSON.stringify(credentials, null, 2),
37
+ { mode: 384 }
38
+ );
39
+ } catch (error) {
40
+ throw new Error(`Failed to save credentials: ${error}`);
41
+ }
42
+ }
43
+ function clearCredentials() {
44
+ try {
45
+ if (fs.existsSync(CREDENTIALS_FILE)) {
46
+ fs.unlinkSync(CREDENTIALS_FILE);
47
+ }
48
+ } catch (error) {
49
+ console.error("Failed to clear credentials:", error);
50
+ }
51
+ }
52
+ async function runDeviceAuth(baseUrl = DEFAULT_BASE_URL) {
53
+ console.log("\u{1F510} Starting device authentication...\n");
54
+ const deviceAuth = await requestDeviceCode(baseUrl);
55
+ 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");
56
+ console.log("\u{1F4CB} To authenticate, visit:");
57
+ console.log(` ${deviceAuth.verificationUri}`);
58
+ console.log("");
59
+ console.log("\u{1F511} Enter this code:");
60
+ console.log(` ${deviceAuth.userCode}`);
61
+ 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");
62
+ console.log("\u23F3 Waiting for authentication...\n");
63
+ const credentials = await pollForToken(
64
+ baseUrl,
65
+ deviceAuth.deviceCode,
66
+ deviceAuth.interval,
67
+ deviceAuth.expiresIn
68
+ );
69
+ saveCredentials(credentials);
70
+ console.log("\u2705 Authentication successful!");
71
+ console.log(` Agent ID: ${credentials.agentId}`);
72
+ if (credentials.agentName) {
73
+ console.log(` Agent Name: ${credentials.agentName}`);
74
+ }
75
+ console.log("");
76
+ return credentials;
77
+ }
78
+ async function requestDeviceCode(baseUrl) {
79
+ const response = await fetch(`${baseUrl}/auth/device`, {
80
+ method: "POST",
81
+ headers: {
82
+ "Content-Type": "application/json"
83
+ },
84
+ body: JSON.stringify({
85
+ client_id: "artyfacts-claude",
86
+ scope: "agent:execute"
87
+ })
88
+ });
89
+ if (!response.ok) {
90
+ const error = await response.text();
91
+ throw new Error(`Failed to start device auth: ${error}`);
92
+ }
93
+ const data = await response.json();
94
+ return {
95
+ deviceCode: data.deviceCode || data.device_code || "",
96
+ userCode: data.userCode || data.user_code || "",
97
+ verificationUri: data.verificationUri || data.verification_uri || `https://artyfacts.dev/auth/device`,
98
+ expiresIn: data.expiresIn || data.expires_in || 600,
99
+ interval: data.interval || 5
100
+ };
101
+ }
102
+ async function pollForToken(baseUrl, deviceCode, interval, expiresIn) {
103
+ const startTime = Date.now();
104
+ const timeoutMs = expiresIn * 1e3;
105
+ while (true) {
106
+ if (Date.now() - startTime > timeoutMs) {
107
+ throw new Error("Device authentication timed out");
108
+ }
109
+ await sleep(interval * 1e3);
110
+ const response = await fetch(`${baseUrl}/auth/device/token`, {
111
+ method: "POST",
112
+ headers: {
113
+ "Content-Type": "application/json"
114
+ },
115
+ body: JSON.stringify({
116
+ device_code: deviceCode,
117
+ client_id: "artyfacts-claude"
118
+ })
119
+ });
120
+ if (response.ok) {
121
+ const data = await response.json();
122
+ return {
123
+ apiKey: data.apiKey,
124
+ agentId: data.agentId,
125
+ agentName: data.agentName,
126
+ expiresAt: data.expiresAt
127
+ };
128
+ }
129
+ const errorData = await response.json().catch(() => ({}));
130
+ const errorCode = errorData.error || errorData.code;
131
+ if (errorCode === "authorization_pending") {
132
+ process.stdout.write(".");
133
+ continue;
134
+ }
135
+ if (errorCode === "slow_down") {
136
+ interval = Math.min(interval * 2, 30);
137
+ continue;
138
+ }
139
+ if (errorCode === "expired_token") {
140
+ throw new Error("Device code expired. Please try again.");
141
+ }
142
+ if (errorCode === "access_denied") {
143
+ throw new Error("Authorization was denied.");
144
+ }
145
+ throw new Error(`Authentication failed: ${errorData.message || errorCode || response.statusText}`);
146
+ }
147
+ }
148
+ async function promptForApiKey() {
149
+ const rl = readline.createInterface({
150
+ input: process.stdin,
151
+ output: process.stdout
152
+ });
153
+ const question = (prompt) => {
154
+ return new Promise((resolve) => {
155
+ rl.question(prompt, resolve);
156
+ });
157
+ };
158
+ console.log("\u{1F511} Manual Configuration\n");
159
+ console.log("Enter your Artyfacts credentials:\n");
160
+ const apiKey = await question("API Key: ");
161
+ const agentId = await question("Agent ID: ");
162
+ const agentName = await question("Agent Name (optional): ");
163
+ rl.close();
164
+ if (!apiKey || !agentId) {
165
+ throw new Error("API Key and Agent ID are required");
166
+ }
167
+ const credentials = {
168
+ apiKey: apiKey.trim(),
169
+ agentId: agentId.trim(),
170
+ agentName: agentName.trim() || void 0
171
+ };
172
+ saveCredentials(credentials);
173
+ console.log("\n\u2705 Credentials saved!");
174
+ return credentials;
175
+ }
176
+ function sleep(ms) {
177
+ return new Promise((resolve) => setTimeout(resolve, ms));
178
+ }
179
+ async function getCredentials(options) {
180
+ if (!options?.forceAuth) {
181
+ const existing = loadCredentials();
182
+ if (existing) {
183
+ return existing;
184
+ }
185
+ }
186
+ return runDeviceAuth(options?.baseUrl);
187
+ }
188
+
189
+ // src/context.ts
190
+ var ContextFetcher = class {
191
+ config;
192
+ constructor(config) {
193
+ this.config = config;
194
+ }
195
+ /**
196
+ * Fetch full context for a task
197
+ */
198
+ async fetchTaskContext(taskId) {
199
+ const response = await fetch(
200
+ `${this.config.baseUrl}/tasks/${taskId}/context`,
201
+ {
202
+ headers: {
203
+ "Authorization": `Bearer ${this.config.apiKey}`,
204
+ "Accept": "application/json"
205
+ }
206
+ }
207
+ );
208
+ if (!response.ok) {
209
+ const errorText = await response.text().catch(() => "Unknown error");
210
+ throw new Error(`Failed to fetch task context: ${response.status} - ${errorText}`);
211
+ }
212
+ const data = await response.json();
213
+ return data;
214
+ }
215
+ };
216
+ function buildPromptWithContext(context) {
217
+ const parts = [];
218
+ parts.push(`You are an AI agent working within the Artyfacts task management system.
219
+
220
+ Your job is to complete the assigned task. You have full context about the organization, project, and related work.
221
+
222
+ Guidelines:
223
+ - Be thorough but concise
224
+ - If the task requires code, provide working code
225
+ - If the task requires analysis, provide structured findings
226
+ - If the task requires a decision, explain your reasoning
227
+ - If you cannot complete the task, explain why
228
+
229
+ Format your response as follows:
230
+ 1. First, provide your main output (the task deliverable)
231
+ 2. End with a brief summary line starting with "SUMMARY:"`);
232
+ parts.push("");
233
+ parts.push("---");
234
+ parts.push("");
235
+ parts.push("## Organization Context");
236
+ parts.push(`**${context.organization.name}**`);
237
+ if (context.organization.context) {
238
+ parts.push("");
239
+ parts.push(formatOrgContext(context.organization.context));
240
+ }
241
+ parts.push("");
242
+ if (context.project) {
243
+ parts.push(`## Project: ${context.project.name}`);
244
+ if (context.project.description) {
245
+ parts.push(context.project.description);
246
+ }
247
+ parts.push("");
248
+ }
249
+ parts.push(`## Artifact: ${context.artifact.title}`);
250
+ if (context.artifact.summary) {
251
+ parts.push(context.artifact.summary);
252
+ }
253
+ if (context.artifact.description) {
254
+ parts.push("");
255
+ parts.push(context.artifact.description);
256
+ }
257
+ parts.push("");
258
+ const relatedSections = context.artifact.sections.filter(
259
+ (s) => s.id !== context.task.id
260
+ );
261
+ if (relatedSections.length > 0) {
262
+ parts.push("### Related Sections:");
263
+ for (const section of relatedSections) {
264
+ const preview = section.content ? section.content.substring(0, 200) + (section.content.length > 200 ? "..." : "") : "No content";
265
+ const statusBadge = section.task_status ? ` [${section.task_status}]` : "";
266
+ parts.push(`- **${section.heading}**${statusBadge}: ${preview}`);
267
+ }
268
+ parts.push("");
269
+ }
270
+ parts.push("---");
271
+ parts.push("");
272
+ parts.push(`## Your Task: ${context.task.heading}`);
273
+ if (context.task.priority) {
274
+ const priorityLabels = ["\u{1F534} High", "\u{1F7E1} Medium", "\u{1F7E2} Low"];
275
+ parts.push(`**Priority:** ${priorityLabels[context.task.priority - 1] || "Medium"}`);
276
+ }
277
+ parts.push("");
278
+ parts.push("### Description");
279
+ parts.push(context.task.content || "No additional description provided.");
280
+ parts.push("");
281
+ if (context.task.expected_output) {
282
+ parts.push("### Expected Output");
283
+ if (context.task.expected_output.format) {
284
+ parts.push(`**Format:** ${context.task.expected_output.format}`);
285
+ }
286
+ if (context.task.expected_output.requirements && context.task.expected_output.requirements.length > 0) {
287
+ parts.push("**Requirements:**");
288
+ for (const req of context.task.expected_output.requirements) {
289
+ parts.push(`- ${req}`);
290
+ }
291
+ }
292
+ parts.push("");
293
+ }
294
+ parts.push("---");
295
+ parts.push("");
296
+ parts.push("Complete this task and provide your output below.");
297
+ return parts.join("\n");
298
+ }
299
+ function formatOrgContext(context) {
300
+ const trimmed = context.trim();
301
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
302
+ try {
303
+ const parsed = JSON.parse(trimmed);
304
+ return formatContextObject(parsed);
305
+ } catch {
306
+ return context;
307
+ }
308
+ }
309
+ return context;
310
+ }
311
+ function formatContextObject(obj, indent = "") {
312
+ if (typeof obj !== "object" || obj === null) {
313
+ return String(obj);
314
+ }
315
+ if (Array.isArray(obj)) {
316
+ return obj.map((item) => `${indent}- ${formatContextObject(item, indent + " ")}`).join("\n");
317
+ }
318
+ const lines = [];
319
+ for (const [key, value] of Object.entries(obj)) {
320
+ const label = key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
321
+ if (typeof value === "object" && value !== null) {
322
+ lines.push(`${indent}**${label}:**`);
323
+ lines.push(formatContextObject(value, indent + " "));
324
+ } else {
325
+ lines.push(`${indent}- **${label}:** ${value}`);
326
+ }
327
+ }
328
+ return lines.join("\n");
329
+ }
330
+ function createContextFetcher(config) {
331
+ return new ContextFetcher(config);
332
+ }
333
+
334
+ // src/executor.ts
335
+ import { spawn } from "child_process";
336
+ var DEFAULT_TIMEOUT = 5 * 60 * 1e3;
337
+ var DEFAULT_SYSTEM_PROMPT = `You are an AI agent working within the Artyfacts task management system.
338
+
339
+ Your job is to complete tasks assigned to you. For each task:
340
+ 1. Understand the requirements from the task heading and content
341
+ 2. Complete the task to the best of your ability
342
+ 3. Provide a clear, actionable output
343
+
344
+ Guidelines:
345
+ - Be thorough but concise
346
+ - If the task requires code, provide working code
347
+ - If the task requires analysis, provide structured findings
348
+ - If the task requires a decision, explain your reasoning
349
+ - If you cannot complete the task, explain why
350
+
351
+ Format your response as follows:
352
+ 1. First, provide your main output (the task deliverable)
353
+ 2. End with a brief summary line starting with "SUMMARY:"`;
354
+ var ClaudeExecutor = class {
355
+ config;
356
+ contextFetcher = null;
357
+ constructor(config = {}) {
358
+ this.config = {
359
+ ...config,
360
+ timeout: config.timeout || DEFAULT_TIMEOUT,
361
+ claudePath: config.claudePath || "claude"
362
+ };
363
+ if (config.baseUrl && config.apiKey) {
364
+ this.contextFetcher = createContextFetcher({
365
+ baseUrl: config.baseUrl,
366
+ apiKey: config.apiKey
367
+ });
368
+ }
369
+ }
370
+ /**
371
+ * Execute a task using Claude Code CLI
372
+ *
373
+ * If full context is available (baseUrl + apiKey configured), fetches
374
+ * organization, project, artifact, and related sections for a rich prompt.
375
+ */
376
+ async execute(task) {
377
+ try {
378
+ let prompt;
379
+ let fullContext = null;
380
+ const useFullContext = this.config.useFullContext !== false && this.contextFetcher;
381
+ if (useFullContext) {
382
+ try {
383
+ fullContext = await this.contextFetcher.fetchTaskContext(task.taskId);
384
+ prompt = buildPromptWithContext(fullContext);
385
+ console.log(" \u{1F4DA} Using full context (org, project, artifact, related sections)");
386
+ } catch (contextError) {
387
+ console.warn(" \u26A0\uFE0F Could not fetch full context, using minimal prompt");
388
+ console.warn(` ${contextError instanceof Error ? contextError.message : contextError}`);
389
+ prompt = this.buildTaskPrompt(task);
390
+ }
391
+ } else {
392
+ prompt = this.buildTaskPrompt(task);
393
+ }
394
+ const output = await this.runClaude(prompt);
395
+ const { content, summary } = this.parseResponse(output, task.heading);
396
+ return {
397
+ success: true,
398
+ output: content,
399
+ summary,
400
+ promptUsed: prompt
401
+ };
402
+ } catch (error) {
403
+ const errorMessage = error instanceof Error ? error.message : String(error);
404
+ return {
405
+ success: false,
406
+ output: "",
407
+ summary: `Failed: ${errorMessage}`,
408
+ error: errorMessage
409
+ };
410
+ }
411
+ }
412
+ /**
413
+ * Run Claude Code CLI with the given prompt
414
+ */
415
+ runClaude(prompt) {
416
+ return new Promise((resolve, reject) => {
417
+ const claudePath = this.config.claudePath || "claude";
418
+ const proc = spawn(claudePath, ["--print"], {
419
+ stdio: ["pipe", "pipe", "pipe"],
420
+ timeout: this.config.timeout
421
+ });
422
+ let stdout = "";
423
+ let stderr = "";
424
+ proc.stdout.on("data", (data) => {
425
+ stdout += data.toString();
426
+ });
427
+ proc.stderr.on("data", (data) => {
428
+ stderr += data.toString();
429
+ });
430
+ proc.on("close", (code) => {
431
+ if (code === 0) {
432
+ resolve(stdout.trim());
433
+ } else {
434
+ reject(new Error(stderr || `Claude exited with code ${code}`));
435
+ }
436
+ });
437
+ proc.on("error", (err) => {
438
+ if (err.code === "ENOENT") {
439
+ reject(new Error(
440
+ "Claude Code CLI not found. Please install it:\n npm install -g @anthropic-ai/claude-code"
441
+ ));
442
+ } else {
443
+ reject(err);
444
+ }
445
+ });
446
+ proc.stdin.write(prompt);
447
+ proc.stdin.end();
448
+ });
449
+ }
450
+ /**
451
+ * Build the task prompt
452
+ */
453
+ buildTaskPrompt(task) {
454
+ const parts = [];
455
+ const systemPrompt = this.config.systemPromptPrefix ? `${this.config.systemPromptPrefix}
456
+
457
+ ${DEFAULT_SYSTEM_PROMPT}` : DEFAULT_SYSTEM_PROMPT;
458
+ parts.push(systemPrompt);
459
+ parts.push("");
460
+ parts.push("---");
461
+ parts.push("");
462
+ parts.push(`# Task: ${task.heading}`);
463
+ parts.push("");
464
+ if (task.artifactTitle) {
465
+ parts.push(`**Artifact:** ${task.artifactTitle}`);
466
+ }
467
+ if (task.priority) {
468
+ const priorityLabels = ["High", "Medium", "Low"];
469
+ parts.push(`**Priority:** ${priorityLabels[task.priority - 1] || "Medium"}`);
470
+ }
471
+ parts.push("");
472
+ parts.push("## Description");
473
+ parts.push(task.content || "No additional description provided.");
474
+ parts.push("");
475
+ if (task.context && Object.keys(task.context).length > 0) {
476
+ parts.push("## Additional Context");
477
+ parts.push("```json");
478
+ parts.push(JSON.stringify(task.context, null, 2));
479
+ parts.push("```");
480
+ parts.push("");
481
+ }
482
+ parts.push("## Instructions");
483
+ parts.push("Complete this task and provide your output below.");
484
+ return parts.join("\n");
485
+ }
486
+ /**
487
+ * Parse the response to extract output and summary
488
+ */
489
+ parseResponse(fullOutput, taskHeading) {
490
+ const summaryMatch = fullOutput.match(/SUMMARY:\s*(.+?)(?:\n|$)/i);
491
+ if (summaryMatch) {
492
+ const summary2 = summaryMatch[1].trim();
493
+ const content = fullOutput.replace(/SUMMARY:\s*.+?(?:\n|$)/i, "").trim();
494
+ return { content, summary: summary2 };
495
+ }
496
+ const lines = fullOutput.split("\n").filter((l) => l.trim());
497
+ const firstLine = lines[0] || "";
498
+ const summary = firstLine.length > 100 ? `${firstLine.substring(0, 97)}...` : firstLine || `Completed: ${taskHeading}`;
499
+ return { content: fullOutput, summary };
500
+ }
501
+ /**
502
+ * Test that Claude Code CLI is available and working
503
+ */
504
+ async testConnection() {
505
+ try {
506
+ const output = await this.runClaude('Say "connected" and nothing else.');
507
+ return output.toLowerCase().includes("connected");
508
+ } catch {
509
+ return false;
510
+ }
511
+ }
512
+ /**
513
+ * Check if Claude Code CLI is installed
514
+ */
515
+ async isInstalled() {
516
+ return new Promise((resolve) => {
517
+ const proc = spawn(this.config.claudePath || "claude", ["--version"], {
518
+ stdio: ["ignore", "pipe", "pipe"]
519
+ });
520
+ proc.on("close", (code) => {
521
+ resolve(code === 0);
522
+ });
523
+ proc.on("error", () => {
524
+ resolve(false);
525
+ });
526
+ });
527
+ }
528
+ };
529
+ function createExecutor(config) {
530
+ return new ClaudeExecutor(config);
531
+ }
532
+
533
+ // src/listener.ts
534
+ import EventSource from "eventsource";
535
+ var DEFAULT_BASE_URL2 = "https://artyfacts.dev/api/v1";
536
+ var EVENT_TYPES = [
537
+ "connected",
538
+ "heartbeat",
539
+ "task_assigned",
540
+ "task_unblocked",
541
+ "blocker_resolved",
542
+ "notification"
543
+ ];
544
+ var ArtyfactsListener = class {
545
+ config;
546
+ eventSource = null;
547
+ callbacks = /* @__PURE__ */ new Map();
548
+ allCallbacks = /* @__PURE__ */ new Set();
549
+ state = "disconnected";
550
+ reconnectAttempts = 0;
551
+ maxReconnectAttempts = 10;
552
+ reconnectDelay = 1e3;
553
+ constructor(config) {
554
+ if (!config.apiKey) {
555
+ throw new Error("API key is required");
556
+ }
557
+ if (!config.agentId) {
558
+ throw new Error("Agent ID is required");
559
+ }
560
+ this.config = {
561
+ ...config,
562
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL2
563
+ };
564
+ }
565
+ /**
566
+ * Get current connection state
567
+ */
568
+ get connectionState() {
569
+ return this.state;
570
+ }
571
+ /**
572
+ * Check if connected
573
+ */
574
+ get isConnected() {
575
+ return this.state === "connected";
576
+ }
577
+ /**
578
+ * Subscribe to all events
579
+ */
580
+ subscribe(callback) {
581
+ this.allCallbacks.add(callback);
582
+ return () => {
583
+ this.allCallbacks.delete(callback);
584
+ };
585
+ }
586
+ /**
587
+ * Subscribe to a specific event type
588
+ */
589
+ on(type, callback) {
590
+ if (!this.callbacks.has(type)) {
591
+ this.callbacks.set(type, /* @__PURE__ */ new Set());
592
+ }
593
+ this.callbacks.get(type).add(callback);
594
+ return () => {
595
+ const typeCallbacks = this.callbacks.get(type);
596
+ if (typeCallbacks) {
597
+ typeCallbacks.delete(callback);
598
+ if (typeCallbacks.size === 0) {
599
+ this.callbacks.delete(type);
600
+ }
601
+ }
602
+ };
603
+ }
604
+ /**
605
+ * Connect to the SSE stream
606
+ */
607
+ connect() {
608
+ if (this.eventSource) {
609
+ return;
610
+ }
611
+ this.setState("connecting");
612
+ const url = new URL(`${this.config.baseUrl}/events/stream`);
613
+ url.searchParams.set("apiKey", this.config.apiKey);
614
+ url.searchParams.set("agentId", this.config.agentId);
615
+ this.eventSource = new EventSource(url.toString(), {
616
+ headers: {
617
+ "Authorization": `Bearer ${this.config.apiKey}`
618
+ }
619
+ });
620
+ this.eventSource.onopen = () => {
621
+ this.reconnectAttempts = 0;
622
+ this.reconnectDelay = 1e3;
623
+ this.setState("connected");
624
+ };
625
+ this.eventSource.onmessage = (event) => {
626
+ this.handleMessage(event);
627
+ };
628
+ this.eventSource.onerror = (event) => {
629
+ this.handleError(event);
630
+ };
631
+ for (const eventType of EVENT_TYPES) {
632
+ this.eventSource.addEventListener(eventType, (event) => {
633
+ this.handleMessage(event, eventType);
634
+ });
635
+ }
636
+ }
637
+ /**
638
+ * Disconnect from the SSE stream
639
+ */
640
+ disconnect() {
641
+ if (this.eventSource) {
642
+ this.eventSource.close();
643
+ this.eventSource = null;
644
+ }
645
+ this.setState("disconnected");
646
+ }
647
+ /**
648
+ * Reconnect to the SSE stream
649
+ */
650
+ reconnect() {
651
+ this.disconnect();
652
+ this.connect();
653
+ }
654
+ /**
655
+ * Handle incoming SSE message
656
+ */
657
+ handleMessage(event, eventType) {
658
+ try {
659
+ const data = JSON.parse(event.data);
660
+ const rawData = data.data || data;
661
+ const normalizedData = rawData.task_id ? {
662
+ taskId: rawData.task_id,
663
+ sectionId: rawData.section_id,
664
+ artifactId: rawData.artifact_id,
665
+ artifactTitle: rawData.artifact_title,
666
+ heading: rawData.heading,
667
+ content: rawData.content,
668
+ assignedTo: rawData.assigned_to,
669
+ assignedAt: rawData.assigned_at,
670
+ priority: rawData.priority,
671
+ ...rawData
672
+ // Keep original fields too
673
+ } : rawData;
674
+ const artyfactsEvent = {
675
+ type: eventType || data.type || "unknown",
676
+ timestamp: data.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
677
+ data: normalizedData
678
+ };
679
+ const typeCallbacks = this.callbacks.get(artyfactsEvent.type);
680
+ if (typeCallbacks) {
681
+ for (const callback of typeCallbacks) {
682
+ this.safeCallCallback(callback, artyfactsEvent);
683
+ }
684
+ }
685
+ for (const callback of this.allCallbacks) {
686
+ this.safeCallCallback(callback, artyfactsEvent);
687
+ }
688
+ } catch (err) {
689
+ console.error("[Listener] Failed to parse SSE message:", event.data, err);
690
+ }
691
+ }
692
+ /**
693
+ * Safely call a callback, handling async and errors
694
+ */
695
+ async safeCallCallback(callback, event) {
696
+ try {
697
+ await callback(event);
698
+ } catch (err) {
699
+ console.error(`[Listener] Error in event callback for '${event.type}':`, err);
700
+ }
701
+ }
702
+ /**
703
+ * Handle SSE error
704
+ */
705
+ handleError(event) {
706
+ if (this.eventSource?.readyState === EventSource.CONNECTING) {
707
+ this.setState("reconnecting");
708
+ } else if (this.eventSource?.readyState === EventSource.CLOSED) {
709
+ this.setState("disconnected");
710
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
711
+ this.reconnectAttempts++;
712
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, 3e4);
713
+ console.log(
714
+ `[Listener] Connection lost, reconnecting in ${this.reconnectDelay / 1e3}s (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
715
+ );
716
+ setTimeout(() => {
717
+ if (this.state === "disconnected") {
718
+ this.connect();
719
+ }
720
+ }, this.reconnectDelay);
721
+ } else {
722
+ const error = new Error("Max reconnection attempts reached");
723
+ this.config.onError?.(error);
724
+ }
725
+ }
726
+ }
727
+ /**
728
+ * Update connection state
729
+ */
730
+ setState(state) {
731
+ if (this.state !== state) {
732
+ this.state = state;
733
+ this.config.onStateChange?.(state);
734
+ }
735
+ }
736
+ };
737
+ function createListener(config) {
738
+ return new ArtyfactsListener(config);
739
+ }
740
+
741
+ export {
742
+ loadCredentials,
743
+ saveCredentials,
744
+ clearCredentials,
745
+ runDeviceAuth,
746
+ promptForApiKey,
747
+ getCredentials,
748
+ ContextFetcher,
749
+ buildPromptWithContext,
750
+ createContextFetcher,
751
+ ClaudeExecutor,
752
+ createExecutor,
753
+ ArtyfactsListener,
754
+ createListener
755
+ };
package/dist/cli.js CHANGED
@@ -687,10 +687,24 @@ var ArtyfactsListener = class {
687
687
  handleMessage(event, eventType) {
688
688
  try {
689
689
  const data = JSON.parse(event.data);
690
+ const rawData = data.data || data;
691
+ const normalizedData = rawData.task_id ? {
692
+ taskId: rawData.task_id,
693
+ sectionId: rawData.section_id,
694
+ artifactId: rawData.artifact_id,
695
+ artifactTitle: rawData.artifact_title,
696
+ heading: rawData.heading,
697
+ content: rawData.content,
698
+ assignedTo: rawData.assigned_to,
699
+ assignedAt: rawData.assigned_at,
700
+ priority: rawData.priority,
701
+ ...rawData
702
+ // Keep original fields too
703
+ } : rawData;
690
704
  const artyfactsEvent = {
691
705
  type: eventType || data.type || "unknown",
692
706
  timestamp: data.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
693
- data: data.data || data
707
+ data: normalizedData
694
708
  };
695
709
  const typeCallbacks = this.callbacks.get(artyfactsEvent.type);
696
710
  if (typeCallbacks) {
@@ -846,11 +860,11 @@ async function checkAndClaimTasks(baseUrl, apiKey, agentId, activeTasks, executo
846
860
  console.log(` \u26A0\uFE0F Could not claim: ${error.error || "Unknown error"}`);
847
861
  continue;
848
862
  }
849
- const claimedTask = await claimResponse.json().catch((e) => {
863
+ const claimData = await claimResponse.json().catch((e) => {
850
864
  console.log(` \u26A0\uFE0F Could not parse claim response: ${e}`);
851
- return { id: task.id };
865
+ return { task: { id: task.id } };
852
866
  });
853
- const taskUuid = claimedTask.id || task.id;
867
+ const taskUuid = claimData.task?.id || task.id;
854
868
  console.log(` \u{1F4CE} Using task UUID: ${taskUuid} (from queue: ${task.id})`);
855
869
  activeTasks.add(task.section_id);
856
870
  console.log(" \u2713 Claimed!");
package/dist/cli.mjs CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  getCredentials,
7
7
  loadCredentials,
8
8
  promptForApiKey
9
- } from "./chunk-EROV5RIA.mjs";
9
+ } from "./chunk-7ULDABOE.mjs";
10
10
 
11
11
  // src/cli.ts
12
12
  import { Command } from "commander";
@@ -101,11 +101,11 @@ async function checkAndClaimTasks(baseUrl, apiKey, agentId, activeTasks, executo
101
101
  console.log(` \u26A0\uFE0F Could not claim: ${error.error || "Unknown error"}`);
102
102
  continue;
103
103
  }
104
- const claimedTask = await claimResponse.json().catch((e) => {
104
+ const claimData = await claimResponse.json().catch((e) => {
105
105
  console.log(` \u26A0\uFE0F Could not parse claim response: ${e}`);
106
- return { id: task.id };
106
+ return { task: { id: task.id } };
107
107
  });
108
- const taskUuid = claimedTask.id || task.id;
108
+ const taskUuid = claimData.task?.id || task.id;
109
109
  console.log(` \u{1F4CE} Using task UUID: ${taskUuid} (from queue: ${task.id})`);
110
110
  activeTasks.add(task.section_id);
111
111
  console.log(" \u2713 Claimed!");
package/dist/index.js CHANGED
@@ -707,10 +707,24 @@ var ArtyfactsListener = class {
707
707
  handleMessage(event, eventType) {
708
708
  try {
709
709
  const data = JSON.parse(event.data);
710
+ const rawData = data.data || data;
711
+ const normalizedData = rawData.task_id ? {
712
+ taskId: rawData.task_id,
713
+ sectionId: rawData.section_id,
714
+ artifactId: rawData.artifact_id,
715
+ artifactTitle: rawData.artifact_title,
716
+ heading: rawData.heading,
717
+ content: rawData.content,
718
+ assignedTo: rawData.assigned_to,
719
+ assignedAt: rawData.assigned_at,
720
+ priority: rawData.priority,
721
+ ...rawData
722
+ // Keep original fields too
723
+ } : rawData;
710
724
  const artyfactsEvent = {
711
725
  type: eventType || data.type || "unknown",
712
726
  timestamp: data.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
713
- data: data.data || data
727
+ data: normalizedData
714
728
  };
715
729
  const typeCallbacks = this.callbacks.get(artyfactsEvent.type);
716
730
  if (typeCallbacks) {
package/dist/index.mjs CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  promptForApiKey,
13
13
  runDeviceAuth,
14
14
  saveCredentials
15
- } from "./chunk-EROV5RIA.mjs";
15
+ } from "./chunk-7ULDABOE.mjs";
16
16
  export {
17
17
  ArtyfactsListener,
18
18
  ClaudeExecutor,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artyfacts/claude",
3
- "version": "1.2.2",
3
+ "version": "1.2.6",
4
4
  "description": "Claude adapter for Artyfacts - Execute tasks using Claude Code CLI",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
package/src/cli.ts CHANGED
@@ -197,16 +197,17 @@ async function checkAndClaimTasks(
197
197
  }
198
198
 
199
199
  // Get the claimed task data (includes UUID which is globally unique)
200
- const claimedTask = await claimResponse.json().catch((e) => {
200
+ // Response format: { success: true, task: { id: ..., artifact_id: ..., ... } }
201
+ const claimData = await claimResponse.json().catch((e) => {
201
202
  console.log(` ⚠️ Could not parse claim response: ${e}`);
202
- return { id: task.id };
203
+ return { task: { id: task.id } };
203
204
  }) as {
204
- id: string;
205
- section_id: string;
205
+ success?: boolean;
206
+ task?: { id: string; artifact_id?: string };
206
207
  };
207
208
 
208
209
  // Use UUID for subsequent calls (globally unique, avoids org filter issues)
209
- const taskUuid = claimedTask.id || task.id;
210
+ const taskUuid = claimData.task?.id || task.id;
210
211
  console.log(` 📎 Using task UUID: ${taskUuid} (from queue: ${task.id})`); // DEBUG
211
212
 
212
213
  // Successfully claimed - now execute
package/src/listener.ts CHANGED
@@ -224,12 +224,27 @@ export class ArtyfactsListener {
224
224
  private handleMessage(event: MessageEvent, eventType?: string): void {
225
225
  try {
226
226
  const data = JSON.parse(event.data);
227
+
228
+ // Normalize snake_case to camelCase for task data
229
+ const rawData = data.data || data;
230
+ const normalizedData = rawData.task_id ? {
231
+ taskId: rawData.task_id,
232
+ sectionId: rawData.section_id,
233
+ artifactId: rawData.artifact_id,
234
+ artifactTitle: rawData.artifact_title,
235
+ heading: rawData.heading,
236
+ content: rawData.content,
237
+ assignedTo: rawData.assigned_to,
238
+ assignedAt: rawData.assigned_at,
239
+ priority: rawData.priority,
240
+ ...rawData, // Keep original fields too
241
+ } : rawData;
227
242
 
228
243
  // Normalize event structure
229
244
  const artyfactsEvent: ArtyfactsEvent = {
230
245
  type: eventType || data.type || 'unknown',
231
246
  timestamp: data.timestamp || new Date().toISOString(),
232
- data: data.data || data,
247
+ data: normalizedData,
233
248
  };
234
249
 
235
250
  // Route to type-specific callbacks