@artyfacts/claude 1.1.0 → 1.1.2

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,567 @@
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/executor.ts
190
+ import { spawn } from "child_process";
191
+ var DEFAULT_TIMEOUT = 5 * 60 * 1e3;
192
+ var DEFAULT_SYSTEM_PROMPT = `You are an AI agent working within the Artyfacts task management system.
193
+
194
+ Your job is to complete tasks assigned to you. For each task:
195
+ 1. Understand the requirements from the task heading and content
196
+ 2. Complete the task to the best of your ability
197
+ 3. Provide a clear, actionable output
198
+
199
+ Guidelines:
200
+ - Be thorough but concise
201
+ - If the task requires code, provide working code
202
+ - If the task requires analysis, provide structured findings
203
+ - If the task requires a decision, explain your reasoning
204
+ - If you cannot complete the task, explain why
205
+
206
+ Format your response as follows:
207
+ 1. First, provide your main output (the task deliverable)
208
+ 2. End with a brief summary line starting with "SUMMARY:"`;
209
+ var ClaudeExecutor = class {
210
+ config;
211
+ constructor(config = {}) {
212
+ this.config = {
213
+ ...config,
214
+ timeout: config.timeout || DEFAULT_TIMEOUT,
215
+ claudePath: config.claudePath || "claude"
216
+ };
217
+ }
218
+ /**
219
+ * Execute a task using Claude Code CLI
220
+ */
221
+ async execute(task) {
222
+ try {
223
+ const prompt = this.buildTaskPrompt(task);
224
+ const output = await this.runClaude(prompt);
225
+ const { content, summary } = this.parseResponse(output, task.heading);
226
+ return {
227
+ success: true,
228
+ output: content,
229
+ summary
230
+ };
231
+ } catch (error) {
232
+ const errorMessage = error instanceof Error ? error.message : String(error);
233
+ return {
234
+ success: false,
235
+ output: "",
236
+ summary: `Failed: ${errorMessage}`,
237
+ error: errorMessage
238
+ };
239
+ }
240
+ }
241
+ /**
242
+ * Run Claude Code CLI with the given prompt
243
+ */
244
+ runClaude(prompt) {
245
+ return new Promise((resolve, reject) => {
246
+ const claudePath = this.config.claudePath || "claude";
247
+ const proc = spawn(claudePath, ["--print"], {
248
+ stdio: ["pipe", "pipe", "pipe"],
249
+ timeout: this.config.timeout
250
+ });
251
+ let stdout = "";
252
+ let stderr = "";
253
+ proc.stdout.on("data", (data) => {
254
+ stdout += data.toString();
255
+ });
256
+ proc.stderr.on("data", (data) => {
257
+ stderr += data.toString();
258
+ });
259
+ proc.on("close", (code) => {
260
+ if (code === 0) {
261
+ resolve(stdout.trim());
262
+ } else {
263
+ reject(new Error(stderr || `Claude exited with code ${code}`));
264
+ }
265
+ });
266
+ proc.on("error", (err) => {
267
+ if (err.code === "ENOENT") {
268
+ reject(new Error(
269
+ "Claude Code CLI not found. Please install it:\n npm install -g @anthropic-ai/claude-code"
270
+ ));
271
+ } else {
272
+ reject(err);
273
+ }
274
+ });
275
+ proc.stdin.write(prompt);
276
+ proc.stdin.end();
277
+ });
278
+ }
279
+ /**
280
+ * Build the task prompt
281
+ */
282
+ buildTaskPrompt(task) {
283
+ const parts = [];
284
+ const systemPrompt = this.config.systemPromptPrefix ? `${this.config.systemPromptPrefix}
285
+
286
+ ${DEFAULT_SYSTEM_PROMPT}` : DEFAULT_SYSTEM_PROMPT;
287
+ parts.push(systemPrompt);
288
+ parts.push("");
289
+ parts.push("---");
290
+ parts.push("");
291
+ parts.push(`# Task: ${task.heading}`);
292
+ parts.push("");
293
+ if (task.artifactTitle) {
294
+ parts.push(`**Artifact:** ${task.artifactTitle}`);
295
+ }
296
+ if (task.priority) {
297
+ const priorityLabels = ["High", "Medium", "Low"];
298
+ parts.push(`**Priority:** ${priorityLabels[task.priority - 1] || "Medium"}`);
299
+ }
300
+ parts.push("");
301
+ parts.push("## Description");
302
+ parts.push(task.content || "No additional description provided.");
303
+ parts.push("");
304
+ if (task.context && Object.keys(task.context).length > 0) {
305
+ parts.push("## Additional Context");
306
+ parts.push("```json");
307
+ parts.push(JSON.stringify(task.context, null, 2));
308
+ parts.push("```");
309
+ parts.push("");
310
+ }
311
+ parts.push("## Instructions");
312
+ parts.push("Complete this task and provide your output below.");
313
+ return parts.join("\n");
314
+ }
315
+ /**
316
+ * Parse the response to extract output and summary
317
+ */
318
+ parseResponse(fullOutput, taskHeading) {
319
+ const summaryMatch = fullOutput.match(/SUMMARY:\s*(.+?)(?:\n|$)/i);
320
+ if (summaryMatch) {
321
+ const summary2 = summaryMatch[1].trim();
322
+ const content = fullOutput.replace(/SUMMARY:\s*.+?(?:\n|$)/i, "").trim();
323
+ return { content, summary: summary2 };
324
+ }
325
+ const lines = fullOutput.split("\n").filter((l) => l.trim());
326
+ const firstLine = lines[0] || "";
327
+ const summary = firstLine.length > 100 ? `${firstLine.substring(0, 97)}...` : firstLine || `Completed: ${taskHeading}`;
328
+ return { content: fullOutput, summary };
329
+ }
330
+ /**
331
+ * Test that Claude Code CLI is available and working
332
+ */
333
+ async testConnection() {
334
+ try {
335
+ const output = await this.runClaude('Say "connected" and nothing else.');
336
+ return output.toLowerCase().includes("connected");
337
+ } catch {
338
+ return false;
339
+ }
340
+ }
341
+ /**
342
+ * Check if Claude Code CLI is installed
343
+ */
344
+ async isInstalled() {
345
+ return new Promise((resolve) => {
346
+ const proc = spawn(this.config.claudePath || "claude", ["--version"], {
347
+ stdio: ["ignore", "pipe", "pipe"]
348
+ });
349
+ proc.on("close", (code) => {
350
+ resolve(code === 0);
351
+ });
352
+ proc.on("error", () => {
353
+ resolve(false);
354
+ });
355
+ });
356
+ }
357
+ };
358
+ function createExecutor(config) {
359
+ return new ClaudeExecutor(config);
360
+ }
361
+
362
+ // src/listener.ts
363
+ import EventSource from "eventsource";
364
+ var DEFAULT_BASE_URL2 = "https://artyfacts.dev/api/v1";
365
+ var EVENT_TYPES = [
366
+ "connected",
367
+ "heartbeat",
368
+ "task_assigned",
369
+ "task_unblocked",
370
+ "blocker_resolved",
371
+ "notification"
372
+ ];
373
+ var ArtyfactsListener = class {
374
+ config;
375
+ eventSource = null;
376
+ callbacks = /* @__PURE__ */ new Map();
377
+ allCallbacks = /* @__PURE__ */ new Set();
378
+ state = "disconnected";
379
+ reconnectAttempts = 0;
380
+ maxReconnectAttempts = 10;
381
+ reconnectDelay = 1e3;
382
+ constructor(config) {
383
+ if (!config.apiKey) {
384
+ throw new Error("API key is required");
385
+ }
386
+ if (!config.agentId) {
387
+ throw new Error("Agent ID is required");
388
+ }
389
+ this.config = {
390
+ ...config,
391
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL2
392
+ };
393
+ }
394
+ /**
395
+ * Get current connection state
396
+ */
397
+ get connectionState() {
398
+ return this.state;
399
+ }
400
+ /**
401
+ * Check if connected
402
+ */
403
+ get isConnected() {
404
+ return this.state === "connected";
405
+ }
406
+ /**
407
+ * Subscribe to all events
408
+ */
409
+ subscribe(callback) {
410
+ this.allCallbacks.add(callback);
411
+ return () => {
412
+ this.allCallbacks.delete(callback);
413
+ };
414
+ }
415
+ /**
416
+ * Subscribe to a specific event type
417
+ */
418
+ on(type, callback) {
419
+ if (!this.callbacks.has(type)) {
420
+ this.callbacks.set(type, /* @__PURE__ */ new Set());
421
+ }
422
+ this.callbacks.get(type).add(callback);
423
+ return () => {
424
+ const typeCallbacks = this.callbacks.get(type);
425
+ if (typeCallbacks) {
426
+ typeCallbacks.delete(callback);
427
+ if (typeCallbacks.size === 0) {
428
+ this.callbacks.delete(type);
429
+ }
430
+ }
431
+ };
432
+ }
433
+ /**
434
+ * Connect to the SSE stream
435
+ */
436
+ connect() {
437
+ if (this.eventSource) {
438
+ return;
439
+ }
440
+ this.setState("connecting");
441
+ const url = new URL(`${this.config.baseUrl}/events/stream`);
442
+ url.searchParams.set("apiKey", this.config.apiKey);
443
+ url.searchParams.set("agentId", this.config.agentId);
444
+ this.eventSource = new EventSource(url.toString(), {
445
+ headers: {
446
+ "Authorization": `Bearer ${this.config.apiKey}`
447
+ }
448
+ });
449
+ this.eventSource.onopen = () => {
450
+ this.reconnectAttempts = 0;
451
+ this.reconnectDelay = 1e3;
452
+ this.setState("connected");
453
+ };
454
+ this.eventSource.onmessage = (event) => {
455
+ this.handleMessage(event);
456
+ };
457
+ this.eventSource.onerror = (event) => {
458
+ this.handleError(event);
459
+ };
460
+ for (const eventType of EVENT_TYPES) {
461
+ this.eventSource.addEventListener(eventType, (event) => {
462
+ this.handleMessage(event, eventType);
463
+ });
464
+ }
465
+ }
466
+ /**
467
+ * Disconnect from the SSE stream
468
+ */
469
+ disconnect() {
470
+ if (this.eventSource) {
471
+ this.eventSource.close();
472
+ this.eventSource = null;
473
+ }
474
+ this.setState("disconnected");
475
+ }
476
+ /**
477
+ * Reconnect to the SSE stream
478
+ */
479
+ reconnect() {
480
+ this.disconnect();
481
+ this.connect();
482
+ }
483
+ /**
484
+ * Handle incoming SSE message
485
+ */
486
+ handleMessage(event, eventType) {
487
+ try {
488
+ const data = JSON.parse(event.data);
489
+ const artyfactsEvent = {
490
+ type: eventType || data.type || "unknown",
491
+ timestamp: data.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
492
+ data: data.data || data
493
+ };
494
+ const typeCallbacks = this.callbacks.get(artyfactsEvent.type);
495
+ if (typeCallbacks) {
496
+ for (const callback of typeCallbacks) {
497
+ this.safeCallCallback(callback, artyfactsEvent);
498
+ }
499
+ }
500
+ for (const callback of this.allCallbacks) {
501
+ this.safeCallCallback(callback, artyfactsEvent);
502
+ }
503
+ } catch (err) {
504
+ console.error("[Listener] Failed to parse SSE message:", event.data, err);
505
+ }
506
+ }
507
+ /**
508
+ * Safely call a callback, handling async and errors
509
+ */
510
+ async safeCallCallback(callback, event) {
511
+ try {
512
+ await callback(event);
513
+ } catch (err) {
514
+ console.error(`[Listener] Error in event callback for '${event.type}':`, err);
515
+ }
516
+ }
517
+ /**
518
+ * Handle SSE error
519
+ */
520
+ handleError(event) {
521
+ if (this.eventSource?.readyState === EventSource.CONNECTING) {
522
+ this.setState("reconnecting");
523
+ } else if (this.eventSource?.readyState === EventSource.CLOSED) {
524
+ this.setState("disconnected");
525
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
526
+ this.reconnectAttempts++;
527
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, 3e4);
528
+ console.log(
529
+ `[Listener] Connection lost, reconnecting in ${this.reconnectDelay / 1e3}s (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
530
+ );
531
+ setTimeout(() => {
532
+ if (this.state === "disconnected") {
533
+ this.connect();
534
+ }
535
+ }, this.reconnectDelay);
536
+ } else {
537
+ const error = new Error("Max reconnection attempts reached");
538
+ this.config.onError?.(error);
539
+ }
540
+ }
541
+ }
542
+ /**
543
+ * Update connection state
544
+ */
545
+ setState(state) {
546
+ if (this.state !== state) {
547
+ this.state = state;
548
+ this.config.onStateChange?.(state);
549
+ }
550
+ }
551
+ };
552
+ function createListener(config) {
553
+ return new ArtyfactsListener(config);
554
+ }
555
+
556
+ export {
557
+ loadCredentials,
558
+ saveCredentials,
559
+ clearCredentials,
560
+ runDeviceAuth,
561
+ promptForApiKey,
562
+ getCredentials,
563
+ ClaudeExecutor,
564
+ createExecutor,
565
+ ArtyfactsListener,
566
+ createListener
567
+ };
package/dist/cli.js CHANGED
@@ -120,10 +120,10 @@ async function requestDeviceCode(baseUrl) {
120
120
  }
121
121
  const data = await response.json();
122
122
  return {
123
- deviceCode: data.device_code,
124
- userCode: data.user_code,
125
- verificationUri: data.verification_uri || `https://artyfacts.dev/auth/device`,
126
- expiresIn: data.expires_in || 600,
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
127
  interval: data.interval || 5
128
128
  };
129
129
  }
@@ -637,6 +637,88 @@ program.command("status").description("Check authentication and connection statu
637
637
  console.log(" Install: npm install -g @anthropic-ai/claude-code");
638
638
  }
639
639
  });
