@artyfacts/claude 1.3.7 → 1.3.9

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,806 @@
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
+ ## Available Tools
223
+
224
+ You have access to Artyfacts MCP tools. USE THEM to complete your task:
225
+
226
+ - **create_artifact** - Create new artifacts (documents, specs, reports)
227
+ - **create_section** - Add sections to artifacts (content, tasks, decisions)
228
+ - **update_section** - Update existing sections
229
+ - **create_agent** - Create new AI agents with specific roles
230
+ - **list_artifacts** - Query existing artifacts
231
+ - **list_sections** - Query sections within an artifact
232
+ - **complete_task** - Mark a task as complete
233
+ - **block_task** - Block a task with a reason
234
+ - **create_blocker** - Create a decision blocker
235
+
236
+ IMPORTANT: When asked to create agents or update artifacts, USE THE TOOLS. Don't just describe what you would do - actually do it with the tools.
237
+
238
+ ## Guidelines
239
+
240
+ - Be thorough but concise
241
+ - USE THE TOOLS to take action, don't just analyze
242
+ - If the task requires creating something, use create_artifact or create_section
243
+ - If the task requires creating agents, use create_agent
244
+ - If you cannot complete the task, explain why
245
+
246
+ Format your response as follows:
247
+ 1. First, use the tools to complete the task
248
+ 2. Then summarize what you did
249
+ 3. End with a brief summary line starting with "SUMMARY:"`);
250
+ parts.push("");
251
+ parts.push("---");
252
+ parts.push("");
253
+ parts.push("## Organization Context");
254
+ parts.push(`**${context.organization.name}**`);
255
+ if (context.organization.context) {
256
+ parts.push("");
257
+ parts.push(formatOrgContext(context.organization.context));
258
+ }
259
+ parts.push("");
260
+ if (context.project) {
261
+ parts.push(`## Project: ${context.project.name}`);
262
+ if (context.project.description) {
263
+ parts.push(context.project.description);
264
+ }
265
+ parts.push("");
266
+ }
267
+ parts.push(`## Artifact: ${context.artifact.title}`);
268
+ if (context.artifact.summary) {
269
+ parts.push(context.artifact.summary);
270
+ }
271
+ if (context.artifact.description) {
272
+ parts.push("");
273
+ parts.push(context.artifact.description);
274
+ }
275
+ parts.push("");
276
+ const relatedSections = context.artifact.sections.filter(
277
+ (s) => s.id !== context.task.id
278
+ );
279
+ if (relatedSections.length > 0) {
280
+ parts.push("### Related Sections:");
281
+ for (const section of relatedSections) {
282
+ const preview = section.content ? section.content.substring(0, 200) + (section.content.length > 200 ? "..." : "") : "No content";
283
+ const statusBadge = section.task_status ? ` [${section.task_status}]` : "";
284
+ parts.push(`- **${section.heading}**${statusBadge}: ${preview}`);
285
+ }
286
+ parts.push("");
287
+ }
288
+ parts.push("---");
289
+ parts.push("");
290
+ parts.push(`## Your Task: ${context.task.heading}`);
291
+ if (context.task.priority) {
292
+ const priorityLabels = ["\u{1F534} High", "\u{1F7E1} Medium", "\u{1F7E2} Low"];
293
+ parts.push(`**Priority:** ${priorityLabels[context.task.priority - 1] || "Medium"}`);
294
+ }
295
+ parts.push("");
296
+ parts.push("### Description");
297
+ parts.push(context.task.content || "No additional description provided.");
298
+ parts.push("");
299
+ if (context.task.expected_output) {
300
+ parts.push("### Expected Output");
301
+ if (context.task.expected_output.format) {
302
+ parts.push(`**Format:** ${context.task.expected_output.format}`);
303
+ }
304
+ if (context.task.expected_output.requirements && context.task.expected_output.requirements.length > 0) {
305
+ parts.push("**Requirements:**");
306
+ for (const req of context.task.expected_output.requirements) {
307
+ parts.push(`- ${req}`);
308
+ }
309
+ }
310
+ parts.push("");
311
+ }
312
+ parts.push("---");
313
+ parts.push("");
314
+ parts.push("Complete this task and provide your output below.");
315
+ return parts.join("\n");
316
+ }
317
+ function formatOrgContext(context) {
318
+ const trimmed = context.trim();
319
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
320
+ try {
321
+ const parsed = JSON.parse(trimmed);
322
+ return formatContextObject(parsed);
323
+ } catch {
324
+ return context;
325
+ }
326
+ }
327
+ return context;
328
+ }
329
+ function formatContextObject(obj, indent = "") {
330
+ if (typeof obj !== "object" || obj === null) {
331
+ return String(obj);
332
+ }
333
+ if (Array.isArray(obj)) {
334
+ return obj.map((item) => `${indent}- ${formatContextObject(item, indent + " ")}`).join("\n");
335
+ }
336
+ const lines = [];
337
+ for (const [key, value] of Object.entries(obj)) {
338
+ const label = key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
339
+ if (typeof value === "object" && value !== null) {
340
+ lines.push(`${indent}**${label}:**`);
341
+ lines.push(formatContextObject(value, indent + " "));
342
+ } else {
343
+ lines.push(`${indent}- **${label}:** ${value}`);
344
+ }
345
+ }
346
+ return lines.join("\n");
347
+ }
348
+ function createContextFetcher(config) {
349
+ return new ContextFetcher(config);
350
+ }
351
+
352
+ // src/executor.ts
353
+ import { spawn } from "child_process";
354
+ var DEFAULT_TIMEOUT = 5 * 60 * 1e3;
355
+ var DEFAULT_SYSTEM_PROMPT = `You are an AI agent working within the Artyfacts task management system.
356
+
357
+ Your job is to complete tasks assigned to you using the available tools.
358
+
359
+ ## Available Tools
360
+
361
+ You have access to Artyfacts MCP tools. USE THEM to complete your task:
362
+
363
+ - **create_artifact** - Create new artifacts (documents, specs, reports)
364
+ - **create_section** - Add sections to artifacts (content, tasks, decisions)
365
+ - **update_section** - Update existing sections
366
+ - **create_agent** - Create new AI agents with specific roles
367
+ - **list_artifacts** - Query existing artifacts
368
+ - **list_sections** - Query sections within an artifact
369
+ - **complete_task** - Mark a task as complete
370
+ - **block_task** - Block a task with a reason
371
+ - **create_blocker** - Create a decision blocker
372
+
373
+ IMPORTANT: When asked to create agents or update artifacts, USE THE TOOLS. Don't just describe what you would do - actually do it.
374
+
375
+ ## Guidelines
376
+
377
+ - USE THE TOOLS to take action
378
+ - If creating something, use create_artifact or create_section
379
+ - If creating agents, use create_agent
380
+ - If you cannot complete the task, explain why
381
+
382
+ Format your response as follows:
383
+ 1. First, use the tools to complete the task
384
+ 2. Summarize what you accomplished
385
+ 3. End with a brief summary line starting with "SUMMARY:"`;
386
+ var ClaudeExecutor = class {
387
+ config;
388
+ contextFetcher = null;
389
+ constructor(config = {}) {
390
+ this.config = {
391
+ ...config,
392
+ timeout: config.timeout || DEFAULT_TIMEOUT,
393
+ claudePath: config.claudePath || "claude"
394
+ };
395
+ if (config.baseUrl && config.apiKey) {
396
+ this.contextFetcher = createContextFetcher({
397
+ baseUrl: config.baseUrl,
398
+ apiKey: config.apiKey
399
+ });
400
+ }
401
+ }
402
+ /**
403
+ * Execute a task using Claude Code CLI
404
+ *
405
+ * If full context is available (baseUrl + apiKey configured), fetches
406
+ * organization, project, artifact, and related sections for a rich prompt.
407
+ */
408
+ async execute(task) {
409
+ try {
410
+ let prompt;
411
+ let fullContext = null;
412
+ const useFullContext = this.config.useFullContext !== false && this.contextFetcher;
413
+ if (useFullContext) {
414
+ try {
415
+ fullContext = await this.contextFetcher.fetchTaskContext(task.taskId);
416
+ prompt = buildPromptWithContext(fullContext);
417
+ console.log(" \u{1F4DA} Using full context (org, project, artifact, related sections)");
418
+ } catch (contextError) {
419
+ console.warn(" \u26A0\uFE0F Could not fetch full context, using minimal prompt");
420
+ console.warn(` ${contextError instanceof Error ? contextError.message : contextError}`);
421
+ prompt = this.buildTaskPrompt(task);
422
+ }
423
+ } else {
424
+ prompt = this.buildTaskPrompt(task);
425
+ }
426
+ const output = await this.runClaude(prompt);
427
+ const { content, summary } = this.parseResponse(output, task.heading);
428
+ return {
429
+ success: true,
430
+ output: content,
431
+ summary,
432
+ promptUsed: prompt
433
+ };
434
+ } catch (error) {
435
+ const errorMessage = error instanceof Error ? error.message : String(error);
436
+ return {
437
+ success: false,
438
+ output: "",
439
+ summary: `Failed: ${errorMessage}`,
440
+ error: errorMessage
441
+ };
442
+ }
443
+ }
444
+ /**
445
+ * Run Claude Code CLI with the given prompt
446
+ */
447
+ runClaude(prompt) {
448
+ return new Promise((resolve, reject) => {
449
+ const claudePath = this.config.claudePath || "claude";
450
+ const mcpConfig = {
451
+ mcpServers: {
452
+ artyfacts: {
453
+ command: "npx",
454
+ args: ["-y", "@artyfacts/mcp-server"],
455
+ env: {
456
+ ARTYFACTS_API_KEY: this.config.apiKey || process.env.ARTYFACTS_API_KEY || "",
457
+ ARTYFACTS_BASE_URL: this.config.baseUrl || "https://artyfacts.dev/api/v1"
458
+ }
459
+ }
460
+ }
461
+ };
462
+ const args = [
463
+ "--print",
464
+ "--mcp-config",
465
+ JSON.stringify(mcpConfig),
466
+ "--permission-mode",
467
+ "bypassPermissions"
468
+ ];
469
+ const proc = spawn(claudePath, args, {
470
+ stdio: ["pipe", "pipe", "pipe"],
471
+ timeout: this.config.timeout
472
+ });
473
+ let stdout = "";
474
+ let stderr = "";
475
+ proc.stdout.on("data", (data) => {
476
+ stdout += data.toString();
477
+ });
478
+ proc.stderr.on("data", (data) => {
479
+ stderr += data.toString();
480
+ });
481
+ proc.on("close", (code) => {
482
+ if (code === 0) {
483
+ resolve(stdout.trim());
484
+ } else {
485
+ reject(new Error(stderr || `Claude exited with code ${code}`));
486
+ }
487
+ });
488
+ proc.on("error", (err) => {
489
+ if (err.code === "ENOENT") {
490
+ reject(new Error(
491
+ "Claude Code CLI not found. Please install it:\n npm install -g @anthropic-ai/claude-code"
492
+ ));
493
+ } else {
494
+ reject(err);
495
+ }
496
+ });
497
+ proc.stdin.write(prompt);
498
+ proc.stdin.end();
499
+ });
500
+ }
501
+ /**
502
+ * Build the task prompt
503
+ */
504
+ buildTaskPrompt(task) {
505
+ const parts = [];
506
+ const systemPrompt = this.config.systemPromptPrefix ? `${this.config.systemPromptPrefix}
507
+
508
+ ${DEFAULT_SYSTEM_PROMPT}` : DEFAULT_SYSTEM_PROMPT;
509
+ parts.push(systemPrompt);
510
+ parts.push("");
511
+ parts.push("---");
512
+ parts.push("");
513
+ parts.push(`# Task: ${task.heading}`);
514
+ parts.push("");
515
+ if (task.artifactTitle) {
516
+ parts.push(`**Artifact:** ${task.artifactTitle}`);
517
+ }
518
+ if (task.priority) {
519
+ const priorityLabels = ["High", "Medium", "Low"];
520
+ parts.push(`**Priority:** ${priorityLabels[task.priority - 1] || "Medium"}`);
521
+ }
522
+ parts.push("");
523
+ parts.push("## Description");
524
+ parts.push(task.content || "No additional description provided.");
525
+ parts.push("");
526
+ if (task.context && Object.keys(task.context).length > 0) {
527
+ parts.push("## Additional Context");
528
+ parts.push("```json");
529
+ parts.push(JSON.stringify(task.context, null, 2));
530
+ parts.push("```");
531
+ parts.push("");
532
+ }
533
+ parts.push("## Instructions");
534
+ parts.push("Complete this task and provide your output below.");
535
+ return parts.join("\n");
536
+ }
537
+ /**
538
+ * Parse the response to extract output and summary
539
+ */
540
+ parseResponse(fullOutput, taskHeading) {
541
+ const summaryMatch = fullOutput.match(/SUMMARY:\s*(.+?)(?:\n|$)/i);
542
+ if (summaryMatch) {
543
+ const summary2 = summaryMatch[1].trim();
544
+ const content = fullOutput.replace(/SUMMARY:\s*.+?(?:\n|$)/i, "").trim();
545
+ return { content, summary: summary2 };
546
+ }
547
+ const lines = fullOutput.split("\n").filter((l) => l.trim());
548
+ const firstLine = lines[0] || "";
549
+ const summary = firstLine.length > 100 ? `${firstLine.substring(0, 97)}...` : firstLine || `Completed: ${taskHeading}`;
550
+ return { content: fullOutput, summary };
551
+ }
552
+ /**
553
+ * Test that Claude Code CLI is available and working
554
+ */
555
+ async testConnection() {
556
+ try {
557
+ const output = await this.runClaude('Say "connected" and nothing else.');
558
+ return output.toLowerCase().includes("connected");
559
+ } catch {
560
+ return false;
561
+ }
562
+ }
563
+ /**
564
+ * Check if Claude Code CLI is installed
565
+ */
566
+ async isInstalled() {
567
+ return new Promise((resolve) => {
568
+ const proc = spawn(this.config.claudePath || "claude", ["--version"], {
569
+ stdio: ["ignore", "pipe", "pipe"]
570
+ });
571
+ proc.on("close", (code) => {
572
+ resolve(code === 0);
573
+ });
574
+ proc.on("error", () => {
575
+ resolve(false);
576
+ });
577
+ });
578
+ }
579
+ };
580
+ function createExecutor(config) {
581
+ return new ClaudeExecutor(config);
582
+ }
583
+
584
+ // src/listener.ts
585
+ import EventSource from "eventsource";
586
+ var DEFAULT_BASE_URL2 = "https://artyfacts.dev/api/v1";
587
+ var EVENT_TYPES = [
588
+ "connected",
589
+ "heartbeat",
590
+ "task_assigned",
591
+ "task_unblocked",
592
+ "blocker_resolved",
593
+ "notification"
594
+ ];
595
+ var ArtyfactsListener = class {
596
+ config;
597
+ eventSource = null;
598
+ callbacks = /* @__PURE__ */ new Map();
599
+ allCallbacks = /* @__PURE__ */ new Set();
600
+ state = "disconnected";
601
+ reconnectAttempts = 0;
602
+ maxReconnectAttempts = 10;
603
+ reconnectDelay = 1e3;
604
+ constructor(config) {
605
+ if (!config.apiKey) {
606
+ throw new Error("API key is required");
607
+ }
608
+ if (!config.agentId) {
609
+ throw new Error("Agent ID is required");
610
+ }
611
+ this.config = {
612
+ ...config,
613
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL2
614
+ };
615
+ }
616
+ /**
617
+ * Get current connection state
618
+ */
619
+ get connectionState() {
620
+ return this.state;
621
+ }
622
+ /**
623
+ * Check if connected
624
+ */
625
+ get isConnected() {
626
+ return this.state === "connected";
627
+ }
628
+ /**
629
+ * Subscribe to all events
630
+ */
631
+ subscribe(callback) {
632
+ this.allCallbacks.add(callback);
633
+ return () => {
634
+ this.allCallbacks.delete(callback);
635
+ };
636
+ }
637
+ /**
638
+ * Subscribe to a specific event type
639
+ */
640
+ on(type, callback) {
641
+ if (!this.callbacks.has(type)) {
642
+ this.callbacks.set(type, /* @__PURE__ */ new Set());
643
+ }
644
+ this.callbacks.get(type).add(callback);
645
+ return () => {
646
+ const typeCallbacks = this.callbacks.get(type);
647
+ if (typeCallbacks) {
648
+ typeCallbacks.delete(callback);
649
+ if (typeCallbacks.size === 0) {
650
+ this.callbacks.delete(type);
651
+ }
652
+ }
653
+ };
654
+ }
655
+ /**
656
+ * Connect to the SSE stream
657
+ */
658
+ connect() {
659
+ if (this.eventSource) {
660
+ return;
661
+ }
662
+ this.setState("connecting");
663
+ const url = new URL(`${this.config.baseUrl}/events/stream`);
664
+ url.searchParams.set("apiKey", this.config.apiKey);
665
+ url.searchParams.set("agentId", this.config.agentId);
666
+ this.eventSource = new EventSource(url.toString(), {
667
+ headers: {
668
+ "Authorization": `Bearer ${this.config.apiKey}`
669
+ }
670
+ });
671
+ this.eventSource.onopen = () => {
672
+ this.reconnectAttempts = 0;
673
+ this.reconnectDelay = 1e3;
674
+ this.setState("connected");
675
+ };
676
+ this.eventSource.onmessage = (event) => {
677
+ this.handleMessage(event);
678
+ };
679
+ this.eventSource.onerror = (event) => {
680
+ this.handleError(event);
681
+ };
682
+ for (const eventType of EVENT_TYPES) {
683
+ this.eventSource.addEventListener(eventType, (event) => {
684
+ this.handleMessage(event, eventType);
685
+ });
686
+ }
687
+ }
688
+ /**
689
+ * Disconnect from the SSE stream
690
+ */
691
+ disconnect() {
692
+ if (this.eventSource) {
693
+ this.eventSource.close();
694
+ this.eventSource = null;
695
+ }
696
+ this.setState("disconnected");
697
+ }
698
+ /**
699
+ * Reconnect to the SSE stream
700
+ */
701
+ reconnect() {
702
+ this.disconnect();
703
+ this.connect();
704
+ }
705
+ /**
706
+ * Handle incoming SSE message
707
+ */
708
+ handleMessage(event, eventType) {
709
+ try {
710
+ const data = JSON.parse(event.data);
711
+ const rawData = data.data || data;
712
+ const normalizedData = rawData.task_id ? {
713
+ taskId: rawData.task_id,
714
+ sectionId: rawData.section_id,
715
+ artifactId: rawData.artifact_id,
716
+ artifactTitle: rawData.artifact_title,
717
+ heading: rawData.heading,
718
+ content: rawData.content,
719
+ assignedTo: rawData.assigned_to,
720
+ assignedAt: rawData.assigned_at,
721
+ priority: rawData.priority,
722
+ ...rawData
723
+ // Keep original fields too
724
+ } : rawData;
725
+ const artyfactsEvent = {
726
+ type: eventType || data.type || "unknown",
727
+ timestamp: data.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
728
+ data: normalizedData
729
+ };
730
+ const typeCallbacks = this.callbacks.get(artyfactsEvent.type);
731
+ if (typeCallbacks) {
732
+ for (const callback of typeCallbacks) {
733
+ this.safeCallCallback(callback, artyfactsEvent);
734
+ }
735
+ }
736
+ for (const callback of this.allCallbacks) {
737
+ this.safeCallCallback(callback, artyfactsEvent);
738
+ }
739
+ } catch (err) {
740
+ console.error("[Listener] Failed to parse SSE message:", event.data, err);
741
+ }
742
+ }
743
+ /**
744
+ * Safely call a callback, handling async and errors
745
+ */
746
+ async safeCallCallback(callback, event) {
747
+ try {
748
+ await callback(event);
749
+ } catch (err) {
750
+ console.error(`[Listener] Error in event callback for '${event.type}':`, err);
751
+ }
752
+ }
753
+ /**
754
+ * Handle SSE error
755
+ */
756
+ handleError(event) {
757
+ if (this.eventSource?.readyState === EventSource.CONNECTING) {
758
+ this.setState("reconnecting");
759
+ } else if (this.eventSource?.readyState === EventSource.CLOSED) {
760
+ this.setState("disconnected");
761
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
762
+ this.reconnectAttempts++;
763
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, 3e4);
764
+ console.log(
765
+ `[Listener] Connection lost, reconnecting in ${this.reconnectDelay / 1e3}s (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
766
+ );
767
+ setTimeout(() => {
768
+ if (this.state === "disconnected") {
769
+ this.connect();
770
+ }
771
+ }, this.reconnectDelay);
772
+ } else {
773
+ const error = new Error("Max reconnection attempts reached");
774
+ this.config.onError?.(error);
775
+ }
776
+ }
777
+ }
778
+ /**
779
+ * Update connection state
780
+ */
781
+ setState(state) {
782
+ if (this.state !== state) {
783
+ this.state = state;
784
+ this.config.onStateChange?.(state);
785
+ }
786
+ }
787
+ };
788
+ function createListener(config) {
789
+ return new ArtyfactsListener(config);
790
+ }
791
+
792
+ export {
793
+ loadCredentials,
794
+ saveCredentials,
795
+ clearCredentials,
796
+ runDeviceAuth,
797
+ promptForApiKey,
798
+ getCredentials,
799
+ ContextFetcher,
800
+ buildPromptWithContext,
801
+ createContextFetcher,
802
+ ClaudeExecutor,
803
+ createExecutor,
804
+ ArtyfactsListener,
805
+ createListener
806
+ };
package/dist/cli.js CHANGED
@@ -493,7 +493,9 @@ var ClaudeExecutor = class {
493
493
  const args = [
494
494
  "--print",
495
495
  "--mcp-config",
496
- JSON.stringify(mcpConfig)
496
+ JSON.stringify(mcpConfig),
497
+ "--permission-mode",
498
+ "bypassPermissions"
497
499
  ];
498
500
  const proc = (0, import_child_process.spawn)(claudePath, args, {
499
501
  stdio: ["pipe", "pipe", "pipe"],
@@ -988,14 +990,21 @@ async function checkAndClaimTasks(baseUrl, apiKey, agentId, activeTasks, executo
988
990
  priority: task.priority
989
991
  });
990
992
  if (result.success) {
991
- await completeTask({
992
- baseUrl,
993
- apiKey,
994
- taskId: taskUuid,
995
- // Use UUID instead of section_id
996
- output: result.output,
997
- summary: result.summary
998
- });
993
+ try {
994
+ await completeTask({
995
+ baseUrl,
996
+ apiKey,
997
+ taskId: taskUuid,
998
+ // Use UUID instead of section_id
999
+ output: result.output,
1000
+ summary: result.summary
1001
+ });
1002
+ } catch (err) {
1003
+ const errMsg = err instanceof Error ? err.message : String(err);
1004
+ if (!errMsg.includes("already complete")) {
1005
+ throw err;
1006
+ }
1007
+ }
999
1008
  console.log(` \u2192 \u2705 Completed! ${result.summary}`);
1000
1009
  } else {
1001
1010
  console.log(` \u2192 \u274C Failed: ${result.error}`);
@@ -1108,13 +1117,20 @@ async function runAgent(options) {
1108
1117
  };
1109
1118
  const result = await executor.execute(taskContext);
1110
1119
  if (result.success) {
1111
- await completeTask({
1112
- baseUrl: options.baseUrl,
1113
- apiKey: credentials.apiKey,
1114
- taskId: task.taskId,
1115
- output: result.output,
1116
- summary: result.summary
1117
- });
1120
+ try {
1121
+ await completeTask({
1122
+ baseUrl: options.baseUrl,
1123
+ apiKey: credentials.apiKey,
1124
+ taskId: task.taskId,
1125
+ output: result.output,
1126
+ summary: result.summary
1127
+ });
1128
+ } catch (err) {
1129
+ const errMsg = err instanceof Error ? err.message : String(err);
1130
+ if (!errMsg.includes("already complete")) {
1131
+ throw err;
1132
+ }
1133
+ }
1118
1134
  console.log(` \u2192 \u2705 Completed! ${result.summary}`);
1119
1135
  } else {
1120
1136
  console.log(` \u2192 \u274C Failed: ${result.error}`);
@@ -1132,8 +1148,15 @@ async function runAgent(options) {
1132
1148
  }
1133
1149
  });
1134
1150
  listener.connect();
1151
+ const POLL_INTERVAL_MS = 3e4;
1152
+ const pollInterval = setInterval(() => {
1153
+ if (!options.dryRun) {
1154
+ checkAndClaimTasks(options.baseUrl, credentials.apiKey, credentials.agentId, activeTasks, executor, options.dryRun);
1155
+ }
1156
+ }, POLL_INTERVAL_MS);
1135
1157
  const shutdown = () => {
1136
1158
  console.log("\n\u{1F44B} Disconnecting...");
1159
+ clearInterval(pollInterval);
1137
1160
  listener.disconnect();
1138
1161
  process.exit(0);
1139
1162
  };
package/dist/cli.mjs CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  getCredentials,
7
7
  loadCredentials,
8
8
  promptForApiKey
9
- } from "./chunk-HD3C42LY.mjs";
9
+ } from "./chunk-G6HSQFKJ.mjs";
10
10
 
11
11
  // src/cli.ts
12
12
  import { Command } from "commander";
@@ -180,14 +180,21 @@ async function checkAndClaimTasks(baseUrl, apiKey, agentId, activeTasks, executo
180
180
  priority: task.priority
181
181
  });
182
182
  if (result.success) {
183
- await completeTask({
184
- baseUrl,
185
- apiKey,
186
- taskId: taskUuid,
187
- // Use UUID instead of section_id
188
- output: result.output,
189
- summary: result.summary
190
- });
183
+ try {
184
+ await completeTask({
185
+ baseUrl,
186
+ apiKey,
187
+ taskId: taskUuid,
188
+ // Use UUID instead of section_id
189
+ output: result.output,
190
+ summary: result.summary
191
+ });
192
+ } catch (err) {
193
+ const errMsg = err instanceof Error ? err.message : String(err);
194
+ if (!errMsg.includes("already complete")) {
195
+ throw err;
196
+ }
197
+ }
191
198
  console.log(` \u2192 \u2705 Completed! ${result.summary}`);
192
199
  } else {
193
200
  console.log(` \u2192 \u274C Failed: ${result.error}`);
@@ -300,13 +307,20 @@ async function runAgent(options) {
300
307
  };
301
308
  const result = await executor.execute(taskContext);
302
309
  if (result.success) {
303
- await completeTask({
304
- baseUrl: options.baseUrl,
305
- apiKey: credentials.apiKey,
306
- taskId: task.taskId,
307
- output: result.output,
308
- summary: result.summary
309
- });
310
+ try {
311
+ await completeTask({
312
+ baseUrl: options.baseUrl,
313
+ apiKey: credentials.apiKey,
314
+ taskId: task.taskId,
315
+ output: result.output,
316
+ summary: result.summary
317
+ });
318
+ } catch (err) {
319
+ const errMsg = err instanceof Error ? err.message : String(err);
320
+ if (!errMsg.includes("already complete")) {
321
+ throw err;
322
+ }
323
+ }
310
324
  console.log(` \u2192 \u2705 Completed! ${result.summary}`);
311
325
  } else {
312
326
  console.log(` \u2192 \u274C Failed: ${result.error}`);
@@ -324,8 +338,15 @@ async function runAgent(options) {
324
338
  }
325
339
  });
326
340
  listener.connect();
341
+ const POLL_INTERVAL_MS = 3e4;
342
+ const pollInterval = setInterval(() => {
343
+ if (!options.dryRun) {
344
+ checkAndClaimTasks(options.baseUrl, credentials.apiKey, credentials.agentId, activeTasks, executor, options.dryRun);
345
+ }
346
+ }, POLL_INTERVAL_MS);
327
347
  const shutdown = () => {
328
348
  console.log("\n\u{1F44B} Disconnecting...");
349
+ clearInterval(pollInterval);
329
350
  listener.disconnect();
330
351
  process.exit(0);
331
352
  };
package/dist/index.js CHANGED
@@ -523,7 +523,9 @@ var ClaudeExecutor = class {
523
523
  const args = [
524
524
  "--print",
525
525
  "--mcp-config",
526
- JSON.stringify(mcpConfig)
526
+ JSON.stringify(mcpConfig),
527
+ "--permission-mode",
528
+ "bypassPermissions"
527
529
  ];
528
530
  const proc = (0, import_child_process.spawn)(claudePath, args, {
529
531
  stdio: ["pipe", "pipe", "pipe"],
package/dist/index.mjs CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  promptForApiKey,
13
13
  runDeviceAuth,
14
14
  saveCredentials
15
- } from "./chunk-HD3C42LY.mjs";
15
+ } from "./chunk-G6HSQFKJ.mjs";
16
16
 
17
17
  // node_modules/@anthropic-ai/sdk/internal/tslib.mjs
18
18
  function __classPrivateFieldSet(receiver, state, value, kind, f) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artyfacts/claude",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
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
@@ -305,13 +305,21 @@ async function checkAndClaimTasks(
305
305
  });
306
306
 
307
307
  if (result.success) {
308
- await completeTask({
309
- baseUrl,
310
- apiKey,
311
- taskId: taskUuid, // Use UUID instead of section_id
312
- output: result.output,
313
- summary: result.summary,
314
- });
308
+ try {
309
+ await completeTask({
310
+ baseUrl,
311
+ apiKey,
312
+ taskId: taskUuid, // Use UUID instead of section_id
313
+ output: result.output,
314
+ summary: result.summary,
315
+ });
316
+ } catch (err) {
317
+ // Ignore "already complete" - means Claude completed it via MCP
318
+ const errMsg = err instanceof Error ? err.message : String(err);
319
+ if (!errMsg.includes('already complete')) {
320
+ throw err;
321
+ }
322
+ }
315
323
  console.log(` → ✅ Completed! ${result.summary}`);
316
324
  } else {
317
325
  console.log(` → ❌ Failed: ${result.error}`);
@@ -460,14 +468,22 @@ async function runAgent(options: {
460
468
  const result = await executor!.execute(taskContext);
461
469
 
462
470
  if (result.success) {
463
- // Complete task via API
464
- await completeTask({
465
- baseUrl: options.baseUrl,
466
- apiKey: credentials.apiKey,
467
- taskId: task.taskId,
468
- output: result.output,
469
- summary: result.summary,
470
- });
471
+ // Complete task via API (Claude may have already done this via MCP tool)
472
+ try {
473
+ await completeTask({
474
+ baseUrl: options.baseUrl,
475
+ apiKey: credentials.apiKey,
476
+ taskId: task.taskId,
477
+ output: result.output,
478
+ summary: result.summary,
479
+ });
480
+ } catch (err) {
481
+ // Ignore "already complete" - means Claude completed it via MCP
482
+ const errMsg = err instanceof Error ? err.message : String(err);
483
+ if (!errMsg.includes('already complete')) {
484
+ throw err;
485
+ }
486
+ }
471
487
 
472
488
  console.log(` → ✅ Completed! ${result.summary}`);
473
489
  } else {
@@ -491,9 +507,19 @@ async function runAgent(options: {
491
507
  // Start listening
492
508
  listener.connect();
493
509
 
510
+ // Poll for tasks periodically (every 30 seconds) as a backup to SSE
511
+ // This catches tasks that were created before we connected or if SSE events are missed
512
+ const POLL_INTERVAL_MS = 30_000;
513
+ const pollInterval = setInterval(() => {
514
+ if (!options.dryRun) {
515
+ checkAndClaimTasks(options.baseUrl, credentials.apiKey, credentials.agentId, activeTasks, executor!, options.dryRun);
516
+ }
517
+ }, POLL_INTERVAL_MS);
518
+
494
519
  // Handle shutdown
495
520
  const shutdown = () => {
496
521
  console.log('\n👋 Disconnecting...');
522
+ clearInterval(pollInterval);
497
523
  listener.disconnect();
498
524
  process.exit(0);
499
525
  };
package/src/executor.ts CHANGED
@@ -198,9 +198,11 @@ export class ClaudeExecutor {
198
198
 
199
199
  // Use claude CLI with --print flag for non-interactive output
200
200
  // Pass MCP config to enable Artyfacts tools
201
+ // Use permission-mode to allow tool execution without prompts
201
202
  const args = [
202
203
  '--print',
203
204
  '--mcp-config', JSON.stringify(mcpConfig),
205
+ '--permission-mode', 'bypassPermissions',
204
206
  ];
205
207
 
206
208
  const proc = spawn(claudePath, args, {