@artyfacts/claude 1.0.0 → 1.1.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/README.md +159 -78
- package/bin/artyfacts-claude.js +38 -0
- package/dist/chunk-365PEWTO.mjs +567 -0
- package/dist/chunk-QKDOZSBI.mjs +571 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +791 -0
- package/dist/cli.mjs +189 -726
- package/dist/index.d.mts +178 -249
- package/dist/index.d.ts +178 -249
- package/dist/index.js +472 -486
- package/dist/index.mjs +22 -584
- package/package.json +27 -16
- package/src/auth.ts +344 -0
- package/src/cli.ts +344 -0
- package/src/executor.ts +293 -0
- package/src/index.ts +86 -0
- package/src/listener.ts +313 -0
- package/tsconfig.json +20 -0
- package/bin/cli.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -30,597 +30,583 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
ArtyfactsListener: () => ArtyfactsListener,
|
|
34
|
+
ClaudeExecutor: () => ClaudeExecutor,
|
|
35
|
+
clearCredentials: () => clearCredentials,
|
|
36
|
+
createExecutor: () => createExecutor,
|
|
37
|
+
createListener: () => createListener,
|
|
38
|
+
getCredentials: () => getCredentials,
|
|
39
|
+
loadCredentials: () => loadCredentials,
|
|
40
|
+
promptForApiKey: () => promptForApiKey,
|
|
41
|
+
runDeviceAuth: () => runDeviceAuth,
|
|
42
|
+
saveCredentials: () => saveCredentials
|
|
37
43
|
});
|
|
38
44
|
module.exports = __toCommonJS(index_exports);
|
|
39
45
|
|
|
40
|
-
// src/
|
|
41
|
-
var
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
var
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
...options.headers
|
|
46
|
+
// src/auth.ts
|
|
47
|
+
var fs = __toESM(require("fs"));
|
|
48
|
+
var path = __toESM(require("path"));
|
|
49
|
+
var os = __toESM(require("os"));
|
|
50
|
+
var readline = __toESM(require("readline"));
|
|
51
|
+
var CREDENTIALS_DIR = path.join(os.homedir(), ".artyfacts");
|
|
52
|
+
var CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
|
|
53
|
+
var DEFAULT_BASE_URL = "https://artyfacts.dev/api/v1";
|
|
54
|
+
function loadCredentials() {
|
|
55
|
+
try {
|
|
56
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const data = fs.readFileSync(CREDENTIALS_FILE, "utf-8");
|
|
60
|
+
const credentials = JSON.parse(data);
|
|
61
|
+
if (credentials.expiresAt) {
|
|
62
|
+
const expiresAt = new Date(credentials.expiresAt);
|
|
63
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
64
|
+
console.log("\u26A0\uFE0F Credentials have expired");
|
|
65
|
+
return null;
|
|
61
66
|
}
|
|
62
|
-
});
|
|
63
|
-
if (!response.ok) {
|
|
64
|
-
const error = await response.text();
|
|
65
|
-
throw new Error(`API error (${response.status}): ${error}`);
|
|
66
67
|
}
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
68
|
+
return credentials;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error("Failed to load credentials:", error);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function saveCredentials(credentials) {
|
|
75
|
+
try {
|
|
76
|
+
if (!fs.existsSync(CREDENTIALS_DIR)) {
|
|
77
|
+
fs.mkdirSync(CREDENTIALS_DIR, { mode: 448, recursive: true });
|
|
78
|
+
}
|
|
79
|
+
fs.writeFileSync(
|
|
80
|
+
CREDENTIALS_FILE,
|
|
81
|
+
JSON.stringify(credentials, null, 2),
|
|
82
|
+
{ mode: 384 }
|
|
83
|
+
);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
throw new Error(`Failed to save credentials: ${error}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function clearCredentials() {
|
|
89
|
+
try {
|
|
90
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
91
|
+
fs.unlinkSync(CREDENTIALS_FILE);
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error("Failed to clear credentials:", error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function runDeviceAuth(baseUrl = DEFAULT_BASE_URL) {
|
|
98
|
+
console.log("\u{1F510} Starting device authentication...\n");
|
|
99
|
+
const deviceAuth = await requestDeviceCode(baseUrl);
|
|
100
|
+
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");
|
|
101
|
+
console.log("\u{1F4CB} To authenticate, visit:");
|
|
102
|
+
console.log(` ${deviceAuth.verificationUri}`);
|
|
103
|
+
console.log("");
|
|
104
|
+
console.log("\u{1F511} Enter this code:");
|
|
105
|
+
console.log(` ${deviceAuth.userCode}`);
|
|
106
|
+
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");
|
|
107
|
+
console.log("\u23F3 Waiting for authentication...\n");
|
|
108
|
+
const credentials = await pollForToken(
|
|
109
|
+
baseUrl,
|
|
110
|
+
deviceAuth.deviceCode,
|
|
111
|
+
deviceAuth.interval,
|
|
112
|
+
deviceAuth.expiresIn
|
|
113
|
+
);
|
|
114
|
+
saveCredentials(credentials);
|
|
115
|
+
console.log("\u2705 Authentication successful!");
|
|
116
|
+
console.log(` Agent ID: ${credentials.agentId}`);
|
|
117
|
+
if (credentials.agentName) {
|
|
118
|
+
console.log(` Agent Name: ${credentials.agentName}`);
|
|
119
|
+
}
|
|
120
|
+
console.log("");
|
|
121
|
+
return credentials;
|
|
122
|
+
}
|
|
123
|
+
async function requestDeviceCode(baseUrl) {
|
|
124
|
+
const response = await fetch(`${baseUrl}/auth/device`, {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: {
|
|
127
|
+
"Content-Type": "application/json"
|
|
128
|
+
},
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
client_id: "artyfacts-claude",
|
|
131
|
+
scope: "agent:execute"
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
const error = await response.text();
|
|
136
|
+
throw new Error(`Failed to start device auth: ${error}`);
|
|
137
|
+
}
|
|
138
|
+
const data = await response.json();
|
|
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,
|
|
144
|
+
interval: data.interval || 5
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async function pollForToken(baseUrl, deviceCode, interval, expiresIn) {
|
|
148
|
+
const startTime = Date.now();
|
|
149
|
+
const timeoutMs = expiresIn * 1e3;
|
|
150
|
+
while (true) {
|
|
151
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
152
|
+
throw new Error("Device authentication timed out");
|
|
153
|
+
}
|
|
154
|
+
await sleep(interval * 1e3);
|
|
155
|
+
const response = await fetch(`${baseUrl}/auth/device/token`, {
|
|
92
156
|
method: "POST",
|
|
157
|
+
headers: {
|
|
158
|
+
"Content-Type": "application/json"
|
|
159
|
+
},
|
|
93
160
|
body: JSON.stringify({
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
summary: options?.summary
|
|
161
|
+
device_code: deviceCode,
|
|
162
|
+
client_id: "artyfacts-claude"
|
|
97
163
|
})
|
|
98
164
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
165
|
+
if (response.ok) {
|
|
166
|
+
const data = await response.json();
|
|
167
|
+
return {
|
|
168
|
+
apiKey: data.apiKey,
|
|
169
|
+
agentId: data.agentId,
|
|
170
|
+
agentName: data.agentName,
|
|
171
|
+
expiresAt: data.expiresAt
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const errorData = await response.json().catch(() => ({}));
|
|
175
|
+
const errorCode = errorData.error || errorData.code;
|
|
176
|
+
if (errorCode === "authorization_pending") {
|
|
177
|
+
process.stdout.write(".");
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (errorCode === "slow_down") {
|
|
181
|
+
interval = Math.min(interval * 2, 30);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (errorCode === "expired_token") {
|
|
185
|
+
throw new Error("Device code expired. Please try again.");
|
|
186
|
+
}
|
|
187
|
+
if (errorCode === "access_denied") {
|
|
188
|
+
throw new Error("Authorization was denied.");
|
|
189
|
+
}
|
|
190
|
+
throw new Error(`Authentication failed: ${errorData.message || errorCode || response.statusText}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function promptForApiKey() {
|
|
194
|
+
const rl = readline.createInterface({
|
|
195
|
+
input: process.stdin,
|
|
196
|
+
output: process.stdout
|
|
197
|
+
});
|
|
198
|
+
const question = (prompt) => {
|
|
199
|
+
return new Promise((resolve) => {
|
|
200
|
+
rl.question(prompt, resolve);
|
|
110
201
|
});
|
|
202
|
+
};
|
|
203
|
+
console.log("\u{1F511} Manual Configuration\n");
|
|
204
|
+
console.log("Enter your Artyfacts credentials:\n");
|
|
205
|
+
const apiKey = await question("API Key: ");
|
|
206
|
+
const agentId = await question("Agent ID: ");
|
|
207
|
+
const agentName = await question("Agent Name (optional): ");
|
|
208
|
+
rl.close();
|
|
209
|
+
if (!apiKey || !agentId) {
|
|
210
|
+
throw new Error("API Key and Agent ID are required");
|
|
211
|
+
}
|
|
212
|
+
const credentials = {
|
|
213
|
+
apiKey: apiKey.trim(),
|
|
214
|
+
agentId: agentId.trim(),
|
|
215
|
+
agentName: agentName.trim() || void 0
|
|
216
|
+
};
|
|
217
|
+
saveCredentials(credentials);
|
|
218
|
+
console.log("\n\u2705 Credentials saved!");
|
|
219
|
+
return credentials;
|
|
220
|
+
}
|
|
221
|
+
function sleep(ms) {
|
|
222
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
223
|
+
}
|
|
224
|
+
async function getCredentials(options) {
|
|
225
|
+
if (!options?.forceAuth) {
|
|
226
|
+
const existing = loadCredentials();
|
|
227
|
+
if (existing) {
|
|
228
|
+
return existing;
|
|
229
|
+
}
|
|
111
230
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
*/
|
|
115
|
-
async getMe() {
|
|
116
|
-
return this.fetch("/me");
|
|
117
|
-
}
|
|
118
|
-
};
|
|
231
|
+
return runDeviceAuth(options?.baseUrl);
|
|
232
|
+
}
|
|
119
233
|
|
|
120
|
-
// src/
|
|
234
|
+
// src/executor.ts
|
|
121
235
|
var import_child_process = require("child_process");
|
|
122
|
-
var
|
|
123
|
-
var
|
|
236
|
+
var DEFAULT_TIMEOUT = 5 * 60 * 1e3;
|
|
237
|
+
var DEFAULT_SYSTEM_PROMPT = `You are an AI agent working within the Artyfacts task management system.
|
|
238
|
+
|
|
239
|
+
Your job is to complete tasks assigned to you. For each task:
|
|
240
|
+
1. Understand the requirements from the task heading and content
|
|
241
|
+
2. Complete the task to the best of your ability
|
|
242
|
+
3. Provide a clear, actionable output
|
|
243
|
+
|
|
244
|
+
Guidelines:
|
|
245
|
+
- Be thorough but concise
|
|
246
|
+
- If the task requires code, provide working code
|
|
247
|
+
- If the task requires analysis, provide structured findings
|
|
248
|
+
- If the task requires a decision, explain your reasoning
|
|
249
|
+
- If you cannot complete the task, explain why
|
|
250
|
+
|
|
251
|
+
Format your response as follows:
|
|
252
|
+
1. First, provide your main output (the task deliverable)
|
|
253
|
+
2. End with a brief summary line starting with "SUMMARY:"`;
|
|
254
|
+
var ClaudeExecutor = class {
|
|
124
255
|
config;
|
|
125
|
-
runningTasks = /* @__PURE__ */ new Map();
|
|
126
256
|
constructor(config = {}) {
|
|
127
|
-
super();
|
|
128
257
|
this.config = {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
timeoutMs: config.timeoutMs ?? 5 * 60 * 1e3
|
|
133
|
-
// 5 minutes
|
|
258
|
+
...config,
|
|
259
|
+
timeout: config.timeout || DEFAULT_TIMEOUT,
|
|
260
|
+
claudePath: config.claudePath || "claude"
|
|
134
261
|
};
|
|
135
262
|
}
|
|
136
263
|
/**
|
|
137
|
-
*
|
|
264
|
+
* Execute a task using Claude Code CLI
|
|
138
265
|
*/
|
|
139
|
-
async
|
|
266
|
+
async execute(task) {
|
|
140
267
|
try {
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
268
|
+
const prompt = this.buildTaskPrompt(task);
|
|
269
|
+
const output = await this.runClaude(prompt);
|
|
270
|
+
const { content, summary } = this.parseResponse(output, task.heading);
|
|
271
|
+
return {
|
|
272
|
+
success: true,
|
|
273
|
+
output: content,
|
|
274
|
+
summary
|
|
275
|
+
};
|
|
146
276
|
} catch (error) {
|
|
277
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
147
278
|
return {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
279
|
+
success: false,
|
|
280
|
+
output: "",
|
|
281
|
+
summary: `Failed: ${errorMessage}`,
|
|
282
|
+
error: errorMessage
|
|
151
283
|
};
|
|
152
284
|
}
|
|
153
285
|
}
|
|
154
286
|
/**
|
|
155
|
-
* Run
|
|
287
|
+
* Run Claude Code CLI with the given prompt
|
|
156
288
|
*/
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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,
|
|
289
|
+
runClaude(prompt) {
|
|
290
|
+
return new Promise((resolve, reject) => {
|
|
291
|
+
const claudePath = this.config.claudePath || "claude";
|
|
292
|
+
const proc = (0, import_child_process.spawn)(claudePath, ["--print"], {
|
|
177
293
|
stdio: ["pipe", "pipe", "pipe"],
|
|
178
|
-
|
|
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
|
-
});
|
|
294
|
+
timeout: this.config.timeout
|
|
220
295
|
});
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
promise
|
|
296
|
+
let stdout = "";
|
|
297
|
+
let stderr = "";
|
|
298
|
+
proc.stdout.on("data", (data) => {
|
|
299
|
+
stdout += data.toString();
|
|
226
300
|
});
|
|
227
|
-
|
|
228
|
-
|
|
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();
|
|
301
|
+
proc.stderr.on("data", (data) => {
|
|
302
|
+
stderr += data.toString();
|
|
246
303
|
});
|
|
247
304
|
proc.on("close", (code) => {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
durationMs: Date.now() - startedAt
|
|
254
|
-
});
|
|
305
|
+
if (code === 0) {
|
|
306
|
+
resolve(stdout.trim());
|
|
307
|
+
} else {
|
|
308
|
+
reject(new Error(stderr || `Claude exited with code ${code}`));
|
|
309
|
+
}
|
|
255
310
|
});
|
|
256
311
|
proc.on("error", (err) => {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
312
|
+
if (err.code === "ENOENT") {
|
|
313
|
+
reject(new Error(
|
|
314
|
+
"Claude Code CLI not found. Please install it:\n npm install -g @anthropic-ai/claude-code"
|
|
315
|
+
));
|
|
316
|
+
} else {
|
|
317
|
+
reject(err);
|
|
318
|
+
}
|
|
264
319
|
});
|
|
320
|
+
proc.stdin.write(prompt);
|
|
321
|
+
proc.stdin.end();
|
|
265
322
|
});
|
|
266
323
|
}
|
|
267
324
|
/**
|
|
268
|
-
*
|
|
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
|
|
325
|
+
* Build the task prompt
|
|
282
326
|
*/
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Cancel all running tasks
|
|
288
|
-
*/
|
|
289
|
-
cancelAll() {
|
|
290
|
-
for (const [taskId] of this.runningTasks) {
|
|
291
|
-
this.cancelTask(taskId);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
};
|
|
327
|
+
buildTaskPrompt(task) {
|
|
328
|
+
const parts = [];
|
|
329
|
+
const systemPrompt = this.config.systemPromptPrefix ? `${this.config.systemPromptPrefix}
|
|
295
330
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
};
|
|
331
|
+
${DEFAULT_SYSTEM_PROMPT}` : DEFAULT_SYSTEM_PROMPT;
|
|
332
|
+
parts.push(systemPrompt);
|
|
333
|
+
parts.push("");
|
|
334
|
+
parts.push("---");
|
|
335
|
+
parts.push("");
|
|
336
|
+
parts.push(`# Task: ${task.heading}`);
|
|
337
|
+
parts.push("");
|
|
338
|
+
if (task.artifactTitle) {
|
|
339
|
+
parts.push(`**Artifact:** ${task.artifactTitle}`);
|
|
336
340
|
}
|
|
337
|
-
if (
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
error: "Claude Code is not authenticated. Run: claude login"
|
|
341
|
-
};
|
|
341
|
+
if (task.priority) {
|
|
342
|
+
const priorityLabels = ["High", "Medium", "Low"];
|
|
343
|
+
parts.push(`**Priority:** ${priorityLabels[task.priority - 1] || "Medium"}`);
|
|
342
344
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
345
|
+
parts.push("");
|
|
346
|
+
parts.push("## Description");
|
|
347
|
+
parts.push(task.content || "No additional description provided.");
|
|
348
|
+
parts.push("");
|
|
349
|
+
if (task.context && Object.keys(task.context).length > 0) {
|
|
350
|
+
parts.push("## Additional Context");
|
|
351
|
+
parts.push("```json");
|
|
352
|
+
parts.push(JSON.stringify(task.context, null, 2));
|
|
353
|
+
parts.push("```");
|
|
354
|
+
parts.push("");
|
|
353
355
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
this.pollTimer = setInterval(() => {
|
|
358
|
-
this.poll().catch((err) => this.emit("error", err));
|
|
359
|
-
}, this.config.pollIntervalMs);
|
|
356
|
+
parts.push("## Instructions");
|
|
357
|
+
parts.push("Complete this task and provide your output below.");
|
|
358
|
+
return parts.join("\n");
|
|
360
359
|
}
|
|
361
360
|
/**
|
|
362
|
-
*
|
|
361
|
+
* Parse the response to extract output and summary
|
|
363
362
|
*/
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
363
|
+
parseResponse(fullOutput, taskHeading) {
|
|
364
|
+
const summaryMatch = fullOutput.match(/SUMMARY:\s*(.+?)(?:\n|$)/i);
|
|
365
|
+
if (summaryMatch) {
|
|
366
|
+
const summary2 = summaryMatch[1].trim();
|
|
367
|
+
const content = fullOutput.replace(/SUMMARY:\s*.+?(?:\n|$)/i, "").trim();
|
|
368
|
+
return { content, summary: summary2 };
|
|
370
369
|
}
|
|
371
|
-
|
|
372
|
-
|
|
370
|
+
const lines = fullOutput.split("\n").filter((l) => l.trim());
|
|
371
|
+
const firstLine = lines[0] || "";
|
|
372
|
+
const summary = firstLine.length > 100 ? `${firstLine.substring(0, 97)}...` : firstLine || `Completed: ${taskHeading}`;
|
|
373
|
+
return { content: fullOutput, summary };
|
|
373
374
|
}
|
|
374
375
|
/**
|
|
375
|
-
*
|
|
376
|
+
* Test that Claude Code CLI is available and working
|
|
376
377
|
*/
|
|
377
|
-
async
|
|
378
|
+
async testConnection() {
|
|
378
379
|
try {
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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)));
|
|
380
|
+
const output = await this.runClaude('Say "connected" and nothing else.');
|
|
381
|
+
return output.toLowerCase().includes("connected");
|
|
382
|
+
} catch {
|
|
383
|
+
return false;
|
|
390
384
|
}
|
|
391
385
|
}
|
|
392
386
|
/**
|
|
393
|
-
*
|
|
387
|
+
* Check if Claude Code CLI is installed
|
|
394
388
|
*/
|
|
395
|
-
async
|
|
396
|
-
|
|
397
|
-
const
|
|
398
|
-
|
|
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
|
|
389
|
+
async isInstalled() {
|
|
390
|
+
return new Promise((resolve) => {
|
|
391
|
+
const proc = (0, import_child_process.spawn)(this.config.claudePath || "claude", ["--version"], {
|
|
392
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
405
393
|
});
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
-
}
|
|
394
|
+
proc.on("close", (code) => {
|
|
395
|
+
resolve(code === 0);
|
|
396
|
+
});
|
|
397
|
+
proc.on("error", () => {
|
|
398
|
+
resolve(false);
|
|
399
|
+
});
|
|
400
|
+
});
|
|
421
401
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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.
|
|
402
|
+
};
|
|
403
|
+
function createExecutor(config) {
|
|
404
|
+
return new ClaudeExecutor(config);
|
|
405
|
+
}
|
|
440
406
|
|
|
441
|
-
|
|
407
|
+
// src/listener.ts
|
|
408
|
+
var import_eventsource = __toESM(require("eventsource"));
|
|
409
|
+
var DEFAULT_BASE_URL2 = "https://artyfacts.dev/api/v1";
|
|
410
|
+
var EVENT_TYPES = [
|
|
411
|
+
"connected",
|
|
412
|
+
"heartbeat",
|
|
413
|
+
"task_assigned",
|
|
414
|
+
"task_unblocked",
|
|
415
|
+
"blocker_resolved",
|
|
416
|
+
"notification"
|
|
417
|
+
];
|
|
418
|
+
var ArtyfactsListener = class {
|
|
419
|
+
config;
|
|
420
|
+
eventSource = null;
|
|
421
|
+
callbacks = /* @__PURE__ */ new Map();
|
|
422
|
+
allCallbacks = /* @__PURE__ */ new Set();
|
|
423
|
+
state = "disconnected";
|
|
424
|
+
reconnectAttempts = 0;
|
|
425
|
+
maxReconnectAttempts = 10;
|
|
426
|
+
reconnectDelay = 1e3;
|
|
427
|
+
constructor(config) {
|
|
428
|
+
if (!config.apiKey) {
|
|
429
|
+
throw new Error("API key is required");
|
|
430
|
+
}
|
|
431
|
+
if (!config.agentId) {
|
|
432
|
+
throw new Error("Agent ID is required");
|
|
433
|
+
}
|
|
434
|
+
this.config = {
|
|
435
|
+
...config,
|
|
436
|
+
baseUrl: config.baseUrl || DEFAULT_BASE_URL2
|
|
437
|
+
};
|
|
442
438
|
}
|
|
443
439
|
/**
|
|
444
|
-
*
|
|
440
|
+
* Get current connection state
|
|
445
441
|
*/
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (output.length <= maxLen) return output;
|
|
449
|
-
return "..." + output.slice(-maxLen);
|
|
442
|
+
get connectionState() {
|
|
443
|
+
return this.state;
|
|
450
444
|
}
|
|
451
445
|
/**
|
|
452
|
-
* Check if
|
|
446
|
+
* Check if connected
|
|
453
447
|
*/
|
|
454
|
-
|
|
455
|
-
return this.
|
|
448
|
+
get isConnected() {
|
|
449
|
+
return this.state === "connected";
|
|
456
450
|
}
|
|
457
451
|
/**
|
|
458
|
-
*
|
|
452
|
+
* Subscribe to all events
|
|
459
453
|
*/
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
maxConcurrent: this.config.maxConcurrent
|
|
454
|
+
subscribe(callback) {
|
|
455
|
+
this.allCallbacks.add(callback);
|
|
456
|
+
return () => {
|
|
457
|
+
this.allCallbacks.delete(callback);
|
|
465
458
|
};
|
|
466
459
|
}
|
|
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
460
|
/**
|
|
482
|
-
*
|
|
461
|
+
* Subscribe to a specific event type
|
|
483
462
|
*/
|
|
484
|
-
|
|
485
|
-
|
|
463
|
+
on(type, callback) {
|
|
464
|
+
if (!this.callbacks.has(type)) {
|
|
465
|
+
this.callbacks.set(type, /* @__PURE__ */ new Set());
|
|
466
|
+
}
|
|
467
|
+
this.callbacks.get(type).add(callback);
|
|
468
|
+
return () => {
|
|
469
|
+
const typeCallbacks = this.callbacks.get(type);
|
|
470
|
+
if (typeCallbacks) {
|
|
471
|
+
typeCallbacks.delete(callback);
|
|
472
|
+
if (typeCallbacks.size === 0) {
|
|
473
|
+
this.callbacks.delete(type);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
};
|
|
486
477
|
}
|
|
487
478
|
/**
|
|
488
|
-
*
|
|
479
|
+
* Connect to the SSE stream
|
|
489
480
|
*/
|
|
490
|
-
|
|
491
|
-
if (
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
481
|
+
connect() {
|
|
482
|
+
if (this.eventSource) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
this.setState("connecting");
|
|
486
|
+
const url = new URL(`${this.config.baseUrl}/events/stream`);
|
|
487
|
+
url.searchParams.set("apiKey", this.config.apiKey);
|
|
488
|
+
url.searchParams.set("agentId", this.config.agentId);
|
|
489
|
+
this.eventSource = new import_eventsource.default(url.toString(), {
|
|
490
|
+
headers: {
|
|
491
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
497
492
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
493
|
+
});
|
|
494
|
+
this.eventSource.onopen = () => {
|
|
495
|
+
this.reconnectAttempts = 0;
|
|
496
|
+
this.reconnectDelay = 1e3;
|
|
497
|
+
this.setState("connected");
|
|
498
|
+
};
|
|
499
|
+
this.eventSource.onmessage = (event) => {
|
|
500
|
+
this.handleMessage(event);
|
|
501
|
+
};
|
|
502
|
+
this.eventSource.onerror = (event) => {
|
|
503
|
+
this.handleError(event);
|
|
504
|
+
};
|
|
505
|
+
for (const eventType of EVENT_TYPES) {
|
|
506
|
+
this.eventSource.addEventListener(eventType, (event) => {
|
|
507
|
+
this.handleMessage(event, eventType);
|
|
508
|
+
});
|
|
501
509
|
}
|
|
502
510
|
}
|
|
503
511
|
/**
|
|
504
|
-
*
|
|
512
|
+
* Disconnect from the SSE stream
|
|
505
513
|
*/
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
/**
|
|
511
|
-
* Clear stored credentials (logout)
|
|
512
|
-
*/
|
|
513
|
-
logout() {
|
|
514
|
-
if (fs.existsSync(this.credentialsPath)) {
|
|
515
|
-
fs.unlinkSync(this.credentialsPath);
|
|
514
|
+
disconnect() {
|
|
515
|
+
if (this.eventSource) {
|
|
516
|
+
this.eventSource.close();
|
|
517
|
+
this.eventSource = null;
|
|
516
518
|
}
|
|
519
|
+
this.setState("disconnected");
|
|
517
520
|
}
|
|
518
521
|
/**
|
|
519
|
-
*
|
|
522
|
+
* Reconnect to the SSE stream
|
|
520
523
|
*/
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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();
|
|
524
|
+
reconnect() {
|
|
525
|
+
this.disconnect();
|
|
526
|
+
this.connect();
|
|
530
527
|
}
|
|
531
528
|
/**
|
|
532
|
-
*
|
|
529
|
+
* Handle incoming SSE message
|
|
533
530
|
*/
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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.");
|
|
531
|
+
handleMessage(event, eventType) {
|
|
532
|
+
try {
|
|
533
|
+
const data = JSON.parse(event.data);
|
|
534
|
+
const artyfactsEvent = {
|
|
535
|
+
type: eventType || data.type || "unknown",
|
|
536
|
+
timestamp: data.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
537
|
+
data: data.data || data
|
|
538
|
+
};
|
|
539
|
+
const typeCallbacks = this.callbacks.get(artyfactsEvent.type);
|
|
540
|
+
if (typeCallbacks) {
|
|
541
|
+
for (const callback of typeCallbacks) {
|
|
542
|
+
this.safeCallCallback(callback, artyfactsEvent);
|
|
543
|
+
}
|
|
557
544
|
}
|
|
558
|
-
|
|
559
|
-
|
|
545
|
+
for (const callback of this.allCallbacks) {
|
|
546
|
+
this.safeCallCallback(callback, artyfactsEvent);
|
|
560
547
|
}
|
|
561
|
-
|
|
548
|
+
} catch (err) {
|
|
549
|
+
console.error("[Listener] Failed to parse SSE message:", event.data, err);
|
|
562
550
|
}
|
|
563
|
-
throw new Error("Authorization timed out. Please try again.");
|
|
564
551
|
}
|
|
565
552
|
/**
|
|
566
|
-
*
|
|
553
|
+
* Safely call a callback, handling async and errors
|
|
567
554
|
*/
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
555
|
+
async safeCallCallback(callback, event) {
|
|
556
|
+
try {
|
|
557
|
+
await callback(event);
|
|
558
|
+
} catch (err) {
|
|
559
|
+
console.error(`[Listener] Error in event callback for '${event.type}':`, err);
|
|
572
560
|
}
|
|
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
561
|
}
|
|
582
562
|
/**
|
|
583
|
-
*
|
|
563
|
+
* Handle SSE error
|
|
584
564
|
*/
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
565
|
+
handleError(event) {
|
|
566
|
+
if (this.eventSource?.readyState === import_eventsource.default.CONNECTING) {
|
|
567
|
+
this.setState("reconnecting");
|
|
568
|
+
} else if (this.eventSource?.readyState === import_eventsource.default.CLOSED) {
|
|
569
|
+
this.setState("disconnected");
|
|
570
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
571
|
+
this.reconnectAttempts++;
|
|
572
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, 3e4);
|
|
573
|
+
console.log(
|
|
574
|
+
`[Listener] Connection lost, reconnecting in ${this.reconnectDelay / 1e3}s (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
|
|
575
|
+
);
|
|
576
|
+
setTimeout(() => {
|
|
577
|
+
if (this.state === "disconnected") {
|
|
578
|
+
this.connect();
|
|
579
|
+
}
|
|
580
|
+
}, this.reconnectDelay);
|
|
592
581
|
} else {
|
|
593
|
-
|
|
582
|
+
const error = new Error("Max reconnection attempts reached");
|
|
583
|
+
this.config.onError?.(error);
|
|
594
584
|
}
|
|
595
|
-
} catch {
|
|
596
|
-
console.log(`Please open this URL in your browser: ${url}`);
|
|
597
585
|
}
|
|
598
586
|
}
|
|
599
587
|
/**
|
|
600
|
-
*
|
|
588
|
+
* Update connection state
|
|
601
589
|
*/
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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));
|
|
590
|
+
setState(state) {
|
|
591
|
+
if (this.state !== state) {
|
|
592
|
+
this.state = state;
|
|
593
|
+
this.config.onStateChange?.(state);
|
|
594
|
+
}
|
|
617
595
|
}
|
|
618
596
|
};
|
|
597
|
+
function createListener(config) {
|
|
598
|
+
return new ArtyfactsListener(config);
|
|
599
|
+
}
|
|
619
600
|
// Annotate the CommonJS export names for ESM import in node:
|
|
620
601
|
0 && (module.exports = {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
602
|
+
ArtyfactsListener,
|
|
603
|
+
ClaudeExecutor,
|
|
604
|
+
clearCredentials,
|
|
605
|
+
createExecutor,
|
|
606
|
+
createListener,
|
|
607
|
+
getCredentials,
|
|
608
|
+
loadCredentials,
|
|
609
|
+
promptForApiKey,
|
|
610
|
+
runDeviceAuth,
|
|
611
|
+
saveCredentials
|
|
625
612
|
});
|
|
626
|
-
//# sourceMappingURL=index.js.map
|