640
+ async function checkAndClaimTasks(baseUrl, apiKey, agentId, activeTasks, executor, dryRun) {
641
+ try {
642
+ const response = await fetch(`${baseUrl}/tasks/queue?limit=5`, {
643
+ headers: {
644
+ "Authorization": `Bearer ${apiKey}`
645
+ }
646
+ });
647
+ if (!response.ok) {
648
+ console.log("\u26A0\uFE0F Could not fetch task queue");
649
+ return;
650
+ }
651
+ const data = await response.json();
652
+ const tasks = data.tasks || [];
653
+ if (tasks.length === 0) {
654
+ console.log("\u{1F4ED} No claimable tasks in queue");
655
+ return;
656
+ }
657
+ console.log(`\u{1F4EC} Found ${tasks.length} claimable task(s)`);
658
+ for (const task of tasks) {
659
+ if (activeTasks.has(task.section_id)) {
660
+ continue;
661
+ }
662
+ console.log(`
663
+ [Claiming] ${task.heading}`);
664
+ const claimResponse = await fetch(`${baseUrl}/tasks/${task.section_id}/claim`, {
665
+ method: "POST",
666
+ headers: {
667
+ "Authorization": `Bearer ${apiKey}`,
668
+ "Content-Type": "application/json"
669
+ }
670
+ });
671
+ if (!claimResponse.ok) {
672
+ const error = await claimResponse.json().catch(() => ({}));
673
+ console.log(` \u26A0\uFE0F Could not claim: ${error.error || "Unknown error"}`);
674
+ continue;
675
+ }
676
+ activeTasks.add(task.section_id);
677
+ console.log(" \u2713 Claimed!");
678
+ if (dryRun) {
679
+ console.log(" \u{1F4CB} Dry run - not executing");
680
+ activeTasks.delete(task.section_id);
681
+ continue;
682
+ }
683
+ console.log(" \u2192 Executing with Claude...");
684
+ try {
685
+ const result = await executor.execute({
686
+ taskId: task.section_id,
687
+ heading: task.heading,
688
+ content: task.content,
689
+ artifactId: task.artifact_id,
690
+ artifactTitle: task.artifact_title,
691
+ priority: task.priority
692
+ });
693
+ if (result.success) {
694
+ await completeTask({
695
+ baseUrl,
696
+ apiKey,
697
+ taskId: task.section_id,
698
+ output: result.output,
699
+ summary: result.summary
700
+ });
701
+ console.log(` \u2192 \u2705 Completed! ${result.summary}`);
702
+ } else {
703
+ console.log(` \u2192 \u274C Failed: ${result.error}`);
704
+ await blockTask({
705
+ baseUrl,
706
+ apiKey,
707
+ taskId: task.section_id,
708
+ reason: result.error || "Execution failed"
709
+ });
710
+ }
711
+ } catch (error) {
712
+ console.error(` \u2192 \u274C Error:`, error instanceof Error ? error.message : error);
713
+ } finally {
714
+ activeTasks.delete(task.section_id);
715
+ }
716
+ break;
717
+ }
718
+ } catch (error) {
719
+ console.error("Error checking task queue:", error instanceof Error ? error.message : error);
720
+ }
721
+ }
640
722
  async function runAgent(options) {
641
723
  console.log("\u{1F517} Connecting to Artyfacts...");
642
724
  let credentials;
@@ -677,6 +759,7 @@ async function runAgent(options) {
677
759
  case "connected":
678
760
  console.log(`\u2705 Connected as ${credentials.agentId}`);
679
761
  console.log("\u{1F442} Listening for tasks...\n");
762
+ checkAndClaimTasks(options.baseUrl, credentials.apiKey, credentials.agentId, activeTasks, executor, options.dryRun);
680
763
  break;
681
764
  case "reconnecting":
682
765
  console.log("\u{1F504} Reconnecting...");
package/dist/cli.mjs CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  getCredentials,
7
7
  loadCredentials,
8
8
  promptForApiKey
9
- } from "./chunk-365PEWTO.mjs";
9
+ } from "./chunk-4RRGLYN6.mjs";
10
10
 
