@artyfacts/claude 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,626 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ArtyfactsClient: () => ArtyfactsClient,
34
+ ClaudeAdapter: () => ClaudeAdapter,
35
+ ClaudeRunner: () => ClaudeRunner,
36
+ DeviceAuth: () => DeviceAuth
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // src/adapter.ts
41
+ var import_events2 = require("events");
42
+
43
+ // src/artyfacts-client.ts
44
+ var ArtyfactsClient = class {
45
+ config;
46
+ constructor(config) {
47
+ this.config = {
48
+ apiKey: config.apiKey,
49
+ baseUrl: config.baseUrl ?? "https://artyfacts.dev/api/v1",
50
+ agentId: config.agentId ?? "claude-agent"
51
+ };
52
+ }
53
+ async fetch(path2, options = {}) {
54
+ const url = `${this.config.baseUrl}${path2}`;
55
+ const response = await fetch(url, {
56
+ ...options,
57
+ headers: {
58
+ "Content-Type": "application/json",
59
+ "Authorization": `Bearer ${this.config.apiKey}`,
60
+ ...options.headers
61
+ }
62
+ });
63
+ if (!response.ok) {
64
+ const error = await response.text();
65
+ throw new Error(`API error (${response.status}): ${error}`);
66
+ }
67
+ return response.json();
68
+ }
69
+ /**
70
+ * Get claimable tasks from the queue
71
+ */
72
+ async getTaskQueue(options) {
73
+ const params = new URLSearchParams();
74
+ if (options?.limit) params.set("limit", options.limit.toString());
75
+ const result = await this.fetch(`/tasks/queue?${params}`);
76
+ return result.tasks;
77
+ }
78
+ /**
79
+ * Claim a task
80
+ */
81
+ async claimTask(taskId) {
82
+ return this.fetch(`/tasks/${taskId}/claim`, {
83
+ method: "POST",
84
+ body: JSON.stringify({ agent_id: this.config.agentId })
85
+ });
86
+ }
87
+ /**
88
+ * Complete a task
89
+ */
90
+ async completeTask(taskId, options) {
91
+ return this.fetch(`/tasks/${taskId}/complete`, {
92
+ method: "POST",
93
+ body: JSON.stringify({
94
+ agent_id: this.config.agentId,
95
+ output_url: options?.outputUrl,
96
+ summary: options?.summary
97
+ })
98
+ });
99
+ }
100
+ /**
101
+ * Report task as blocked
102
+ */
103
+ async blockTask(taskId, reason) {
104
+ await this.fetch(`/tasks/${taskId}/block`, {
105
+ method: "POST",
106
+ body: JSON.stringify({
107
+ agent_id: this.config.agentId,
108
+ reason
109
+ })
110
+ });
111
+ }
112
+ /**
113
+ * Get current user/org info
114
+ */
115
+ async getMe() {
116
+ return this.fetch("/me");
117
+ }
118
+ };
119
+
120
+ // src/claude-runner.ts
121
+ var import_child_process = require("child_process");
122
+ var import_events = require("events");
123
+ var ClaudeRunner = class extends import_events.EventEmitter {
124
+ config;
125
+ runningTasks = /* @__PURE__ */ new Map();
126
+ constructor(config = {}) {
127
+ super();
128
+ this.config = {
129
+ claudePath: config.claudePath ?? "claude",
130
+ model: config.model ?? "sonnet",
131
+ cwd: config.cwd ?? process.cwd(),
132
+ timeoutMs: config.timeoutMs ?? 5 * 60 * 1e3
133
+ // 5 minutes
134
+ };
135
+ }
136
+ /**
137
+ * Check if Claude Code is installed and authenticated
138
+ */
139
+ async checkInstalled() {
140
+ try {
141
+ const result = await this.runCommand(["--version"]);
142
+ const version = result.output.trim();
143
+ const authCheck = await this.runCommand(["-p", 'Say "ok"', "--print", "--max-turns", "1"]);
144
+ const authenticated = authCheck.success && !authCheck.output.includes("not authenticated");
145
+ return { installed: true, authenticated, version };
146
+ } catch (error) {
147
+ return {
148
+ installed: false,
149
+ authenticated: false,
150
+ error: error instanceof Error ? error.message : "Unknown error"
151
+ };
152
+ }
153
+ }
154
+ /**
155
+ * Run a task with Claude Code
156
+ */
157
+ async runTask(taskId, prompt, options) {
158
+ const startedAt = /* @__PURE__ */ new Date();
159
+ const model = options?.model ?? this.config.model;
160
+ const cwd = options?.cwd ?? this.config.cwd;
161
+ const timeoutMs = options?.timeoutMs ?? this.config.timeoutMs;
162
+ const args = [
163
+ "-p",
164
+ prompt,
165
+ "--print",
166
+ "--output-format",
167
+ "text",
168
+ "--model",
169
+ model
170
+ ];
171
+ this.emit("task:start", { taskId, prompt, model });
172
+ const promise = new Promise((resolve) => {
173
+ let output = "";
174
+ let error = "";
175
+ const proc = (0, import_child_process.spawn)(this.config.claudePath, args, {
176
+ cwd,
177
+ stdio: ["pipe", "pipe", "pipe"],
178
+ env: { ...process.env }
179
+ });
180
+ const timeout = setTimeout(() => {
181
+ proc.kill("SIGTERM");
182
+ resolve({
183
+ success: false,
184
+ output,
185
+ error: "Task timed out",
186
+ exitCode: null,
187
+ durationMs: Date.now() - startedAt.getTime()
188
+ });
189
+ }, timeoutMs);
190
+ proc.stdout?.on("data", (data) => {
191
+ output += data.toString();
192
+ this.emit("task:output", { taskId, chunk: data.toString() });
193
+ });
194
+ proc.stderr?.on("data", (data) => {
195
+ error += data.toString();
196
+ });
197
+ proc.on("close", (code) => {
198
+ clearTimeout(timeout);
199
+ this.runningTasks.delete(taskId);
200
+ const result = {
201
+ success: code === 0,
202
+ output: output.trim(),
203
+ error: error.trim() || void 0,
204
+ exitCode: code,
205
+ durationMs: Date.now() - startedAt.getTime()
206
+ };
207
+ this.emit("task:complete", { taskId, result });
208
+ resolve(result);
209
+ });
210
+ proc.on("error", (err) => {
211
+ clearTimeout(timeout);
212
+ this.runningTasks.delete(taskId);
213
+ resolve({
214
+ success: false,
215
+ output: "",
216
+ error: err.message,
217
+ exitCode: null,
218
+ durationMs: Date.now() - startedAt.getTime()
219
+ });
220
+ });
221
+ this.runningTasks.set(taskId, {
222
+ taskId,
223
+ process: proc,
224
+ startedAt,
225
+ promise
226
+ });
227
+ });
228
+ return promise;
229
+ }
230
+ /**
231
+ * Run a raw claude command
232
+ */
233
+ runCommand(args) {
234
+ return new Promise((resolve) => {
235
+ const startedAt = Date.now();
236
+ let output = "";
237
+ let error = "";
238
+ const proc = (0, import_child_process.spawn)(this.config.claudePath, args, {
239
+ stdio: ["pipe", "pipe", "pipe"]
240
+ });
241
+ proc.stdout?.on("data", (data) => {
242
+ output += data.toString();
243
+ });
244
+ proc.stderr?.on("data", (data) => {
245
+ error += data.toString();
246
+ });
247
+ proc.on("close", (code) => {
248
+ resolve({
249
+ success: code === 0,
250
+ output: output.trim(),
251
+ error: error.trim() || void 0,
252
+ exitCode: code,
253
+ durationMs: Date.now() - startedAt
254
+ });
255
+ });
256
+ proc.on("error", (err) => {
257
+ resolve({
258
+ success: false,
259
+ output: "",
260
+ error: err.message,
261
+ exitCode: null,
262
+ durationMs: Date.now() - startedAt
263
+ });
264
+ });
265
+ });
266
+ }
267
+ /**
268
+ * Cancel a running task
269
+ */
270
+ cancelTask(taskId) {
271
+ const task = this.runningTasks.get(taskId);
272
+ if (task) {
273
+ task.process.kill("SIGTERM");
274
+ this.runningTasks.delete(taskId);
275
+ this.emit("task:cancelled", { taskId });
276
+ return true;
277
+ }
278
+ return false;
279
+ }
280
+ /**
281
+ * Get count of running tasks
282
+ */
283
+ getRunningCount() {
284
+ return this.runningTasks.size;
285
+ }
286
+ /**
287
+ * Cancel all running tasks
288
+ */
289
+ cancelAll() {
290
+ for (const [taskId] of this.runningTasks) {
291
+ this.cancelTask(taskId);
292
+ }
293
+ }
294
+ };
295
+
296
+ // src/adapter.ts
297
+ var ClaudeAdapter = class extends import_events2.EventEmitter {
298
+ config;
299
+ client;
300
+ runner;
301
+ running = false;
302
+ pollTimer = null;
303
+ activeTasks = /* @__PURE__ */ new Set();
304
+ constructor(config) {
305
+ super();
306
+ this.config = {
307
+ apiKey: config.apiKey,
308
+ baseUrl: config.baseUrl ?? "https://artyfacts.dev/api/v1",
309
+ agentId: config.agentId ?? "claude-agent",
310
+ pollIntervalMs: config.pollIntervalMs ?? 3e4,
311
+ maxConcurrent: config.maxConcurrent ?? 1,
312
+ model: config.model ?? "sonnet",
313
+ cwd: config.cwd ?? process.cwd()
314
+ };
315
+ this.client = new ArtyfactsClient({
316
+ apiKey: this.config.apiKey,
317
+ baseUrl: this.config.baseUrl,
318
+ agentId: this.config.agentId
319
+ });
320
+ this.runner = new ClaudeRunner({
321
+ model: this.config.model,
322
+ cwd: this.config.cwd
323
+ });
324
+ this.runner.on("task:output", (data) => this.emit("task:output", data));
325
+ }
326
+ /**
327
+ * Check if Claude Code is ready
328
+ */
329
+ async checkReady() {
330
+ const check = await this.runner.checkInstalled();
331
+ if (!check.installed) {
332
+ return {
333
+ ready: false,
334
+ error: "Claude Code is not installed. Run: npm install -g @anthropic-ai/claude-code"
335
+ };
336
+ }
337
+ if (!check.authenticated) {
338
+ return {
339
+ ready: false,
340
+ error: "Claude Code is not authenticated. Run: claude login"
341
+ };
342
+ }
343
+ return { ready: true };
344
+ }
345
+ /**
346
+ * Start the adapter
347
+ */
348
+ async start() {
349
+ if (this.running) return;
350
+ const { ready, error } = await this.checkReady();
351
+ if (!ready) {
352
+ throw new Error(error);
353
+ }
354
+ this.running = true;
355
+ this.emit("started");
356
+ await this.poll();
357
+ this.pollTimer = setInterval(() => {
358
+ this.poll().catch((err) => this.emit("error", err));
359
+ }, this.config.pollIntervalMs);
360
+ }
361
+ /**
362
+ * Stop the adapter
363
+ */
364
+ async stop() {
365
+ if (!this.running) return;
366
+ this.running = false;
367
+ if (this.pollTimer) {
368
+ clearInterval(this.pollTimer);
369
+ this.pollTimer = null;
370
+ }
371
+ this.runner.cancelAll();
372
+ this.emit("stopped");
373
+ }
374
+ /**
375
+ * Poll for and execute tasks
376
+ */
377
+ async poll() {
378
+ try {
379
+ const available = this.config.maxConcurrent - this.activeTasks.size;
380
+ if (available <= 0) return;
381
+ const tasks = await this.client.getTaskQueue({ limit: available });
382
+ this.emit("poll", tasks);
383
+ for (const task of tasks) {
384
+ if (this.activeTasks.has(task.id)) continue;
385
+ if (this.activeTasks.size >= this.config.maxConcurrent) break;
386
+ await this.executeTask(task);
387
+ }
388
+ } catch (error) {
389
+ this.emit("error", error instanceof Error ? error : new Error(String(error)));
390
+ }
391
+ }
392
+ /**
393
+ * Execute a single task
394
+ */
395
+ async executeTask(task) {
396
+ try {
397
+ const claim = await this.client.claimTask(task.id);
398
+ if (!claim.success) return;
399
+ this.activeTasks.add(task.id);
400
+ this.emit("task:claimed", task);
401
+ const prompt = this.buildPrompt(task);
402
+ this.emit("task:running", task);
403
+ const result = await this.runner.runTask(task.id, prompt, {
404
+ cwd: this.config.cwd
405
+ });
406
+ this.activeTasks.delete(task.id);
407
+ if (result.success) {
408
+ await this.client.completeTask(task.id, {
409
+ summary: this.extractSummary(result.output)
410
+ });
411
+ this.emit("task:completed", task, result);
412
+ } else {
413
+ await this.client.blockTask(task.id, result.error ?? "Task failed");
414
+ this.emit("task:failed", task, result.error ?? "Unknown error");
415
+ }
416
+ } catch (error) {
417
+ this.activeTasks.delete(task.id);
418
+ const message = error instanceof Error ? error.message : String(error);
419
+ this.emit("task:failed", task, message);
420
+ }
421
+ }
422
+ /**
423
+ * Build a prompt for Claude from the task
424
+ */
425
+ buildPrompt(task) {
426
+ return `You are working on a task from Artyfacts.
427
+
428
+ ## Task: ${task.heading}
429
+
430
+ ## Context
431
+ - Artifact: ${task.artifactTitle}
432
+ - URL: ${task.artifactUrl}
433
+ - Priority: ${task.priority === 1 ? "High" : task.priority === 2 ? "Medium" : "Low"}
434
+
435
+ ## Details
436
+ ${task.content}
437
+
438
+ ## Instructions
439
+ Complete this task to the best of your ability. When finished, provide a brief summary of what you accomplished.
440
+
441
+ If you cannot complete the task, explain why clearly.`;
442
+ }
443
+ /**
444
+ * Extract a summary from Claude's output
445
+ */
446
+ extractSummary(output) {
447
+ const maxLen = 500;
448
+ if (output.length <= maxLen) return output;
449
+ return "..." + output.slice(-maxLen);
450
+ }
451
+ /**
452
+ * Check if running
453
+ */
454
+ isRunning() {
455
+ return this.running;
456
+ }
457
+ /**
458
+ * Get stats
459
+ */
460
+ getStats() {
461
+ return {
462
+ running: this.running,
463
+ activeTasks: this.activeTasks.size,
464
+ maxConcurrent: this.config.maxConcurrent
465
+ };
466
+ }
467
+ };
468
+
469
+ // src/auth.ts
470
+ var import_child_process2 = require("child_process");
471
+ var fs = __toESM(require("fs"));
472
+ var path = __toESM(require("path"));
473
+ var os = __toESM(require("os"));
474
+ var DeviceAuth = class {
475
+ baseUrl;
476
+ credentialsPath;
477
+ constructor(options) {
478
+ this.baseUrl = options?.baseUrl ?? "https://artyfacts.dev";
479
+ this.credentialsPath = path.join(os.homedir(), ".artyfacts", "credentials.json");
480
+ }
481
+ /**
482
+ * Check if we have stored credentials
483
+ */
484
+ hasCredentials() {
485
+ return fs.existsSync(this.credentialsPath);
486
+ }
487
+ /**
488
+ * Get stored credentials
489
+ */
490
+ getCredentials() {
491
+ if (!this.hasCredentials()) return null;
492
+ try {
493
+ const data = fs.readFileSync(this.credentialsPath, "utf-8");
494
+ const creds = JSON.parse(data);
495
+ if (creds.expiresAt && Date.now() > creds.expiresAt) {
496
+ return null;
497
+ }
498
+ return creds;
499
+ } catch {
500
+ return null;
501
+ }
502
+ }
503
+ /**
504
+ * Get access token (for API calls)
505
+ */
506
+ getAccessToken() {
507
+ const creds = this.getCredentials();
508
+ return creds?.accessToken ?? null;
509
+ }
510
+ /**
511
+ * Clear stored credentials (logout)
512
+ */
513
+ logout() {
514
+ if (fs.existsSync(this.credentialsPath)) {
515
+ fs.unlinkSync(this.credentialsPath);
516
+ }
517
+ }
518
+ /**
519
+ * Start device authorization flow
520
+ */
521
+ async startDeviceFlow() {
522
+ const response = await fetch(`${this.baseUrl}/api/v1/auth/device`, {
523
+ method: "POST",
524
+ headers: { "Content-Type": "application/json" }
525
+ });
526
+ if (!response.ok) {
527
+ throw new Error(`Failed to start device flow: ${response.status}`);
528
+ }
529
+ return response.json();
530
+ }
531
+ /**
532
+ * Poll for token after user authorizes
533
+ */
534
+ async pollForToken(deviceCode, interval, expiresIn) {
535
+ const startTime = Date.now();
536
+ const expiresAt = startTime + expiresIn * 1e3;
537
+ while (Date.now() < expiresAt) {
538
+ await this.sleep(interval * 1e3);
539
+ const response = await fetch(`${this.baseUrl}/api/v1/auth/device/token`, {
540
+ method: "POST",
541
+ headers: { "Content-Type": "application/json" },
542
+ body: JSON.stringify({ deviceCode })
543
+ });
544
+ if (response.ok) {
545
+ return response.json();
546
+ }
547
+ const data = await response.json().catch(() => ({}));
548
+ if (data.error === "authorization_pending") {
549
+ continue;
550
+ }
551
+ if (data.error === "slow_down") {
552
+ interval += 5;
553
+ continue;
554
+ }
555
+ if (data.error === "expired_token") {
556
+ throw new Error("Authorization expired. Please try again.");
557
+ }
558
+ if (data.error === "access_denied") {
559
+ throw new Error("Authorization denied by user.");
560
+ }
561
+ throw new Error(data.error ?? "Unknown error during authorization");
562
+ }
563
+ throw new Error("Authorization timed out. Please try again.");
564
+ }
565
+ /**
566
+ * Save credentials to disk
567
+ */
568
+ saveCredentials(token) {
569
+ const dir = path.dirname(this.credentialsPath);
570
+ if (!fs.existsSync(dir)) {
571
+ fs.mkdirSync(dir, { recursive: true });
572
+ }
573
+ const creds = {
574
+ ...token,
575
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
576
+ };
577
+ fs.writeFileSync(this.credentialsPath, JSON.stringify(creds, null, 2), {
578
+ mode: 384
579
+ // Only user can read/write
580
+ });
581
+ }
582
+ /**
583
+ * Open URL in browser
584
+ */
585
+ openBrowser(url) {
586
+ const platform = process.platform;
587
+ try {
588
+ if (platform === "darwin") {
589
+ (0, import_child_process2.execSync)(`open "${url}"`);
590
+ } else if (platform === "win32") {
591
+ (0, import_child_process2.execSync)(`start "" "${url}"`);
592
+ } else {
593
+ (0, import_child_process2.execSync)(`xdg-open "${url}"`);
594
+ }
595
+ } catch {
596
+ console.log(`Please open this URL in your browser: ${url}`);
597
+ }
598
+ }
599
+ /**
600
+ * Full login flow
601
+ */
602
+ async login(options) {
603
+ const deviceCode = await this.startDeviceFlow();
604
+ options?.onDeviceCode?.(deviceCode);
605
+ this.openBrowser(deviceCode.verificationUri);
606
+ options?.onWaiting?.();
607
+ const token = await this.pollForToken(
608
+ deviceCode.deviceCode,
609
+ deviceCode.interval,
610
+ deviceCode.expiresIn
611
+ );
612
+ this.saveCredentials(token);
613
+ return token;
614
+ }
615
+ sleep(ms) {
616
+ return new Promise((resolve) => setTimeout(resolve, ms));
617
+ }
618
+ };
619
+ // Annotate the CommonJS export names for ESM import in node:
620
+ 0 && (module.exports = {
621
+ ArtyfactsClient,
622
+ ClaudeAdapter,
623
+ ClaudeRunner,
624
+ DeviceAuth
625
+ });
626
+ //# sourceMappingURL=index.js.map