@artyfacts/claude 1.3.22 → 1.3.24

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,1034 @@
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
+ "mcp_connect_request",
595
+ "connection_status"
596
+ ];
597
+ var ArtyfactsListener = class {
598
+ config;
599
+ eventSource = null;
600
+ callbacks = /* @__PURE__ */ new Map();
601
+ allCallbacks = /* @__PURE__ */ new Set();
602
+ state = "disconnected";
603
+ reconnectAttempts = 0;
604
+ maxReconnectAttempts = 10;
605
+ reconnectDelay = 1e3;
606
+ constructor(config) {
607
+ if (!config.apiKey) {
608
+ throw new Error("API key is required");
609
+ }
610
+ if (!config.agentId) {
611
+ throw new Error("Agent ID is required");
612
+ }
613
+ this.config = {
614
+ ...config,
615
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL2
616
+ };
617
+ }
618
+ /**
619
+ * Get current connection state
620
+ */
621
+ get connectionState() {
622
+ return this.state;
623
+ }
624
+ /**
625
+ * Check if connected
626
+ */
627
+ get isConnected() {
628
+ return this.state === "connected";
629
+ }
630
+ /**
631
+ * Subscribe to all events
632
+ */
633
+ subscribe(callback) {
634
+ this.allCallbacks.add(callback);
635
+ return () => {
636
+ this.allCallbacks.delete(callback);
637
+ };
638
+ }
639
+ /**
640
+ * Subscribe to a specific event type
641
+ */
642
+ on(type, callback) {
643
+ if (!this.callbacks.has(type)) {
644
+ this.callbacks.set(type, /* @__PURE__ */ new Set());
645
+ }
646
+ this.callbacks.get(type).add(callback);
647
+ return () => {
648
+ const typeCallbacks = this.callbacks.get(type);
649
+ if (typeCallbacks) {
650
+ typeCallbacks.delete(callback);
651
+ if (typeCallbacks.size === 0) {
652
+ this.callbacks.delete(type);
653
+ }
654
+ }
655
+ };
656
+ }
657
+ /**
658
+ * Connect to the SSE stream
659
+ */
660
+ connect() {
661
+ if (this.eventSource) {
662
+ return;
663
+ }
664
+ this.setState("connecting");
665
+ const url = new URL(`${this.config.baseUrl}/events/stream`);
666
+ url.searchParams.set("apiKey", this.config.apiKey);
667
+ url.searchParams.set("agentId", this.config.agentId);
668
+ this.eventSource = new EventSource(url.toString(), {
669
+ headers: {
670
+ "Authorization": `Bearer ${this.config.apiKey}`
671
+ }
672
+ });
673
+ this.eventSource.onopen = () => {
674
+ this.reconnectAttempts = 0;
675
+ this.reconnectDelay = 1e3;
676
+ this.setState("connected");
677
+ };
678
+ this.eventSource.onmessage = (event) => {
679
+ this.handleMessage(event);
680
+ };
681
+ this.eventSource.onerror = (event) => {
682
+ this.handleError(event);
683
+ };
684
+ for (const eventType of EVENT_TYPES) {
685
+ this.eventSource.addEventListener(eventType, (event) => {
686
+ this.handleMessage(event, eventType);
687
+ });
688
+ }
689
+ }
690
+ /**
691
+ * Disconnect from the SSE stream
692
+ */
693
+ disconnect() {
694
+ if (this.eventSource) {
695
+ this.eventSource.close();
696
+ this.eventSource = null;
697
+ }
698
+ this.setState("disconnected");
699
+ }
700
+ /**
701
+ * Reconnect to the SSE stream
702
+ */
703
+ reconnect() {
704
+ this.disconnect();
705
+ this.connect();
706
+ }
707
+ /**
708
+ * Handle incoming SSE message
709
+ */
710
+ handleMessage(event, eventType) {
711
+ try {
712
+ const data = JSON.parse(event.data);
713
+ const rawData = data.data || data;
714
+ const normalizedData = rawData.task_id ? {
715
+ taskId: rawData.task_id,
716
+ sectionId: rawData.section_id,
717
+ artifactId: rawData.artifact_id,
718
+ artifactTitle: rawData.artifact_title,
719
+ heading: rawData.heading,
720
+ content: rawData.content,
721
+ assignedTo: rawData.assigned_to,
722
+ assignedAt: rawData.assigned_at,
723
+ priority: rawData.priority,
724
+ ...rawData
725
+ // Keep original fields too
726
+ } : rawData;
727
+ const artyfactsEvent = {
728
+ type: eventType || data.type || "unknown",
729
+ timestamp: data.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
730
+ data: normalizedData
731
+ };
732
+ const typeCallbacks = this.callbacks.get(artyfactsEvent.type);
733
+ if (typeCallbacks) {
734
+ for (const callback of typeCallbacks) {
735
+ this.safeCallCallback(callback, artyfactsEvent);
736
+ }
737
+ }
738
+ for (const callback of this.allCallbacks) {
739
+ this.safeCallCallback(callback, artyfactsEvent);
740
+ }
741
+ } catch (err) {
742
+ console.error("[Listener] Failed to parse SSE message:", event.data, err);
743
+ }
744
+ }
745
+ /**
746
+ * Safely call a callback, handling async and errors
747
+ */
748
+ async safeCallCallback(callback, event) {
749
+ try {
750
+ await callback(event);
751
+ } catch (err) {
752
+ console.error(`[Listener] Error in event callback for '${event.type}':`, err);
753
+ }
754
+ }
755
+ /**
756
+ * Handle SSE error
757
+ */
758
+ handleError(event) {
759
+ if (this.eventSource?.readyState === EventSource.CONNECTING) {
760
+ this.setState("reconnecting");
761
+ } else if (this.eventSource?.readyState === EventSource.CLOSED) {
762
+ this.setState("disconnected");
763
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
764
+ this.reconnectAttempts++;
765
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, 3e4);
766
+ console.log(
767
+ `[Listener] Connection lost, reconnecting in ${this.reconnectDelay / 1e3}s (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
768
+ );
769
+ setTimeout(() => {
770
+ if (this.state === "disconnected") {
771
+ this.connect();
772
+ }
773
+ }, this.reconnectDelay);
774
+ } else {
775
+ const error = new Error("Max reconnection attempts reached");
776
+ this.config.onError?.(error);
777
+ }
778
+ }
779
+ }
780
+ /**
781
+ * Update connection state
782
+ */
783
+ setState(state) {
784
+ if (this.state !== state) {
785
+ this.state = state;
786
+ this.config.onStateChange?.(state);
787
+ }
788
+ }
789
+ };
790
+ function createListener(config) {
791
+ return new ArtyfactsListener(config);
792
+ }
793
+
794
+ // src/mcp.ts
795
+ import { spawnSync } from "child_process";
796
+ var DEFAULT_BASE_URL3 = "https://artyfacts.dev/api/v1";
797
+ var OAUTH_MCP_SERVERS = {
798
+ supabase: {
799
+ url: "https://mcp.supabase.com/mcp",
800
+ name: "supabase"
801
+ },
802
+ figma: {
803
+ url: "https://mcp.figma.com/mcp",
804
+ name: "figma"
805
+ }
806
+ };
807
+ var CREDENTIAL_MCP_CONFIGS = {
808
+ postgres: (config) => ({
809
+ command: "npx",
810
+ args: ["-y", "@modelcontextprotocol/server-postgres", config.connection_string]
811
+ }),
812
+ github: (config) => ({
813
+ command: "npx",
814
+ args: ["-y", "@modelcontextprotocol/server-github"],
815
+ env: {
816
+ GITHUB_PERSONAL_ACCESS_TOKEN: config.api_key
817
+ }
818
+ }),
819
+ filesystem: (config) => ({
820
+ command: "npx",
821
+ args: ["-y", "@modelcontextprotocol/server-filesystem", ...config.paths || []]
822
+ })
823
+ };
824
+ function addMcpServer(options) {
825
+ const { name, transport, url, command, args = [], env = {}, scope = "user" } = options;
826
+ const cliArgs = ["mcp", "add", "-s", scope, "-t", transport];
827
+ for (const [key, value] of Object.entries(env)) {
828
+ cliArgs.push("-e", `${key}=${value}`);
829
+ }
830
+ cliArgs.push(name);
831
+ if (transport === "http" && url) {
832
+ cliArgs.push(url);
833
+ } else if (transport === "stdio" && command) {
834
+ cliArgs.push("--", command, ...args);
835
+ } else {
836
+ return { success: false, error: "Invalid configuration: missing url or command" };
837
+ }
838
+ console.log(`[MCP] Running: claude ${cliArgs.join(" ")}`);
839
+ const result = spawnSync("claude", cliArgs, {
840
+ encoding: "utf-8",
841
+ stdio: ["pipe", "pipe", "pipe"]
842
+ });
843
+ if (result.status === 0) {
844
+ return { success: true };
845
+ } else {
846
+ const error = result.stderr || result.stdout || `Exit code ${result.status}`;
847
+ if (error.includes("already exists")) {
848
+ console.log(`[MCP] Server ${name} exists - removing old config...`);
849
+ spawnSync("claude", ["mcp", "remove", name], { encoding: "utf-8" });
850
+ const retryResult = spawnSync("claude", cliArgs, {
851
+ encoding: "utf-8",
852
+ stdio: ["pipe", "pipe", "pipe"]
853
+ });
854
+ if (retryResult.status === 0) {
855
+ return { success: true, replaced: true };
856
+ } else {
857
+ return { success: false, error: retryResult.stderr || retryResult.stdout || "Failed after removing old config" };
858
+ }
859
+ }
860
+ return { success: false, error };
861
+ }
862
+ }
863
+ var McpHandler = class {
864
+ config;
865
+ constructor(config) {
866
+ this.config = {
867
+ ...config,
868
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL3
869
+ };
870
+ }
871
+ /**
872
+ * Handle an MCP connect request event
873
+ * Uses `claude mcp add` to configure the server dynamically
874
+ */
875
+ async handleConnectRequest(event) {
876
+ const { connection_id, platform, config: mcpConfig } = event.data;
877
+ try {
878
+ const serverName = mcpConfig?.server_name || platform;
879
+ const oauthServer = OAUTH_MCP_SERVERS[platform];
880
+ if (oauthServer) {
881
+ const result = addMcpServer({
882
+ name: serverName,
883
+ transport: "http",
884
+ url: oauthServer.url
885
+ });
886
+ if (!result.success) {
887
+ throw new Error(`Failed to add ${platform} MCP: ${result.error}`);
888
+ }
889
+ console.log(`[MCP] Added ${serverName} \u2192 ${oauthServer.url}`);
890
+ console.log(`[MCP] Starting ${platform} OAuth...`);
891
+ console.log(`[MCP] Claude will open - authenticate in browser, then type "done" to continue`);
892
+ console.log("");
893
+ const oauthProcess = spawnSync("claude", [
894
+ "--allowedTools",
895
+ `mcp__${serverName}__*`,
896
+ "--permission-mode",
897
+ "bypassPermissions",
898
+ `Authenticate with ${serverName} MCP now. Call the authenticate tool immediately.`
899
+ ], {
900
+ encoding: "utf-8",
901
+ stdio: "inherit",
902
+ // Interactive - user can see and respond
903
+ timeout: 3e5
904
+ // 5 minute timeout for OAuth
905
+ });
906
+ if (oauthProcess.status === 0) {
907
+ console.log(`[MCP] ${platform} OAuth completed!`);
908
+ } else {
909
+ console.log(`[MCP] OAuth session ended`);
910
+ }
911
+ } else {
912
+ const configBuilder = CREDENTIAL_MCP_CONFIGS[platform];
913
+ if (!configBuilder) {
914
+ throw new Error(`Unsupported MCP platform: ${platform}. Supported: ${[...Object.keys(OAUTH_MCP_SERVERS), ...Object.keys(CREDENTIAL_MCP_CONFIGS)].join(", ")}`);
915
+ }
916
+ if (!mcpConfig) {
917
+ throw new Error(`Platform ${platform} requires configuration (connection_string or api_key)`);
918
+ }
919
+ const serverConfig = configBuilder(mcpConfig);
920
+ const result = addMcpServer({
921
+ name: serverName,
922
+ transport: "stdio",
923
+ command: serverConfig.command,
924
+ args: serverConfig.args,
925
+ env: serverConfig.env
926
+ });
927
+ if (!result.success) {
928
+ throw new Error(`Failed to add ${platform} MCP: ${result.error}`);
929
+ }
930
+ console.log(`[MCP] Added ${serverName} for ${platform}`);
931
+ }
932
+ await this.updateConnectionStatus(connection_id, {
933
+ status: "active",
934
+ mcp_configured: true
935
+ });
936
+ this.config.onConfigured?.(connection_id, platform);
937
+ } catch (err) {
938
+ console.error(`[MCP] Failed to configure ${platform}:`, err);
939
+ await this.updateConnectionStatus(connection_id, {
940
+ status: "error",
941
+ mcp_configured: false,
942
+ error_message: err.message
943
+ });
944
+ this.config.onError?.(err, connection_id);
945
+ }
946
+ }
947
+ /**
948
+ * Check if a platform supports OAuth (no credentials needed)
949
+ */
950
+ static supportsOAuth(platform) {
951
+ return platform in OAUTH_MCP_SERVERS;
952
+ }
953
+ /**
954
+ * Get list of platforms with OAuth support
955
+ */
956
+ static getOAuthPlatforms() {
957
+ return Object.keys(OAUTH_MCP_SERVERS);
958
+ }
959
+ /**
960
+ * Update connection status in Artyfacts
961
+ */
962
+ async updateConnectionStatus(connectionId, update) {
963
+ const url = `${this.config.baseUrl}/connections/${connectionId}`;
964
+ console.log(`[MCP] Updating connection ${connectionId} to status: ${update.status}`);
965
+ try {
966
+ const response = await fetch(url, {
967
+ method: "PATCH",
968
+ headers: {
969
+ "Authorization": `Bearer ${this.config.apiKey}`,
970
+ "Content-Type": "application/json"
971
+ },
972
+ body: JSON.stringify(update)
973
+ });
974
+ if (!response.ok) {
975
+ const body = await response.text();
976
+ console.error(`[MCP] Failed to update connection status: ${response.status} - ${body}`);
977
+ } else {
978
+ console.log(`[MCP] Connection ${connectionId} updated to ${update.status}`);
979
+ }
980
+ } catch (err) {
981
+ console.error("[MCP] Failed to update connection status:", err);
982
+ }
983
+ }
984
+ /**
985
+ * List configured MCP servers using `claude mcp list`
986
+ */
987
+ listServers() {
988
+ const result = spawnSync("claude", ["mcp", "list"], {
989
+ encoding: "utf-8",
990
+ stdio: ["pipe", "pipe", "pipe"]
991
+ });
992
+ if (result.status === 0 && result.stdout) {
993
+ return result.stdout.trim().split("\n").filter(Boolean);
994
+ }
995
+ return [];
996
+ }
997
+ /**
998
+ * Remove an MCP server using `claude mcp remove`
999
+ */
1000
+ removeServer(serverName) {
1001
+ const result = spawnSync("claude", ["mcp", "remove", serverName], {
1002
+ encoding: "utf-8",
1003
+ stdio: ["pipe", "pipe", "pipe"]
1004
+ });
1005
+ if (result.status === 0) {
1006
+ console.log(`[MCP] Removed server: ${serverName}`);
1007
+ return true;
1008
+ } else {
1009
+ console.error(`[MCP] Failed to remove ${serverName}: ${result.stderr || result.stdout}`);
1010
+ return false;
1011
+ }
1012
+ }
1013
+ };
1014
+ function createMcpHandler(config) {
1015
+ return new McpHandler(config);
1016
+ }
1017
+
1018
+ export {
1019
+ loadCredentials,
1020
+ saveCredentials,
1021
+ clearCredentials,
1022
+ runDeviceAuth,
1023
+ promptForApiKey,
1024
+ getCredentials,
1025
+ ContextFetcher,
1026
+ buildPromptWithContext,
1027
+ createContextFetcher,
1028
+ ClaudeExecutor,
1029
+ createExecutor,
1030
+ ArtyfactsListener,
1031
+ createListener,
1032
+ McpHandler,
1033
+ createMcpHandler
1034
+ };
package/dist/cli.js CHANGED
@@ -918,32 +918,26 @@ var McpHandler = class {
918
918
  throw new Error(`Failed to add ${platform} MCP: ${result.error}`);
919
919
  }
920
920
  console.log(`[MCP] Added ${serverName} \u2192 ${oauthServer.url}`);
921
- console.log(`[MCP] Triggering ${platform} OAuth via Claude...`);
921
+ console.log(`[MCP] Starting ${platform} OAuth...`);
922
+ console.log(`[MCP] Claude will open - authenticate in browser, then type "done" to continue`);
923
+ console.log("");
922
924
  const oauthProcess = (0, import_child_process2.spawnSync)("claude", [
923
- "-p",
924
- `Run the mcp__${serverName}__authenticate tool now to start OAuth. Do not ask for confirmation, just run it.`,
925
925
  "--allowedTools",
926
926
  `mcp__${serverName}__*`,
927
927
  "--permission-mode",
928
- "bypassPermissions"
928
+ "bypassPermissions",
929
+ `Authenticate with ${serverName} MCP now. Call the authenticate tool immediately.`
929
930
  ], {
930
931
  encoding: "utf-8",
931
- stdio: ["pipe", "pipe", "pipe"],
932
- // Capture output to parse URL
933
- timeout: 12e4
932
+ stdio: "inherit",
933
+ // Interactive - user can see and respond
934
+ timeout: 3e5
935
+ // 5 minute timeout for OAuth
934
936
  });
935
- const output = oauthProcess.stdout || "";
936
- console.log(output);
937
- const urlMatch = output.match(/https:\/\/[^\s<>"]+oauth[^\s<>"]+/i) || output.match(/https:\/\/api\.supabase\.com[^\s<>"]+/i) || output.match(/https:\/\/www\.figma\.com\/oauth[^\s<>"]+/i);
938
- if (urlMatch) {
939
- const oauthUrl = urlMatch[0];
940
- console.log(`[MCP] Opening OAuth URL in browser...`);
941
- (0, import_child_process2.spawnSync)("open", [oauthUrl], { encoding: "utf-8" });
942
- }
943
937
  if (oauthProcess.status === 0) {
944
- console.log(`[MCP] ${platform} OAuth triggered!`);
938
+ console.log(`[MCP] ${platform} OAuth completed!`);
945
939
  } else {
946
- console.log(`[MCP] OAuth may need completion - try using ${platform} tools in Claude`);
940
+ console.log(`[MCP] OAuth session ended`);
947
941
  }
948
942
  } else {
949
943
  const configBuilder = CREDENTIAL_MCP_CONFIGS[platform];
@@ -1185,7 +1179,7 @@ async function checkAndClaimTasks(baseUrl, apiKey, agentId, activeTasks, executo
1185
1179
  }
1186
1180
  console.log(`
1187
1181
  [Claiming] ${task.heading}`);
1188
- const claimResponse = await fetch(`${baseUrl}/tasks/${task.section_id}/claim`, {
1182
+ const claimResponse = await fetch(`${baseUrl}/tasks/${task.id}/claim`, {
1189
1183
  method: "POST",
1190
1184
  headers: {
1191
1185
  "Authorization": `Bearer ${apiKey}`,
package/dist/cli.mjs CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  getCredentials,
8
8
  loadCredentials,
9
9
  promptForApiKey
10
- } from "./chunk-5H7VYMUX.mjs";
10
+ } from "./chunk-OISOOGKZ.mjs";
11
11
 
12
12
  // src/cli.ts
13
13
  import { Command } from "commander";
@@ -144,7 +144,7 @@ async function checkAndClaimTasks(baseUrl, apiKey, agentId, activeTasks, executo
144
144
  }
145
145
  console.log(`
146
146
  [Claiming] ${task.heading}`);
147
- const claimResponse = await fetch(`${baseUrl}/tasks/${task.section_id}/claim`, {
147
+ const claimResponse = await fetch(`${baseUrl}/tasks/${task.id}/claim`, {
148
148
  method: "POST",
149
149
  headers: {
150
150
  "Authorization": `Bearer ${apiKey}`,
package/dist/index.js CHANGED
@@ -6111,32 +6111,26 @@ var McpHandler = class {
6111
6111
  throw new Error(`Failed to add ${platform} MCP: ${result.error}`);
6112
6112
  }
6113
6113
  console.log(`[MCP] Added ${serverName} \u2192 ${oauthServer.url}`);
6114
- console.log(`[MCP] Triggering ${platform} OAuth via Claude...`);
6114
+ console.log(`[MCP] Starting ${platform} OAuth...`);
6115
+ console.log(`[MCP] Claude will open - authenticate in browser, then type "done" to continue`);
6116
+ console.log("");
6115
6117
  const oauthProcess = (0, import_child_process2.spawnSync)("claude", [
6116
- "-p",
6117
- `Run the mcp__${serverName}__authenticate tool now to start OAuth. Do not ask for confirmation, just run it.`,
6118
6118
  "--allowedTools",
6119
6119
  `mcp__${serverName}__*`,
6120
6120
  "--permission-mode",
6121
- "bypassPermissions"
6121
+ "bypassPermissions",
6122
+ `Authenticate with ${serverName} MCP now. Call the authenticate tool immediately.`
6122
6123
  ], {
6123
6124
  encoding: "utf-8",
6124
- stdio: ["pipe", "pipe", "pipe"],
6125
- // Capture output to parse URL
6126
- timeout: 12e4
6125
+ stdio: "inherit",
6126
+ // Interactive - user can see and respond
6127
+ timeout: 3e5
6128
+ // 5 minute timeout for OAuth
6127
6129
  });
6128
- const output = oauthProcess.stdout || "";
6129
- console.log(output);
6130
- const urlMatch = output.match(/https:\/\/[^\s<>"]+oauth[^\s<>"]+/i) || output.match(/https:\/\/api\.supabase\.com[^\s<>"]+/i) || output.match(/https:\/\/www\.figma\.com\/oauth[^\s<>"]+/i);
6131
- if (urlMatch) {
6132
- const oauthUrl = urlMatch[0];
6133
- console.log(`[MCP] Opening OAuth URL in browser...`);
6134
- (0, import_child_process2.spawnSync)("open", [oauthUrl], { encoding: "utf-8" });
6135
- }
6136
6130
  if (oauthProcess.status === 0) {
6137
- console.log(`[MCP] ${platform} OAuth triggered!`);
6131
+ console.log(`[MCP] ${platform} OAuth completed!`);
6138
6132
  } else {
6139
- console.log(`[MCP] OAuth may need completion - try using ${platform} tools in Claude`);
6133
+ console.log(`[MCP] OAuth session ended`);
6140
6134
  }
6141
6135
  } else {
6142
6136
  const configBuilder = CREDENTIAL_MCP_CONFIGS[platform];
package/dist/index.mjs CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  promptForApiKey,
15
15
  runDeviceAuth,
16
16
  saveCredentials
17
- } from "./chunk-5H7VYMUX.mjs";
17
+ } from "./chunk-OISOOGKZ.mjs";
18
18
 
19
19
  // node_modules/@anthropic-ai/sdk/internal/tslib.mjs
20
20
  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.22",
3
+ "version": "1.3.24",
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
@@ -254,7 +254,8 @@ async function checkAndClaimTasks(
254
254
  // Try to claim the task
255
255
  console.log(`\n[Claiming] ${task.heading}`);
256
256
 
257
- const claimResponse = await fetch(`${baseUrl}/tasks/${task.section_id}/claim`, {
257
+ // Use UUID (task.id) for claiming - section_id isn't globally unique
258
+ const claimResponse = await fetch(`${baseUrl}/tasks/${task.id}/claim`, {
258
259
  method: 'POST',
259
260
  headers: {
260
261
  'Authorization': `Bearer ${apiKey}`,
package/src/mcp.ts CHANGED
@@ -198,37 +198,26 @@ export class McpHandler {
198
198
 
199
199
  console.log(`[MCP] Added ${serverName} → ${oauthServer.url}`);
200
200
 
201
- // Trigger OAuth by telling Claude to run the authenticate tool
202
- console.log(`[MCP] Triggering ${platform} OAuth via Claude...`);
201
+ // Trigger OAuth interactively - Claude needs to stay running for callback
202
+ console.log(`[MCP] Starting ${platform} OAuth...`);
203
+ console.log(`[MCP] Claude will open - authenticate in browser, then type "done" to continue`);
204
+ console.log('');
205
+
206
+ // Run Claude interactively so it can receive the OAuth callback
203
207
  const oauthProcess = spawnSync('claude', [
204
- '-p',
205
- `Run the mcp__${serverName}__authenticate tool now to start OAuth. Do not ask for confirmation, just run it.`,
206
208
  '--allowedTools', `mcp__${serverName}__*`,
207
209
  '--permission-mode', 'bypassPermissions',
210
+ `Authenticate with ${serverName} MCP now. Call the authenticate tool immediately.`,
208
211
  ], {
209
212
  encoding: 'utf-8',
210
- stdio: ['pipe', 'pipe', 'pipe'], // Capture output to parse URL
211
- timeout: 120000,
213
+ stdio: 'inherit', // Interactive - user can see and respond
214
+ timeout: 300000, // 5 minute timeout for OAuth
212
215
  });
213
216
 
214
- const output = oauthProcess.stdout || '';
215
- console.log(output); // Still show the output
216
-
217
- // Parse OAuth URL from output and open it
218
- const urlMatch = output.match(/https:\/\/[^\s<>"]+oauth[^\s<>"]+/i) ||
219
- output.match(/https:\/\/api\.supabase\.com[^\s<>"]+/i) ||
220
- output.match(/https:\/\/www\.figma\.com\/oauth[^\s<>"]+/i);
221
-
222
- if (urlMatch) {
223
- const oauthUrl = urlMatch[0];
224
- console.log(`[MCP] Opening OAuth URL in browser...`);
225
- spawnSync('open', [oauthUrl], { encoding: 'utf-8' });
226
- }
227
-
228
217
  if (oauthProcess.status === 0) {
229
- console.log(`[MCP] ${platform} OAuth triggered!`);
218
+ console.log(`[MCP] ${platform} OAuth completed!`);
230
219
  } else {
231
- console.log(`[MCP] OAuth may need completion - try using ${platform} tools in Claude`);
220
+ console.log(`[MCP] OAuth session ended`);
232
221
  }
233
222
  } else {
234
223
  // Command-based MCP server - needs credentials