11
11
  // src/cli.ts
12
12
  import { Command } from "commander";
@@ -65,6 +65,88 @@ program.command("status").description("Check authentication and connection statu
65
65
  console.log(" Install: npm install -g @anthropic-ai/claude-code");
66
66
  }
67
67
  });
68
+ async function checkAndClaimTasks(baseUrl, apiKey, agentId, activeTasks, executor, dryRun) {
69
+ try {
70
+ const response = await fetch(`${baseUrl}/tasks/queue?limit=5`, {
71
+ headers: {
72
+ "Authorization": `Bearer ${apiKey}`
73
+ }
74
+ });
75
+ if (!response.ok) {
76
+ console.log("\u26A0\uFE0F Could not fetch task queue");
77
+ return;
78
+ }
79
+ const data = await response.json();
80
+ const tasks = data.tasks || [];
81
+ if (tasks.length === 0) {
82
+ console.log("\u{1F4ED} No claimable tasks in queue");
83
+ return;
84
+ }
85
+ console.log(`\u{1F4EC} Found ${tasks.length} claimable task(s)`);
86
+ for (const task of tasks) {
87
+ if (activeTasks.has(task.section_id)) {
88
+ continue;
89
+ }
90
+ console.log(`
91
+ [Claiming] ${task.heading}`);
92
+ const claimResponse = await fetch(`${baseUrl}/tasks/${task.section_id}/claim`, {
93
+ method: "POST",
94
+ headers: {
95
+ "Authorization": `Bearer ${apiKey}`,
96
+ "Content-Type": "application/json"
97
+ }
98
+ });
99
+ if (!claimResponse.ok) {
100
+ const error = await claimResponse.json().catch(() => ({}));
101
+ console.log(` \u26A0\uFE0F Could not claim: ${error.error || "Unknown error"}`);
102
+ continue;
103
+ }
104
+ activeTasks.add(task.section_id);
105
+ console.log(" \u2713 Claimed!");
106
+ if (dryRun) {
107
+ console.log(" \u{1F4CB} Dry run - not executing");
108
+ activeTasks.delete(task.section_id);
109
+ continue;
110
+ }
111
+ console.log(" \u2192 Executing with Claude...");
112
+ try {
113
+ const result = await executor.execute({
114
+ taskId: task.section_id,
115
+ heading: task.heading,
116
+ content: task.content,
117
+ artifactId: task.artifact_id,
118
+ artifactTitle: task.artifact_title,
119
+ priority: task.priority
120
+ });
121
+ if (result.success) {
122
+ await completeTask({
123
+ baseUrl,
124
+ apiKey,
125
+ taskId: task.section_id,
126
+ output: result.output,
127
+ summary: result.summary
128
+ });
129
+ console.log(` \u2192 \u2705 Completed! ${result.summary}`);
130
+ } else {
131
+ console.log(` \u2192 \u274C Failed: ${result.error}`);
132
+ await blockTask({
133
+ baseUrl,
134
+ apiKey,
135
+ taskId: task.section_id,
136
+ reason: result.error || "Execution failed"
137
+ });
138
+ }
139
+ } catch (error) {
140
+ console.error(` \u2192 \u274C Error:`, error instanceof Error ? error.message : error);
141
+ } finally {
142
+ activeTasks.delete(task.section_id);
143
+ }
144
+ break;
145
+ }
146
+ } catch (error) {
147
+ console.error("Error checking task queue:", error instanceof Error ? error.message : error);
148
+ }
149
+ }
68
150
  async function runAgent(options) {
69
151
  console.log("\u{1F517} Connecting to Artyfacts...");
70
152
  let credentials;
@@ -105,6 +187,7 @@ async function runAgent(options) {
105
187
  case "connected":
106
188
  console.log(`\u2705 Connected as ${credentials.agentId}`);
107
189
  console.log("\u{1F442} Listening for tasks...\n");
190
+ checkAndClaimTasks(options.baseUrl, credentials.apiKey, credentials.agentId, activeTasks, executor, options.dryRun);
108
191
  break;
109
192
  case "reconnecting":
110
193
  console.log("\u{1F504} Reconnecting...");
package/dist/index.js CHANGED
@@ -137,10 +137,10 @@ async function requestDeviceCode(baseUrl) {
137
137
  }
138
138
  const data = await response.json();
139
139
  return {
140
- deviceCode: data.device_code,
141
- userCode: data.user_code,
142
- verificationUri: data.verification_uri || `https://artyfacts.dev/auth/device`,
143
- expiresIn: data.expires_in || 600,
140
+ deviceCode: data.deviceCode || data.device_code || "",
141
+ userCode: data.userCode || data.user_code || "",
142
+ verificationUri: data.verificationUri || data.verification_uri || `https://artyfacts.dev/auth/device`,
143
+ expiresIn: data.expiresIn || data.expires_in || 600,
144
144
  interval: data.interval || 5
145
145
  };
146
146
  }
package/dist/index.mjs CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  promptForApiKey,
10
10
  runDeviceAuth,
11
11
  saveCredentials
12
- } from "./chunk-365PEWTO.mjs";
12
+ } from "./chunk-4RRGLYN6.mjs";
13
13
  export {
14
14
  ArtyfactsListener,
15
15
  ClaudeExecutor,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artyfacts/claude",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
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/auth.ts CHANGED
@@ -179,18 +179,24 @@ async function requestDeviceCode(baseUrl: string): Promise<DeviceAuthResponse> {
179
179
  }
180
180
 
181
181
  const data = await response.json() as {
182
- device_code: string;
183
- user_code: string;
182
+ // API returns camelCase
183
+ deviceCode?: string;
184
+ userCode?: string;
185
+ verificationUri?: string;
186
+ expiresIn?: number;
187
+ interval?: number;
188
+ // Also accept snake_case for compatibility
189
+ device_code?: string;
190
+ user_code?: string;
184
191
  verification_uri?: string;
185
192
  expires_in?: number;
186
- interval?: number;
187
193
  };
188
194
 
189
195
  return {
190
- deviceCode: data.device_code,
191
- userCode: data.user_code,
192
- verificationUri: data.verification_uri || `https://artyfacts.dev/auth/device`,
193
- expiresIn: data.expires_in || 600,
196
+ deviceCode: data.deviceCode || data.device_code || '',
197
+ userCode: data.userCode || data.user_code || '',
198
+ verificationUri: data.verificationUri || data.verification_uri || `https://artyfacts.dev/auth/device`,
199
+ expiresIn: data.expiresIn || data.expires_in || 600,
194
200
  interval: data.interval || 5,
195
201
  };
196
202
  }
package/src/cli.ts CHANGED
@@ -126,6 +126,130 @@ program
126
126
  }
127
127
  });
128
128
 
129
+ // ============================================================================
130
+ // Task Queue Polling
131
+ // ============================================================================
132
+
133
+ /**
134
+ * Check for claimable tasks and process them
135
+ */
136
+ async function checkAndClaimTasks(
137
+ baseUrl: string,
138
+ apiKey: string,
139
+ agentId: string,
140
+ activeTasks: Set<string>,
141
+ executor: ClaudeExecutor,
142
+ dryRun: boolean
143
+ ): Promise<void> {
144
+ try {
145
+ // Fetch claimable tasks from queue
146
+ const response = await fetch(`${baseUrl}/tasks/queue?limit=5`, {
147
+ headers: {
148
+ 'Authorization': `Bearer ${apiKey}`,
149
+ },
150
+ });
151
+
152
+ if (!response.ok) {
153
+ console.log('⚠️ Could not fetch task queue');
154
+ return;
155
+ }
156
+
157
+ const data = await response.json() as { tasks?: Array<{
158
+ id: string;
159
+ section_id: string;
160
+ heading: string;
161
+ content: string;
162
+ artifact_id: string;
163
+ artifact_title?: string;
164
+ priority?: number;
165
+ }> };
166
+
167
+ const tasks = data.tasks || [];
168
+
169
+ if (tasks.length === 0) {
170
+ console.log('📭 No claimable tasks in queue');
171
+ return;
172
+ }
173
+
174
+ console.log(`📬 Found ${tasks.length} claimable task(s)`);
175
+
176
+ // Process first available task
177
+ for (const task of tasks) {
178
+ if (activeTasks.has(task.section_id)) {
179
+ continue; // Already processing
180
+ }
181
+
182
+ // Try to claim the task
183
+ console.log(`\n[Claiming] ${task.heading}`);
184
+
185
+ const claimResponse = await fetch(`${baseUrl}/tasks/${task.section_id}/claim`, {
186
+ method: 'POST',
187
+ headers: {
188
+ 'Authorization': `Bearer ${apiKey}`,
189
+ 'Content-Type': 'application/json',
190
+ },
191
+ });
192
+
193
+ if (!claimResponse.ok) {
194
+ const error = await claimResponse.json().catch(() => ({})) as { error?: string };
195
+ console.log(` ⚠️ Could not claim: ${error.error || 'Unknown error'}`);
196
+ continue;
197
+ }
198
+
199
+ // Successfully claimed - now execute
200
+ activeTasks.add(task.section_id);
201
+ console.log(' ✓ Claimed!');
202
+
203
+ if (dryRun) {
204
+ console.log(' 📋 Dry run - not executing');
205
+ activeTasks.delete(task.section_id);
206
+ continue;
207
+ }
208
+
209
+ console.log(' → Executing with Claude...');
210
+
211
+ try {
212
+ const result = await executor.execute({
213
+ taskId: task.section_id,
214
+ heading: task.heading,
215
+ content: task.content,
216
+ artifactId: task.artifact_id,
217
+ artifactTitle: task.artifact_title,
218
+ priority: task.priority,
219
+ });
220
+
221
+ if (result.success) {
222
+ await completeTask({
223
+ baseUrl,
224
+ apiKey,
225
+ taskId: task.section_id,
226
+ output: result.output,
227
+ summary: result.summary,
228
+ });
229
+ console.log(` → ✅ Completed! ${result.summary}`);
230
+ } else {
231
+ console.log(` → ❌ Failed: ${result.error}`);
232
+ await blockTask({
233
+ baseUrl,
234
+ apiKey,
235
+ taskId: task.section_id,
236
+ reason: result.error || 'Execution failed',
237
+ });
238
+ }
239
+ } catch (error) {
240
+ console.error(` → ❌ Error:`, error instanceof Error ? error.message : error);
241
+ } finally {
242
+ activeTasks.delete(task.section_id);
243
+ }
244
+
245
+ // Only process one task at a time for now
246
+ break;
247
+ }
248
+ } catch (error) {
249
+ console.error('Error checking task queue:', error instanceof Error ? error.message : error);
250
+ }
251
+ }
252
+
129
253
  // ============================================================================
130
254
  // Main Agent Loop
131
255
  // ============================================================================
@@ -183,6 +307,9 @@ async function runAgent(options: {
183
307
  case 'connected':
184
308
  console.log(`✅ Connected as ${credentials.agentId}`);
185
309
  console.log('👂 Listening for tasks...\n');
310
+
311
+ // Check for existing claimable tasks on connect
312
+ checkAndClaimTasks(options.baseUrl, credentials.apiKey, credentials.agentId, activeTasks, executor!, options.dryRun);
186
313
  break;
187
314
  case 'reconnecting':
188
315
  console.log('🔄 Reconnecting...');