@artyfacts/openclaw 0.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 +92 -0
- package/bin/artyfacts-openclaw.js +2 -0
- package/dist/chunk-CTWVGZRX.mjs +1119 -0
- package/dist/chunk-TT3SG5BN.mjs +987 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1508 -0
- package/dist/cli.mjs +391 -0
- package/dist/index.d.mts +446 -0
- package/dist/index.d.ts +446 -0
- package/dist/index.js +1172 -0
- package/dist/index.mjs +36 -0
- package/package.json +64 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1508 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/cli.ts
|
|
27
|
+
var import_commander = require("commander");
|
|
28
|
+
|
|
29
|
+
// src/auth.ts
|
|
30
|
+
var fs = __toESM(require("fs"));
|
|
31
|
+
var path = __toESM(require("path"));
|
|
32
|
+
var os = __toESM(require("os"));
|
|
33
|
+
var readline = __toESM(require("readline"));
|
|
34
|
+
var CREDENTIALS_DIR = path.join(os.homedir(), ".artyfacts");
|
|
35
|
+
var CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "openclaw-credentials.json");
|
|
36
|
+
var DEFAULT_BASE_URL = "https://artyfacts.dev/api/v1";
|
|
37
|
+
var CLIENT_ID = "artyfacts-openclaw";
|
|
38
|
+
function loadCredentials() {
|
|
39
|
+
try {
|
|
40
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const data = fs.readFileSync(CREDENTIALS_FILE, "utf-8");
|
|
44
|
+
const credentials = JSON.parse(data);
|
|
45
|
+
if (credentials.expiresAt) {
|
|
46
|
+
const expiresAt = new Date(credentials.expiresAt);
|
|
47
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
48
|
+
console.log("\u26A0\uFE0F Credentials have expired");
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return credentials;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error("Failed to load credentials:", error);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function saveCredentials(credentials) {
|
|
59
|
+
try {
|
|
60
|
+
if (!fs.existsSync(CREDENTIALS_DIR)) {
|
|
61
|
+
fs.mkdirSync(CREDENTIALS_DIR, { mode: 448, recursive: true });
|
|
62
|
+
}
|
|
63
|
+
fs.writeFileSync(
|
|
64
|
+
CREDENTIALS_FILE,
|
|
65
|
+
JSON.stringify(credentials, null, 2),
|
|
66
|
+
{ mode: 384 }
|
|
67
|
+
);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
throw new Error(`Failed to save credentials: ${error}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function clearCredentials() {
|
|
73
|
+
try {
|
|
74
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
75
|
+
fs.unlinkSync(CREDENTIALS_FILE);
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error("Failed to clear credentials:", error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function runDeviceAuth(baseUrl = DEFAULT_BASE_URL) {
|
|
82
|
+
console.log("\u{1F510} Starting device authentication...\n");
|
|
83
|
+
const deviceAuth = await requestDeviceCode(baseUrl);
|
|
84
|
+
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");
|
|
85
|
+
console.log("\u{1F4CB} To authenticate, visit:");
|
|
86
|
+
console.log(` ${deviceAuth.verificationUri}`);
|
|
87
|
+
console.log("");
|
|
88
|
+
console.log("\u{1F511} Enter this code:");
|
|
89
|
+
console.log(` ${deviceAuth.userCode}`);
|
|
90
|
+
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");
|
|
91
|
+
console.log("\u23F3 Waiting for authentication...\n");
|
|
92
|
+
const credentials = await pollForToken(
|
|
93
|
+
baseUrl,
|
|
94
|
+
deviceAuth.deviceCode,
|
|
95
|
+
deviceAuth.interval,
|
|
96
|
+
deviceAuth.expiresIn
|
|
97
|
+
);
|
|
98
|
+
saveCredentials(credentials);
|
|
99
|
+
console.log("\u2705 Authentication successful!");
|
|
100
|
+
console.log(` Agent ID: ${credentials.agentId}`);
|
|
101
|
+
if (credentials.agentName) {
|
|
102
|
+
console.log(` Agent Name: ${credentials.agentName}`);
|
|
103
|
+
}
|
|
104
|
+
console.log("");
|
|
105
|
+
return credentials;
|
|
106
|
+
}
|
|
107
|
+
async function requestDeviceCode(baseUrl) {
|
|
108
|
+
const response = await fetch(`${baseUrl}/auth/device`, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: {
|
|
111
|
+
"Content-Type": "application/json"
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify({
|
|
114
|
+
client_id: CLIENT_ID,
|
|
115
|
+
scope: "agent:execute"
|
|
116
|
+
})
|
|
117
|
+
});
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
const error = await response.text();
|
|
120
|
+
throw new Error(`Failed to start device auth: ${error}`);
|
|
121
|
+
}
|
|
122
|
+
const data = await response.json();
|
|
123
|
+
return {
|
|
124
|
+
deviceCode: data.deviceCode || data.device_code || "",
|
|
125
|
+
userCode: data.userCode || data.user_code || "",
|
|
126
|
+
verificationUri: data.verificationUri || data.verification_uri || `https://artyfacts.dev/auth/device`,
|
|
127
|
+
expiresIn: data.expiresIn || data.expires_in || 600,
|
|
128
|
+
interval: data.interval || 5
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async function pollForToken(baseUrl, deviceCode, interval, expiresIn) {
|
|
132
|
+
const startTime = Date.now();
|
|
133
|
+
const timeoutMs = expiresIn * 1e3;
|
|
134
|
+
while (true) {
|
|
135
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
136
|
+
throw new Error("Device authentication timed out");
|
|
137
|
+
}
|
|
138
|
+
await sleep(interval * 1e3);
|
|
139
|
+
const response = await fetch(`${baseUrl}/auth/device/token`, {
|
|
140
|
+
method: "POST",
|
|
141
|
+
headers: {
|
|
142
|
+
"Content-Type": "application/json"
|
|
143
|
+
},
|
|
144
|
+
body: JSON.stringify({
|
|
145
|
+
device_code: deviceCode,
|
|
146
|
+
client_id: CLIENT_ID
|
|
147
|
+
})
|
|
148
|
+
});
|
|
149
|
+
if (response.ok) {
|
|
150
|
+
const data = await response.json();
|
|
151
|
+
return {
|
|
152
|
+
apiKey: data.apiKey,
|
|
153
|
+
agentId: data.agentId,
|
|
154
|
+
agentName: data.agentName,
|
|
155
|
+
expiresAt: data.expiresAt
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const errorData = await response.json().catch(() => ({}));
|
|
159
|
+
const errorCode = errorData.error || errorData.code;
|
|
160
|
+
if (errorCode === "authorization_pending") {
|
|
161
|
+
process.stdout.write(".");
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (errorCode === "slow_down") {
|
|
165
|
+
interval = Math.min(interval * 2, 30);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (errorCode === "expired_token") {
|
|
169
|
+
throw new Error("Device code expired. Please try again.");
|
|
170
|
+
}
|
|
171
|
+
if (errorCode === "access_denied") {
|
|
172
|
+
throw new Error("Authorization was denied.");
|
|
173
|
+
}
|
|
174
|
+
throw new Error(`Authentication failed: ${errorData.message || errorCode || response.statusText}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function promptForApiKey() {
|
|
178
|
+
const rl = readline.createInterface({
|
|
179
|
+
input: process.stdin,
|
|
180
|
+
output: process.stdout
|
|
181
|
+
});
|
|
182
|
+
const question = (prompt) => {
|
|
183
|
+
return new Promise((resolve) => {
|
|
184
|
+
rl.question(prompt, resolve);
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
console.log("\u{1F511} Manual Configuration\n");
|
|
188
|
+
console.log("Enter your Artyfacts credentials:\n");
|
|
189
|
+
const apiKey = await question("API Key: ");
|
|
190
|
+
const agentId = await question("Agent ID: ");
|
|
191
|
+
const agentName = await question("Agent Name (optional): ");
|
|
192
|
+
rl.close();
|
|
193
|
+
if (!apiKey || !agentId) {
|
|
194
|
+
throw new Error("API Key and Agent ID are required");
|
|
195
|
+
}
|
|
196
|
+
const credentials = {
|
|
197
|
+
apiKey: apiKey.trim(),
|
|
198
|
+
agentId: agentId.trim(),
|
|
199
|
+
agentName: agentName.trim() || void 0
|
|
200
|
+
};
|
|
201
|
+
saveCredentials(credentials);
|
|
202
|
+
console.log("\n\u2705 Credentials saved!");
|
|
203
|
+
return credentials;
|
|
204
|
+
}
|
|
205
|
+
function sleep(ms) {
|
|
206
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
207
|
+
}
|
|
208
|
+
async function getCredentials(options) {
|
|
209
|
+
if (!options?.forceAuth) {
|
|
210
|
+
const existing = loadCredentials();
|
|
211
|
+
if (existing) {
|
|
212
|
+
return existing;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return runDeviceAuth(options?.baseUrl);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/executor.ts
|
|
219
|
+
var import_child_process = require("child_process");
|
|
220
|
+
|
|
221
|
+
// src/context.ts
|
|
222
|
+
var ContextFetcher = class {
|
|
223
|
+
constructor(config) {
|
|
224
|
+
this.config = config;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Fetch full context for a task
|
|
228
|
+
*/
|
|
229
|
+
async fetchTaskContext(taskId) {
|
|
230
|
+
const response = await fetch(
|
|
231
|
+
`${this.config.baseUrl}/tasks/${taskId}/context`,
|
|
232
|
+
{
|
|
233
|
+
headers: {
|
|
234
|
+
"Authorization": `Bearer ${this.config.apiKey}`,
|
|
235
|
+
"Accept": "application/json"
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
if (!response.ok) {
|
|
240
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
241
|
+
throw new Error(`Failed to fetch task context: ${response.status} - ${errorText}`);
|
|
242
|
+
}
|
|
243
|
+
const data = await response.json();
|
|
244
|
+
return data;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
function buildPromptWithContext(context) {
|
|
248
|
+
const parts = [];
|
|
249
|
+
parts.push(`You are an AI agent working within the Artyfacts task management system.
|
|
250
|
+
|
|
251
|
+
Your job is to complete the assigned task. You have full context about the organization, project, and related work.
|
|
252
|
+
|
|
253
|
+
## Available Tools
|
|
254
|
+
|
|
255
|
+
You have access to Artyfacts MCP tools. USE THEM to complete your task:
|
|
256
|
+
|
|
257
|
+
- **create_artifact** - Create new artifacts (documents, specs, reports)
|
|
258
|
+
- **create_section** - Add sections to artifacts (content, tasks, decisions)
|
|
259
|
+
- **update_section** - Update existing sections
|
|
260
|
+
- **create_agent** - Create new AI agents with specific roles
|
|
261
|
+
- **list_artifacts** - Query existing artifacts
|
|
262
|
+
- **list_sections** - Query sections within an artifact
|
|
263
|
+
- **complete_task** - Mark a task as complete
|
|
264
|
+
- **block_task** - Block a task with a reason
|
|
265
|
+
- **create_blocker** - Create a decision blocker
|
|
266
|
+
|
|
267
|
+
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.
|
|
268
|
+
|
|
269
|
+
## Guidelines
|
|
270
|
+
|
|
271
|
+
- Be thorough but concise
|
|
272
|
+
- USE THE TOOLS to take action, don't just analyze
|
|
273
|
+
- If the task requires creating something, use create_artifact or create_section
|
|
274
|
+
- If the task requires creating agents, use create_agent
|
|
275
|
+
- If you cannot complete the task, explain why
|
|
276
|
+
|
|
277
|
+
Format your response as follows:
|
|
278
|
+
1. First, use the tools to complete the task
|
|
279
|
+
2. Then summarize what you did
|
|
280
|
+
3. End with a brief summary line starting with "SUMMARY:"`);
|
|
281
|
+
parts.push("");
|
|
282
|
+
parts.push("---");
|
|
283
|
+
parts.push("");
|
|
284
|
+
parts.push("## Organization Context");
|
|
285
|
+
parts.push(`**${context.organization.name}**`);
|
|
286
|
+
if (context.organization.context) {
|
|
287
|
+
parts.push("");
|
|
288
|
+
parts.push(formatOrgContext(context.organization.context));
|
|
289
|
+
}
|
|
290
|
+
parts.push("");
|
|
291
|
+
if (context.project) {
|
|
292
|
+
parts.push(`## Project: ${context.project.name}`);
|
|
293
|
+
if (context.project.description) {
|
|
294
|
+
parts.push(context.project.description);
|
|
295
|
+
}
|
|
296
|
+
parts.push("");
|
|
297
|
+
}
|
|
298
|
+
parts.push(`## Artifact: ${context.artifact.title}`);
|
|
299
|
+
if (context.artifact.summary) {
|
|
300
|
+
parts.push(context.artifact.summary);
|
|
301
|
+
}
|
|
302
|
+
if (context.artifact.description) {
|
|
303
|
+
parts.push("");
|
|
304
|
+
parts.push(context.artifact.description);
|
|
305
|
+
}
|
|
306
|
+
parts.push("");
|
|
307
|
+
const relatedSections = context.artifact.sections.filter(
|
|
308
|
+
(s) => s.id !== context.task.id
|
|
309
|
+
);
|
|
310
|
+
if (relatedSections.length > 0) {
|
|
311
|
+
parts.push("### Related Sections:");
|
|
312
|
+
for (const section of relatedSections) {
|
|
313
|
+
const preview = section.content ? section.content.substring(0, 200) + (section.content.length > 200 ? "..." : "") : "No content";
|
|
314
|
+
const statusBadge = section.task_status ? ` [${section.task_status}]` : "";
|
|
315
|
+
parts.push(`- **${section.heading}**${statusBadge}: ${preview}`);
|
|
316
|
+
}
|
|
317
|
+
parts.push("");
|
|
318
|
+
}
|
|
319
|
+
parts.push("---");
|
|
320
|
+
parts.push("");
|
|
321
|
+
parts.push(`## Your Task: ${context.task.heading}`);
|
|
322
|
+
if (context.task.priority) {
|
|
323
|
+
const priorityLabels = ["\u{1F534} High", "\u{1F7E1} Medium", "\u{1F7E2} Low"];
|
|
324
|
+
parts.push(`**Priority:** ${priorityLabels[context.task.priority - 1] || "Medium"}`);
|
|
325
|
+
}
|
|
326
|
+
parts.push("");
|
|
327
|
+
parts.push("### Description");
|
|
328
|
+
parts.push(context.task.content || "No additional description provided.");
|
|
329
|
+
parts.push("");
|
|
330
|
+
if (context.task.expected_output) {
|
|
331
|
+
parts.push("### Expected Output");
|
|
332
|
+
if (context.task.expected_output.format) {
|
|
333
|
+
parts.push(`**Format:** ${context.task.expected_output.format}`);
|
|
334
|
+
}
|
|
335
|
+
if (context.task.expected_output.requirements && context.task.expected_output.requirements.length > 0) {
|
|
336
|
+
parts.push("**Requirements:**");
|
|
337
|
+
for (const req of context.task.expected_output.requirements) {
|
|
338
|
+
parts.push(`- ${req}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
parts.push("");
|
|
342
|
+
}
|
|
343
|
+
parts.push("---");
|
|
344
|
+
parts.push("");
|
|
345
|
+
parts.push("Complete this task and provide your output below.");
|
|
346
|
+
return parts.join("\n");
|
|
347
|
+
}
|
|
348
|
+
function formatOrgContext(context) {
|
|
349
|
+
const trimmed = context.trim();
|
|
350
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
351
|
+
try {
|
|
352
|
+
const parsed = JSON.parse(trimmed);
|
|
353
|
+
return formatContextObject(parsed);
|
|
354
|
+
} catch {
|
|
355
|
+
return context;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return context;
|
|
359
|
+
}
|
|
360
|
+
function formatContextObject(obj, indent = "") {
|
|
361
|
+
if (typeof obj !== "object" || obj === null) {
|
|
362
|
+
return String(obj);
|
|
363
|
+
}
|
|
364
|
+
if (Array.isArray(obj)) {
|
|
365
|
+
return obj.map((item) => `${indent}- ${formatContextObject(item, indent + " ")}`).join("\n");
|
|
366
|
+
}
|
|
367
|
+
const lines = [];
|
|
368
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
369
|
+
const label = key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
370
|
+
if (typeof value === "object" && value !== null) {
|
|
371
|
+
lines.push(`${indent}**${label}:**`);
|
|
372
|
+
lines.push(formatContextObject(value, indent + " "));
|
|
373
|
+
} else {
|
|
374
|
+
lines.push(`${indent}- **${label}:** ${value}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return lines.join("\n");
|
|
378
|
+
}
|
|
379
|
+
function createContextFetcher(config) {
|
|
380
|
+
return new ContextFetcher(config);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/executor.ts
|
|
384
|
+
var DEFAULT_TIMEOUT = 5 * 60 * 1e3;
|
|
385
|
+
var DEFAULT_SYSTEM_PROMPT = `You are an AI agent working within the Artyfacts task management system.
|
|
386
|
+
|
|
387
|
+
Your job is to complete tasks assigned to you using the available tools.
|
|
388
|
+
|
|
389
|
+
## Available Tools
|
|
390
|
+
|
|
391
|
+
You have access to Artyfacts MCP tools. USE THEM to complete your task:
|
|
392
|
+
|
|
393
|
+
- **create_artifact** - Create new artifacts (documents, specs, reports)
|
|
394
|
+
- **create_section** - Add sections to artifacts (content, tasks, decisions)
|
|
395
|
+
- **update_section** - Update existing sections
|
|
396
|
+
- **create_agent** - Create new AI agents with specific roles
|
|
397
|
+
- **list_artifacts** - Query existing artifacts
|
|
398
|
+
- **list_sections** - Query sections within an artifact
|
|
399
|
+
- **complete_task** - Mark a task as complete
|
|
400
|
+
- **block_task** - Block a task with a reason
|
|
401
|
+
- **create_blocker** - Create a decision blocker
|
|
402
|
+
|
|
403
|
+
IMPORTANT: When asked to create agents or update artifacts, USE THE TOOLS. Don't just describe what you would do - actually do it.
|
|
404
|
+
|
|
405
|
+
## Guidelines
|
|
406
|
+
|
|
407
|
+
- USE THE TOOLS to take action
|
|
408
|
+
- If creating something, use create_artifact or create_section
|
|
409
|
+
- If creating agents, use create_agent
|
|
410
|
+
- If you cannot complete the task, explain why
|
|
411
|
+
|
|
412
|
+
Format your response as follows:
|
|
413
|
+
1. First, use the tools to complete the task
|
|
414
|
+
2. Summarize what you accomplished
|
|
415
|
+
3. End with a brief summary line starting with "SUMMARY:"`;
|
|
416
|
+
var OpenClawExecutor = class {
|
|
417
|
+
constructor(config = {}) {
|
|
418
|
+
this.contextFetcher = null;
|
|
419
|
+
this.config = {
|
|
420
|
+
...config,
|
|
421
|
+
timeout: config.timeout || DEFAULT_TIMEOUT,
|
|
422
|
+
openclawPath: config.openclawPath || "openclaw"
|
|
423
|
+
};
|
|
424
|
+
if (config.baseUrl && config.apiKey) {
|
|
425
|
+
this.contextFetcher = createContextFetcher({
|
|
426
|
+
baseUrl: config.baseUrl,
|
|
427
|
+
apiKey: config.apiKey
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Check if OpenClaw CLI is installed
|
|
433
|
+
*/
|
|
434
|
+
async isInstalled() {
|
|
435
|
+
try {
|
|
436
|
+
const result = (0, import_child_process.spawnSync)(this.config.openclawPath || "openclaw", ["--version"], {
|
|
437
|
+
encoding: "utf-8",
|
|
438
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
439
|
+
});
|
|
440
|
+
return result.status === 0;
|
|
441
|
+
} catch {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Get OpenClaw version
|
|
447
|
+
*/
|
|
448
|
+
async getVersion() {
|
|
449
|
+
try {
|
|
450
|
+
const result = (0, import_child_process.spawnSync)(this.config.openclawPath || "openclaw", ["--version"], {
|
|
451
|
+
encoding: "utf-8",
|
|
452
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
453
|
+
});
|
|
454
|
+
if (result.status === 0) {
|
|
455
|
+
return result.stdout.trim();
|
|
456
|
+
}
|
|
457
|
+
return null;
|
|
458
|
+
} catch {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Execute a task using OpenClaw CLI
|
|
464
|
+
*/
|
|
465
|
+
async execute(task) {
|
|
466
|
+
try {
|
|
467
|
+
let prompt;
|
|
468
|
+
let fullContext = null;
|
|
469
|
+
const useFullContext = this.config.useFullContext !== false && this.contextFetcher;
|
|
470
|
+
if (useFullContext) {
|
|
471
|
+
try {
|
|
472
|
+
fullContext = await this.contextFetcher.fetchTaskContext(task.taskId);
|
|
473
|
+
prompt = buildPromptWithContext(fullContext);
|
|
474
|
+
console.log(" \u{1F4DA} Using full context (org, project, artifact, related sections)");
|
|
475
|
+
} catch (contextError) {
|
|
476
|
+
console.warn(" \u26A0\uFE0F Could not fetch full context, using minimal prompt");
|
|
477
|
+
console.warn(` ${contextError instanceof Error ? contextError.message : contextError}`);
|
|
478
|
+
prompt = this.buildTaskPrompt(task);
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
prompt = this.buildTaskPrompt(task);
|
|
482
|
+
}
|
|
483
|
+
const output = await this.runOpenClaw(prompt);
|
|
484
|
+
const { content, summary } = this.parseResponse(output, task.heading);
|
|
485
|
+
return {
|
|
486
|
+
success: true,
|
|
487
|
+
output: content,
|
|
488
|
+
summary,
|
|
489
|
+
promptUsed: prompt
|
|
490
|
+
};
|
|
491
|
+
} catch (error) {
|
|
492
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
493
|
+
return {
|
|
494
|
+
success: false,
|
|
495
|
+
output: "",
|
|
496
|
+
summary: `Failed: ${errorMessage}`,
|
|
497
|
+
error: errorMessage
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Run OpenClaw CLI with the given prompt
|
|
503
|
+
*/
|
|
504
|
+
runOpenClaw(prompt) {
|
|
505
|
+
return new Promise((resolve, reject) => {
|
|
506
|
+
const openclawPath = this.config.openclawPath || "openclaw";
|
|
507
|
+
const args = [
|
|
508
|
+
"prompt",
|
|
509
|
+
"--output-format",
|
|
510
|
+
"text"
|
|
511
|
+
// Get plain text output
|
|
512
|
+
];
|
|
513
|
+
if (this.config.sessionKey) {
|
|
514
|
+
args.push("--session", this.config.sessionKey);
|
|
515
|
+
}
|
|
516
|
+
args.push(prompt);
|
|
517
|
+
const proc = (0, import_child_process.spawn)(openclawPath, args, {
|
|
518
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
519
|
+
timeout: this.config.timeout,
|
|
520
|
+
env: {
|
|
521
|
+
...process.env,
|
|
522
|
+
// Pass Artyfacts API key for MCP tools
|
|
523
|
+
ARTYFACTS_API_KEY: this.config.apiKey || process.env.ARTYFACTS_API_KEY || "",
|
|
524
|
+
ARTYFACTS_BASE_URL: this.config.baseUrl || "https://artyfacts.dev/api/v1"
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
let stdout = "";
|
|
528
|
+
let stderr = "";
|
|
529
|
+
proc.stdout.on("data", (data) => {
|
|
530
|
+
stdout += data.toString();
|
|
531
|
+
});
|
|
532
|
+
proc.stderr.on("data", (data) => {
|
|
533
|
+
stderr += data.toString();
|
|
534
|
+
});
|
|
535
|
+
proc.on("close", (code) => {
|
|
536
|
+
if (code === 0) {
|
|
537
|
+
resolve(stdout.trim());
|
|
538
|
+
} else {
|
|
539
|
+
reject(new Error(stderr || `OpenClaw exited with code ${code}`));
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
proc.on("error", (err) => {
|
|
543
|
+
if (err.code === "ENOENT") {
|
|
544
|
+
reject(new Error(
|
|
545
|
+
"OpenClaw CLI not found. Please install it:\n npm install -g openclaw"
|
|
546
|
+
));
|
|
547
|
+
} else {
|
|
548
|
+
reject(err);
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Build the task prompt
|
|
555
|
+
*/
|
|
556
|
+
buildTaskPrompt(task) {
|
|
557
|
+
const parts = [];
|
|
558
|
+
const systemPrompt = this.config.systemPromptPrefix ? `${this.config.systemPromptPrefix}
|
|
559
|
+
|
|
560
|
+
${DEFAULT_SYSTEM_PROMPT}` : DEFAULT_SYSTEM_PROMPT;
|
|
561
|
+
parts.push(systemPrompt);
|
|
562
|
+
parts.push("");
|
|
563
|
+
parts.push("---");
|
|
564
|
+
parts.push("");
|
|
565
|
+
parts.push(`# Task: ${task.heading}`);
|
|
566
|
+
parts.push("");
|
|
567
|
+
if (task.artifactTitle) {
|
|
568
|
+
parts.push(`**Part of:** ${task.artifactTitle}`);
|
|
569
|
+
parts.push(`**Artifact ID:** ${task.artifactId}`);
|
|
570
|
+
parts.push("");
|
|
571
|
+
}
|
|
572
|
+
if (task.priority) {
|
|
573
|
+
const priorityLabel = ["High", "Medium", "Low"][task.priority - 1] || "Unknown";
|
|
574
|
+
parts.push(`**Priority:** ${priorityLabel}`);
|
|
575
|
+
parts.push("");
|
|
576
|
+
}
|
|
577
|
+
parts.push("## Description");
|
|
578
|
+
parts.push("");
|
|
579
|
+
parts.push(task.content || "No description provided.");
|
|
580
|
+
parts.push("");
|
|
581
|
+
if (task.context && Object.keys(task.context).length > 0) {
|
|
582
|
+
parts.push("## Additional Context");
|
|
583
|
+
parts.push("");
|
|
584
|
+
parts.push("```json");
|
|
585
|
+
parts.push(JSON.stringify(task.context, null, 2));
|
|
586
|
+
parts.push("```");
|
|
587
|
+
parts.push("");
|
|
588
|
+
}
|
|
589
|
+
parts.push("---");
|
|
590
|
+
parts.push("");
|
|
591
|
+
parts.push("Please complete this task using the available Artyfacts tools.");
|
|
592
|
+
parts.push("When done, provide a summary of what you accomplished.");
|
|
593
|
+
return parts.join("\n");
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Parse the response to extract content and summary
|
|
597
|
+
*/
|
|
598
|
+
parseResponse(output, fallbackSummary) {
|
|
599
|
+
const summaryMatch = output.match(/SUMMARY:\s*(.+?)(?:\n|$)/i);
|
|
600
|
+
if (summaryMatch) {
|
|
601
|
+
const summary2 = summaryMatch[1].trim();
|
|
602
|
+
const content = output.replace(/SUMMARY:\s*.+?(?:\n|$)/i, "").trim();
|
|
603
|
+
return { content, summary: summary2 };
|
|
604
|
+
}
|
|
605
|
+
const lines = output.split("\n").filter((l) => l.trim());
|
|
606
|
+
const summary = lines.length > 0 ? lines[lines.length - 1].slice(0, 200) : `Completed: ${fallbackSummary}`;
|
|
607
|
+
return { content: output, summary };
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
function createExecutor(config) {
|
|
611
|
+
return new OpenClawExecutor(config);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// src/listener.ts
|
|
615
|
+
var import_eventsource = __toESM(require("eventsource"));
|
|
616
|
+
var DEFAULT_BASE_URL2 = "https://artyfacts.dev/api/v1";
|
|
617
|
+
var EVENT_TYPES = [
|
|
618
|
+
"connected",
|
|
619
|
+
"heartbeat",
|
|
620
|
+
"task_assigned",
|
|
621
|
+
"task_unblocked",
|
|
622
|
+
"blocker_resolved",
|
|
623
|
+
"notification",
|
|
624
|
+
"mcp_connect_request",
|
|
625
|
+
"connection_status"
|
|
626
|
+
];
|
|
627
|
+
var ArtyfactsListener = class {
|
|
628
|
+
constructor(config) {
|
|
629
|
+
this.eventSource = null;
|
|
630
|
+
this.callbacks = /* @__PURE__ */ new Map();
|
|
631
|
+
this.allCallbacks = /* @__PURE__ */ new Set();
|
|
632
|
+
this.state = "disconnected";
|
|
633
|
+
this.reconnectAttempts = 0;
|
|
634
|
+
this.maxReconnectAttempts = 10;
|
|
635
|
+
this.reconnectDelay = 1e3;
|
|
636
|
+
if (!config.apiKey) {
|
|
637
|
+
throw new Error("API key is required");
|
|
638
|
+
}
|
|
639
|
+
if (!config.agentId) {
|
|
640
|
+
throw new Error("Agent ID is required");
|
|
641
|
+
}
|
|
642
|
+
this.config = {
|
|
643
|
+
...config,
|
|
644
|
+
baseUrl: config.baseUrl || DEFAULT_BASE_URL2
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Get current connection state
|
|
649
|
+
*/
|
|
650
|
+
get connectionState() {
|
|
651
|
+
return this.state;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Check if connected
|
|
655
|
+
*/
|
|
656
|
+
get isConnected() {
|
|
657
|
+
return this.state === "connected";
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Subscribe to all events
|
|
661
|
+
*/
|
|
662
|
+
subscribe(callback) {
|
|
663
|
+
this.allCallbacks.add(callback);
|
|
664
|
+
return () => {
|
|
665
|
+
this.allCallbacks.delete(callback);
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Subscribe to a specific event type
|
|
670
|
+
*/
|
|
671
|
+
on(type, callback) {
|
|
672
|
+
if (!this.callbacks.has(type)) {
|
|
673
|
+
this.callbacks.set(type, /* @__PURE__ */ new Set());
|
|
674
|
+
}
|
|
675
|
+
this.callbacks.get(type).add(callback);
|
|
676
|
+
return () => {
|
|
677
|
+
const typeCallbacks = this.callbacks.get(type);
|
|
678
|
+
if (typeCallbacks) {
|
|
679
|
+
typeCallbacks.delete(callback);
|
|
680
|
+
if (typeCallbacks.size === 0) {
|
|
681
|
+
this.callbacks.delete(type);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Connect to the SSE stream
|
|
688
|
+
*/
|
|
689
|
+
connect() {
|
|
690
|
+
if (this.eventSource) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
this.setState("connecting");
|
|
694
|
+
const url = new URL(`${this.config.baseUrl}/events/stream`);
|
|
695
|
+
url.searchParams.set("apiKey", this.config.apiKey);
|
|
696
|
+
url.searchParams.set("agentId", this.config.agentId);
|
|
697
|
+
this.eventSource = new import_eventsource.default(url.toString(), {
|
|
698
|
+
headers: {
|
|
699
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
this.eventSource.onopen = () => {
|
|
703
|
+
this.reconnectAttempts = 0;
|
|
704
|
+
this.reconnectDelay = 1e3;
|
|
705
|
+
this.setState("connected");
|
|
706
|
+
};
|
|
707
|
+
this.eventSource.onmessage = (event) => {
|
|
708
|
+
this.handleMessage(event);
|
|
709
|
+
};
|
|
710
|
+
this.eventSource.onerror = (event) => {
|
|
711
|
+
this.handleError(event);
|
|
712
|
+
};
|
|
713
|
+
for (const eventType of EVENT_TYPES) {
|
|
714
|
+
this.eventSource.addEventListener(eventType, (event) => {
|
|
715
|
+
this.handleMessage(event, eventType);
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Disconnect from the SSE stream
|
|
721
|
+
*/
|
|
722
|
+
disconnect() {
|
|
723
|
+
if (this.eventSource) {
|
|
724
|
+
this.eventSource.close();
|
|
725
|
+
this.eventSource = null;
|
|
726
|
+
}
|
|
727
|
+
this.setState("disconnected");
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Reconnect to the SSE stream
|
|
731
|
+
*/
|
|
732
|
+
reconnect() {
|
|
733
|
+
this.disconnect();
|
|
734
|
+
this.connect();
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Handle incoming SSE message
|
|
738
|
+
*/
|
|
739
|
+
handleMessage(event, eventType) {
|
|
740
|
+
try {
|
|
741
|
+
const data = JSON.parse(event.data);
|
|
742
|
+
const rawData = data.data || data;
|
|
743
|
+
const normalizedData = rawData.task_id ? {
|
|
744
|
+
taskId: rawData.task_id,
|
|
745
|
+
sectionId: rawData.section_id,
|
|
746
|
+
artifactId: rawData.artifact_id,
|
|
747
|
+
artifactTitle: rawData.artifact_title,
|
|
748
|
+
heading: rawData.heading,
|
|
749
|
+
content: rawData.content,
|
|
750
|
+
assignedTo: rawData.assigned_to,
|
|
751
|
+
assignedAt: rawData.assigned_at,
|
|
752
|
+
priority: rawData.priority,
|
|
753
|
+
...rawData
|
|
754
|
+
// Keep original fields too
|
|
755
|
+
} : rawData;
|
|
756
|
+
const artyfactsEvent = {
|
|
757
|
+
type: eventType || data.type || "unknown",
|
|
758
|
+
timestamp: data.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
759
|
+
data: normalizedData
|
|
760
|
+
};
|
|
761
|
+
const typeCallbacks = this.callbacks.get(artyfactsEvent.type);
|
|
762
|
+
if (typeCallbacks) {
|
|
763
|
+
for (const callback of typeCallbacks) {
|
|
764
|
+
this.safeCallCallback(callback, artyfactsEvent);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
for (const callback of this.allCallbacks) {
|
|
768
|
+
this.safeCallCallback(callback, artyfactsEvent);
|
|
769
|
+
}
|
|
770
|
+
} catch (err) {
|
|
771
|
+
console.error("[Listener] Failed to parse SSE message:", event.data, err);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Safely call a callback, handling async and errors
|
|
776
|
+
*/
|
|
777
|
+
async safeCallCallback(callback, event) {
|
|
778
|
+
try {
|
|
779
|
+
await callback(event);
|
|
780
|
+
} catch (err) {
|
|
781
|
+
console.error(`[Listener] Error in event callback for '${event.type}':`, err);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Handle SSE error
|
|
786
|
+
*/
|
|
787
|
+
handleError(event) {
|
|
788
|
+
if (this.eventSource?.readyState === import_eventsource.default.CONNECTING) {
|
|
789
|
+
this.setState("reconnecting");
|
|
790
|
+
} else if (this.eventSource?.readyState === import_eventsource.default.CLOSED) {
|
|
791
|
+
this.setState("disconnected");
|
|
792
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
793
|
+
this.reconnectAttempts++;
|
|
794
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, 3e4);
|
|
795
|
+
console.log(
|
|
796
|
+
`[Listener] Connection lost, reconnecting in ${this.reconnectDelay / 1e3}s (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
|
|
797
|
+
);
|
|
798
|
+
setTimeout(() => {
|
|
799
|
+
if (this.state === "disconnected") {
|
|
800
|
+
this.connect();
|
|
801
|
+
}
|
|
802
|
+
}, this.reconnectDelay);
|
|
803
|
+
} else {
|
|
804
|
+
const error = new Error("Max reconnection attempts reached");
|
|
805
|
+
this.config.onError?.(error);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Update connection state
|
|
811
|
+
*/
|
|
812
|
+
setState(state) {
|
|
813
|
+
if (this.state !== state) {
|
|
814
|
+
this.state = state;
|
|
815
|
+
this.config.onStateChange?.(state);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
function createListener(config) {
|
|
820
|
+
return new ArtyfactsListener(config);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/mcp.ts
|
|
824
|
+
var import_child_process2 = require("child_process");
|
|
825
|
+
var DEFAULT_MCP_NAME = "artyfacts";
|
|
826
|
+
var DEFAULT_BASE_URL3 = "https://artyfacts.dev/api/v1";
|
|
827
|
+
var McpHandler = class {
|
|
828
|
+
constructor(config) {
|
|
829
|
+
this.config = {
|
|
830
|
+
name: DEFAULT_MCP_NAME,
|
|
831
|
+
baseUrl: DEFAULT_BASE_URL3,
|
|
832
|
+
...config
|
|
833
|
+
};
|
|
834
|
+
this.openclawPath = config.openclawPath || "openclaw";
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Check if Artyfacts MCP server is already configured
|
|
838
|
+
*/
|
|
839
|
+
isConfigured() {
|
|
840
|
+
try {
|
|
841
|
+
const result = (0, import_child_process2.spawnSync)(this.openclawPath, ["mcp", "show", this.config.name || DEFAULT_MCP_NAME], {
|
|
842
|
+
encoding: "utf-8",
|
|
843
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
844
|
+
});
|
|
845
|
+
if (result.status === 0) {
|
|
846
|
+
return {
|
|
847
|
+
configured: true,
|
|
848
|
+
name: this.config.name
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
return { configured: false };
|
|
852
|
+
} catch (error) {
|
|
853
|
+
return {
|
|
854
|
+
configured: false,
|
|
855
|
+
error: error instanceof Error ? error.message : String(error)
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Configure Artyfacts MCP server in OpenClaw
|
|
861
|
+
*
|
|
862
|
+
* Uses: openclaw mcp set <name> --command "npx" --args "-y @artyfacts/mcp-server"
|
|
863
|
+
*/
|
|
864
|
+
configure() {
|
|
865
|
+
console.log("\u{1F527} Configuring Artyfacts tools for OpenClaw...");
|
|
866
|
+
try {
|
|
867
|
+
const result = (0, import_child_process2.spawnSync)(this.openclawPath, [
|
|
868
|
+
"mcp",
|
|
869
|
+
"set",
|
|
870
|
+
this.config.name || DEFAULT_MCP_NAME,
|
|
871
|
+
"--command",
|
|
872
|
+
"npx",
|
|
873
|
+
"--args",
|
|
874
|
+
"-y",
|
|
875
|
+
"--args",
|
|
876
|
+
"@artyfacts/mcp-server",
|
|
877
|
+
"--env",
|
|
878
|
+
`ARTYFACTS_API_KEY=${this.config.apiKey}`,
|
|
879
|
+
"--env",
|
|
880
|
+
`ARTYFACTS_BASE_URL=${this.config.baseUrl || DEFAULT_BASE_URL3}`
|
|
881
|
+
], {
|
|
882
|
+
encoding: "utf-8",
|
|
883
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
884
|
+
});
|
|
885
|
+
if (result.status === 0) {
|
|
886
|
+
console.log("\u2705 MCP tools configured! OpenClaw now has access to Artyfacts.");
|
|
887
|
+
return true;
|
|
888
|
+
} else {
|
|
889
|
+
return this.configureAlternative();
|
|
890
|
+
}
|
|
891
|
+
} catch (error) {
|
|
892
|
+
console.log("\u26A0\uFE0F Could not configure MCP:", error instanceof Error ? error.message : error);
|
|
893
|
+
this.printManualInstructions();
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Alternative configuration approach using JSON config
|
|
899
|
+
*/
|
|
900
|
+
configureAlternative() {
|
|
901
|
+
try {
|
|
902
|
+
const mcpConfig = JSON.stringify({
|
|
903
|
+
command: "npx",
|
|
904
|
+
args: ["-y", "@artyfacts/mcp-server"],
|
|
905
|
+
env: {
|
|
906
|
+
ARTYFACTS_API_KEY: this.config.apiKey,
|
|
907
|
+
ARTYFACTS_BASE_URL: this.config.baseUrl || DEFAULT_BASE_URL3
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
const result = (0, import_child_process2.spawnSync)(this.openclawPath, [
|
|
911
|
+
"mcp",
|
|
912
|
+
"set",
|
|
913
|
+
this.config.name || DEFAULT_MCP_NAME,
|
|
914
|
+
"--config",
|
|
915
|
+
mcpConfig
|
|
916
|
+
], {
|
|
917
|
+
encoding: "utf-8",
|
|
918
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
919
|
+
});
|
|
920
|
+
if (result.status === 0) {
|
|
921
|
+
console.log("\u2705 MCP tools configured! OpenClaw now has access to Artyfacts.");
|
|
922
|
+
return true;
|
|
923
|
+
} else {
|
|
924
|
+
console.log("\u26A0\uFE0F Could not configure MCP:", result.stderr || result.stdout);
|
|
925
|
+
this.printManualInstructions();
|
|
926
|
+
return false;
|
|
927
|
+
}
|
|
928
|
+
} catch (error) {
|
|
929
|
+
console.log("\u26A0\uFE0F Could not configure MCP:", error instanceof Error ? error.message : error);
|
|
930
|
+
this.printManualInstructions();
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Print manual configuration instructions
|
|
936
|
+
*/
|
|
937
|
+
printManualInstructions() {
|
|
938
|
+
console.log("");
|
|
939
|
+
console.log("You can manually add the Artyfacts MCP server with:");
|
|
940
|
+
console.log("");
|
|
941
|
+
console.log(` openclaw mcp set ${this.config.name || DEFAULT_MCP_NAME} \\`);
|
|
942
|
+
console.log(` --command "npx" \\`);
|
|
943
|
+
console.log(` --args "-y" --args "@artyfacts/mcp-server" \\`);
|
|
944
|
+
console.log(` --env "ARTYFACTS_API_KEY=${this.config.apiKey}" \\`);
|
|
945
|
+
console.log(` --env "ARTYFACTS_BASE_URL=${this.config.baseUrl || DEFAULT_BASE_URL3}"`);
|
|
946
|
+
console.log("");
|
|
947
|
+
console.log("Or add to your openclaw.json:");
|
|
948
|
+
console.log("");
|
|
949
|
+
console.log(` "mcp": {`);
|
|
950
|
+
console.log(` "${this.config.name || DEFAULT_MCP_NAME}": {`);
|
|
951
|
+
console.log(` "command": "npx",`);
|
|
952
|
+
console.log(` "args": ["-y", "@artyfacts/mcp-server"],`);
|
|
953
|
+
console.log(` "env": {`);
|
|
954
|
+
console.log(` "ARTYFACTS_API_KEY": "<your-api-key>",`);
|
|
955
|
+
console.log(` "ARTYFACTS_BASE_URL": "${this.config.baseUrl || DEFAULT_BASE_URL3}"`);
|
|
956
|
+
console.log(` }`);
|
|
957
|
+
console.log(` }`);
|
|
958
|
+
console.log(` }`);
|
|
959
|
+
console.log("");
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Remove Artyfacts MCP configuration
|
|
963
|
+
*/
|
|
964
|
+
remove() {
|
|
965
|
+
try {
|
|
966
|
+
const result = (0, import_child_process2.spawnSync)(this.openclawPath, [
|
|
967
|
+
"mcp",
|
|
968
|
+
"unset",
|
|
969
|
+
this.config.name || DEFAULT_MCP_NAME
|
|
970
|
+
], {
|
|
971
|
+
encoding: "utf-8",
|
|
972
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
973
|
+
});
|
|
974
|
+
if (result.status === 0) {
|
|
975
|
+
console.log("\u2705 Artyfacts MCP configuration removed.");
|
|
976
|
+
return true;
|
|
977
|
+
} else {
|
|
978
|
+
console.log("\u26A0\uFE0F Could not remove MCP config:", result.stderr || result.stdout);
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
} catch (error) {
|
|
982
|
+
console.log("\u26A0\uFE0F Could not remove MCP config:", error instanceof Error ? error.message : error);
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Ensure MCP is configured, configure if needed
|
|
988
|
+
*/
|
|
989
|
+
ensureConfigured() {
|
|
990
|
+
const status = this.isConfigured();
|
|
991
|
+
if (status.configured) {
|
|
992
|
+
console.log("\u2705 Artyfacts MCP tools already configured");
|
|
993
|
+
return true;
|
|
994
|
+
}
|
|
995
|
+
return this.configure();
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
function createMcpHandler(config) {
|
|
999
|
+
return new McpHandler(config);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/introspect.ts
|
|
1003
|
+
var import_promises = require("fs/promises");
|
|
1004
|
+
var import_path = require("path");
|
|
1005
|
+
var import_os = require("os");
|
|
1006
|
+
var import_child_process3 = require("child_process");
|
|
1007
|
+
var OPENCLAW_DIR = (0, import_path.join)((0, import_os.homedir)(), ".openclaw");
|
|
1008
|
+
var CONFIG_FILE = (0, import_path.join)(OPENCLAW_DIR, "openclaw.json");
|
|
1009
|
+
var CRON_FILE = (0, import_path.join)(OPENCLAW_DIR, "cron", "jobs.json");
|
|
1010
|
+
var SKILLS_DIR = (0, import_path.join)(OPENCLAW_DIR, "skills");
|
|
1011
|
+
function parseJson5(content) {
|
|
1012
|
+
const stripped = content.split("\n").map((line) => {
|
|
1013
|
+
const commentIndex = line.indexOf("//");
|
|
1014
|
+
if (commentIndex === -1) return line;
|
|
1015
|
+
const beforeComment = line.slice(0, commentIndex);
|
|
1016
|
+
const quotes = (beforeComment.match(/"/g) || []).length;
|
|
1017
|
+
if (quotes % 2 === 0) {
|
|
1018
|
+
return beforeComment;
|
|
1019
|
+
}
|
|
1020
|
+
return line;
|
|
1021
|
+
}).join("\n");
|
|
1022
|
+
const noTrailing = stripped.replace(/,(\s*[}\]])/g, "$1");
|
|
1023
|
+
return JSON.parse(noTrailing);
|
|
1024
|
+
}
|
|
1025
|
+
async function readCronJobs() {
|
|
1026
|
+
try {
|
|
1027
|
+
const content = await (0, import_promises.readFile)(CRON_FILE, "utf-8");
|
|
1028
|
+
const jobs = JSON.parse(content);
|
|
1029
|
+
return Array.isArray(jobs) ? jobs : [];
|
|
1030
|
+
} catch (err) {
|
|
1031
|
+
return [];
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
async function readMainConfig() {
|
|
1035
|
+
try {
|
|
1036
|
+
const content = await (0, import_promises.readFile)(CONFIG_FILE, "utf-8");
|
|
1037
|
+
const config = parseJson5(content);
|
|
1038
|
+
const agentsConfig = config.agents;
|
|
1039
|
+
const defaults = agentsConfig?.defaults;
|
|
1040
|
+
const heartbeat = defaults?.heartbeat;
|
|
1041
|
+
const agentsList = agentsConfig?.list || [];
|
|
1042
|
+
const channelsConfig = config.channels;
|
|
1043
|
+
const channelNames = channelsConfig ? Object.keys(channelsConfig).filter((k) => k !== "defaults") : [];
|
|
1044
|
+
return {
|
|
1045
|
+
heartbeat: heartbeat || null,
|
|
1046
|
+
agents: agentsList,
|
|
1047
|
+
channels: channelNames
|
|
1048
|
+
};
|
|
1049
|
+
} catch (err) {
|
|
1050
|
+
return { heartbeat: null, agents: [], channels: [] };
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
async function readMcpServers() {
|
|
1054
|
+
try {
|
|
1055
|
+
const output = (0, import_child_process3.execSync)("openclaw mcp list --json 2>/dev/null", {
|
|
1056
|
+
encoding: "utf-8",
|
|
1057
|
+
timeout: 5e3
|
|
1058
|
+
});
|
|
1059
|
+
const servers = JSON.parse(output);
|
|
1060
|
+
return Array.isArray(servers) ? servers : [];
|
|
1061
|
+
} catch (err) {
|
|
1062
|
+
return [];
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
async function readSkills() {
|
|
1066
|
+
const skills = [];
|
|
1067
|
+
async function scanDir(dir, prefix = "") {
|
|
1068
|
+
try {
|
|
1069
|
+
const entries = await (0, import_promises.readdir)(dir, { withFileTypes: true });
|
|
1070
|
+
for (const entry of entries) {
|
|
1071
|
+
const fullPath = (0, import_path.join)(dir, entry.name);
|
|
1072
|
+
if (entry.isDirectory()) {
|
|
1073
|
+
await scanDir(fullPath, prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
1074
|
+
} else if (entry.name === "SKILL.md") {
|
|
1075
|
+
const skillName = prefix || "root";
|
|
1076
|
+
const content = await (0, import_promises.readFile)(fullPath, "utf-8");
|
|
1077
|
+
const lines = content.split("\n");
|
|
1078
|
+
const descLine = lines.find((l) => l.trim() && !l.startsWith("#"));
|
|
1079
|
+
skills.push({
|
|
1080
|
+
name: skillName,
|
|
1081
|
+
path: fullPath,
|
|
1082
|
+
description: descLine?.trim().slice(0, 200),
|
|
1083
|
+
content
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
} catch (err) {
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
await scanDir(SKILLS_DIR);
|
|
1091
|
+
return skills;
|
|
1092
|
+
}
|
|
1093
|
+
async function introspect() {
|
|
1094
|
+
const [crons, mainConfig, mcpServers, skills] = await Promise.all([
|
|
1095
|
+
readCronJobs(),
|
|
1096
|
+
readMainConfig(),
|
|
1097
|
+
readMcpServers(),
|
|
1098
|
+
readSkills()
|
|
1099
|
+
]);
|
|
1100
|
+
return {
|
|
1101
|
+
crons,
|
|
1102
|
+
heartbeat: mainConfig.heartbeat,
|
|
1103
|
+
agents: mainConfig.agents,
|
|
1104
|
+
mcpServers,
|
|
1105
|
+
skills,
|
|
1106
|
+
channels: mainConfig.channels
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
function summarize(config) {
|
|
1110
|
+
const parts = [];
|
|
1111
|
+
if (config.crons.length > 0) {
|
|
1112
|
+
parts.push(`${config.crons.length} cron job(s)`);
|
|
1113
|
+
}
|
|
1114
|
+
if (config.heartbeat) {
|
|
1115
|
+
parts.push(`heartbeat: ${config.heartbeat.every || "30m"}`);
|
|
1116
|
+
}
|
|
1117
|
+
if (config.agents.length > 0) {
|
|
1118
|
+
parts.push(`${config.agents.length} agent(s)`);
|
|
1119
|
+
}
|
|
1120
|
+
if (config.mcpServers.length > 0) {
|
|
1121
|
+
parts.push(`${config.mcpServers.length} MCP server(s)`);
|
|
1122
|
+
}
|
|
1123
|
+
if (config.skills.length > 0) {
|
|
1124
|
+
parts.push(`${config.skills.length} skill(s)`);
|
|
1125
|
+
}
|
|
1126
|
+
if (config.channels.length > 0) {
|
|
1127
|
+
parts.push(`channels: ${config.channels.join(", ")}`);
|
|
1128
|
+
}
|
|
1129
|
+
return parts.length > 0 ? parts.join(" | ") : "No configuration found";
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// src/cli.ts
|
|
1133
|
+
var VERSION = "0.1.0";
|
|
1134
|
+
var DEFAULT_BASE_URL4 = "https://artyfacts.dev/api/v1";
|
|
1135
|
+
function isMcpConfigured(openclawPath = "openclaw") {
|
|
1136
|
+
const handler = createMcpHandler({ apiKey: "", openclawPath });
|
|
1137
|
+
return handler.isConfigured().configured;
|
|
1138
|
+
}
|
|
1139
|
+
function configureMcp(apiKey, baseUrl, openclawPath = "openclaw") {
|
|
1140
|
+
const handler = createMcpHandler({ apiKey, baseUrl, openclawPath });
|
|
1141
|
+
return handler.configure();
|
|
1142
|
+
}
|
|
1143
|
+
function ensureMcpConfigured(apiKey, baseUrl, openclawPath = "openclaw") {
|
|
1144
|
+
if (isMcpConfigured(openclawPath)) {
|
|
1145
|
+
console.log("\u2705 Artyfacts MCP tools already configured");
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
configureMcp(apiKey, baseUrl, openclawPath);
|
|
1149
|
+
}
|
|
1150
|
+
var program = new import_commander.Command();
|
|
1151
|
+
program.name("artyfacts-openclaw").description("OpenClaw adapter for Artyfacts - Execute tasks using OpenClaw CLI").version(VERSION);
|
|
1152
|
+
program.command("run", { isDefault: true }).description("Start listening for and executing tasks").option("--base-url <url>", "Artyfacts API base URL", DEFAULT_BASE_URL4).option("--dry-run", "Print tasks but do not execute", false).option("--openclaw-path <path>", "Path to openclaw CLI", "openclaw").action(async (options) => {
|
|
1153
|
+
await runAgent(options);
|
|
1154
|
+
});
|
|
1155
|
+
program.command("login").description("Authenticate with Artyfacts").option("--manual", "Enter credentials manually instead of device auth").option("--base-url <url>", "Artyfacts API base URL", DEFAULT_BASE_URL4).action(async (options) => {
|
|
1156
|
+
try {
|
|
1157
|
+
if (options.manual) {
|
|
1158
|
+
await promptForApiKey();
|
|
1159
|
+
} else {
|
|
1160
|
+
await getCredentials({ baseUrl: options.baseUrl, forceAuth: true });
|
|
1161
|
+
}
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
console.error("\u274C Authentication failed:", error instanceof Error ? error.message : error);
|
|
1164
|
+
process.exit(1);
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1167
|
+
program.command("logout").description("Clear stored credentials").action(() => {
|
|
1168
|
+
clearCredentials();
|
|
1169
|
+
console.log("\u2705 Credentials cleared");
|
|
1170
|
+
});
|
|
1171
|
+
program.command("status").description("Check authentication and connection status").option("--openclaw-path <path>", "Path to openclaw CLI", "openclaw").action(async (options) => {
|
|
1172
|
+
const credentials = loadCredentials();
|
|
1173
|
+
if (!credentials) {
|
|
1174
|
+
console.log("\u274C Not authenticated");
|
|
1175
|
+
console.log(" Run: npx @artyfacts/openclaw login");
|
|
1176
|
+
process.exit(1);
|
|
1177
|
+
}
|
|
1178
|
+
console.log("\u2705 Authenticated");
|
|
1179
|
+
console.log(` Agent ID: ${credentials.agentId}`);
|
|
1180
|
+
if (credentials.agentName) {
|
|
1181
|
+
console.log(` Agent Name: ${credentials.agentName}`);
|
|
1182
|
+
}
|
|
1183
|
+
const executor = createExecutor({ openclawPath: options.openclawPath });
|
|
1184
|
+
const installed = await executor.isInstalled();
|
|
1185
|
+
if (installed) {
|
|
1186
|
+
const version = await executor.getVersion();
|
|
1187
|
+
console.log(`\u2705 OpenClaw CLI installed${version ? ` (${version})` : ""}`);
|
|
1188
|
+
} else {
|
|
1189
|
+
console.log("\u274C OpenClaw CLI not found");
|
|
1190
|
+
console.log(" Install with: npm install -g openclaw");
|
|
1191
|
+
}
|
|
1192
|
+
if (isMcpConfigured(options.openclawPath)) {
|
|
1193
|
+
console.log("\u2705 Artyfacts MCP tools configured");
|
|
1194
|
+
} else {
|
|
1195
|
+
console.log("\u26A0\uFE0F Artyfacts MCP tools not configured");
|
|
1196
|
+
console.log(" Will configure automatically on first run");
|
|
1197
|
+
}
|
|
1198
|
+
console.log("");
|
|
1199
|
+
console.log("\u{1F4C2} Local Configuration:");
|
|
1200
|
+
try {
|
|
1201
|
+
const config = await introspect();
|
|
1202
|
+
const summary = summarize(config);
|
|
1203
|
+
console.log(` ${summary}`);
|
|
1204
|
+
if (config.crons.length > 0 || config.skills.length > 0 || config.mcpServers.length > 0) {
|
|
1205
|
+
console.log("");
|
|
1206
|
+
console.log(" Run `npx @artyfacts/openclaw introspect` for details");
|
|
1207
|
+
console.log(" Run `npx @artyfacts/openclaw import` to sync to Artyfacts");
|
|
1208
|
+
}
|
|
1209
|
+
} catch (err) {
|
|
1210
|
+
console.log(" (Could not read local config)");
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1213
|
+
program.command("introspect").description("Show detailed local OpenClaw configuration").option("--json", "Output as JSON").action(async (options) => {
|
|
1214
|
+
try {
|
|
1215
|
+
const config = await introspect();
|
|
1216
|
+
if (options.json) {
|
|
1217
|
+
console.log(JSON.stringify(config, null, 2));
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
console.log("");
|
|
1221
|
+
console.log("\u{1F4C2} OpenClaw Configuration");
|
|
1222
|
+
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");
|
|
1223
|
+
console.log("");
|
|
1224
|
+
console.log(`\u{1F4C5} Cron Jobs (${config.crons.length})`);
|
|
1225
|
+
if (config.crons.length === 0) {
|
|
1226
|
+
console.log(" (none)");
|
|
1227
|
+
} else {
|
|
1228
|
+
for (const job of config.crons) {
|
|
1229
|
+
const schedule = job.schedule.cron || job.schedule.every || job.schedule.at;
|
|
1230
|
+
const status = job.enabled === false ? " [disabled]" : "";
|
|
1231
|
+
console.log(` \u2022 ${job.name || job.jobId}: ${schedule}${status}`);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
console.log("");
|
|
1235
|
+
console.log("\u{1F493} Heartbeat");
|
|
1236
|
+
if (config.heartbeat) {
|
|
1237
|
+
console.log(` Interval: ${config.heartbeat.every || "30m"}`);
|
|
1238
|
+
if (config.heartbeat.target) {
|
|
1239
|
+
console.log(` Target: ${config.heartbeat.target}${config.heartbeat.to ? ` \u2192 ${config.heartbeat.to}` : ""}`);
|
|
1240
|
+
}
|
|
1241
|
+
if (config.heartbeat.activeHours) {
|
|
1242
|
+
console.log(` Active: ${config.heartbeat.activeHours.start} - ${config.heartbeat.activeHours.end}`);
|
|
1243
|
+
}
|
|
1244
|
+
} else {
|
|
1245
|
+
console.log(" (using defaults or disabled)");
|
|
1246
|
+
}
|
|
1247
|
+
console.log("");
|
|
1248
|
+
console.log(`\u{1F916} Agents (${config.agents.length})`);
|
|
1249
|
+
if (config.agents.length === 0) {
|
|
1250
|
+
console.log(" (using default agent)");
|
|
1251
|
+
} else {
|
|
1252
|
+
for (const agent of config.agents) {
|
|
1253
|
+
const def = agent.default ? " [default]" : "";
|
|
1254
|
+
console.log(` \u2022 ${agent.id}${agent.name ? ` (${agent.name})` : ""}${def}`);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
console.log("");
|
|
1258
|
+
console.log(`\u{1F50C} MCP Servers (${config.mcpServers.length})`);
|
|
1259
|
+
if (config.mcpServers.length === 0) {
|
|
1260
|
+
console.log(" (none)");
|
|
1261
|
+
} else {
|
|
1262
|
+
for (const server of config.mcpServers) {
|
|
1263
|
+
console.log(` \u2022 ${server.name}: ${server.url || server.command}`);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
console.log("");
|
|
1267
|
+
console.log(`\u{1F4DA} Skills (${config.skills.length})`);
|
|
1268
|
+
if (config.skills.length === 0) {
|
|
1269
|
+
console.log(" (none in ~/.openclaw/skills/)");
|
|
1270
|
+
} else {
|
|
1271
|
+
for (const skill of config.skills) {
|
|
1272
|
+
const desc = skill.description ? ` - ${skill.description.slice(0, 50)}...` : "";
|
|
1273
|
+
console.log(` \u2022 ${skill.name}${desc}`);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
console.log("");
|
|
1277
|
+
console.log(`\u{1F4F1} Channels (${config.channels.length})`);
|
|
1278
|
+
if (config.channels.length === 0) {
|
|
1279
|
+
console.log(" (none configured)");
|
|
1280
|
+
} else {
|
|
1281
|
+
console.log(` ${config.channels.join(", ")}`);
|
|
1282
|
+
}
|
|
1283
|
+
console.log("");
|
|
1284
|
+
} catch (err) {
|
|
1285
|
+
console.error("\u274C Failed to read configuration:", err instanceof Error ? err.message : err);
|
|
1286
|
+
process.exit(1);
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
program.command("import").description("Import local OpenClaw configuration to Artyfacts").option("--base-url <url>", "Artyfacts API base URL", DEFAULT_BASE_URL4).option("--dry-run", "Show what would be imported without importing").option("--crons", "Import only cron jobs").option("--skills", "Import only skills").option("--mcp", "Import only MCP servers").option("--agents", "Import only agents").action(async (options) => {
|
|
1290
|
+
const credentials = loadCredentials();
|
|
1291
|
+
if (!credentials) {
|
|
1292
|
+
console.log("\u274C Not authenticated. Run login first:");
|
|
1293
|
+
console.log(" npx @artyfacts/openclaw login");
|
|
1294
|
+
process.exit(1);
|
|
1295
|
+
}
|
|
1296
|
+
try {
|
|
1297
|
+
const config = await introspect();
|
|
1298
|
+
const importAll = !options.crons && !options.skills && !options.mcp && !options.agents;
|
|
1299
|
+
const toImport = {
|
|
1300
|
+
crons: importAll || options.crons,
|
|
1301
|
+
skills: importAll || options.skills,
|
|
1302
|
+
mcp: importAll || options.mcp,
|
|
1303
|
+
agents: importAll || options.agents
|
|
1304
|
+
};
|
|
1305
|
+
console.log("");
|
|
1306
|
+
console.log("\u{1F4E4} Importing to Artyfacts");
|
|
1307
|
+
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");
|
|
1308
|
+
console.log("");
|
|
1309
|
+
const payload = {
|
|
1310
|
+
source: "openclaw",
|
|
1311
|
+
agentId: credentials.agentId
|
|
1312
|
+
};
|
|
1313
|
+
if (toImport.crons && config.crons.length > 0) {
|
|
1314
|
+
console.log(`\u{1F4C5} Cron jobs: ${config.crons.length}`);
|
|
1315
|
+
payload.crons = config.crons;
|
|
1316
|
+
}
|
|
1317
|
+
if (toImport.skills && config.skills.length > 0) {
|
|
1318
|
+
console.log(`\u{1F4DA} Skills: ${config.skills.length}`);
|
|
1319
|
+
payload.skills = config.skills.map((s) => ({
|
|
1320
|
+
name: s.name,
|
|
1321
|
+
description: s.description,
|
|
1322
|
+
content: s.content
|
|
1323
|
+
}));
|
|
1324
|
+
}
|
|
1325
|
+
if (toImport.mcp && config.mcpServers.length > 0) {
|
|
1326
|
+
console.log(`\u{1F50C} MCP servers: ${config.mcpServers.length}`);
|
|
1327
|
+
payload.mcpServers = config.mcpServers;
|
|
1328
|
+
}
|
|
1329
|
+
if (toImport.agents && config.agents.length > 0) {
|
|
1330
|
+
console.log(`\u{1F916} Agents: ${config.agents.length}`);
|
|
1331
|
+
payload.agents = config.agents;
|
|
1332
|
+
}
|
|
1333
|
+
if (config.heartbeat && (importAll || options.agents)) {
|
|
1334
|
+
console.log("\u{1F493} Heartbeat config");
|
|
1335
|
+
payload.heartbeat = config.heartbeat;
|
|
1336
|
+
}
|
|
1337
|
+
if (config.channels.length > 0 && importAll) {
|
|
1338
|
+
payload.channels = config.channels;
|
|
1339
|
+
}
|
|
1340
|
+
console.log("");
|
|
1341
|
+
if (options.dryRun) {
|
|
1342
|
+
console.log("[DRY RUN] Would import:");
|
|
1343
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
const response = await fetch(`${options.baseUrl}/agents/${credentials.agentId}/config/import`, {
|
|
1347
|
+
method: "POST",
|
|
1348
|
+
headers: {
|
|
1349
|
+
"Content-Type": "application/json",
|
|
1350
|
+
"Authorization": `Bearer ${credentials.apiKey}`
|
|
1351
|
+
},
|
|
1352
|
+
body: JSON.stringify(payload)
|
|
1353
|
+
});
|
|
1354
|
+
if (!response.ok) {
|
|
1355
|
+
const errorText = await response.text();
|
|
1356
|
+
throw new Error(`Import failed: ${errorText}`);
|
|
1357
|
+
}
|
|
1358
|
+
const result = await response.json();
|
|
1359
|
+
console.log("\u2705 Import successful!");
|
|
1360
|
+
if (result.imported) {
|
|
1361
|
+
console.log(` Imported: ${Object.entries(result.imported).map(([k, v]) => `${v} ${k}`).join(", ")}`);
|
|
1362
|
+
}
|
|
1363
|
+
console.log("");
|
|
1364
|
+
console.log(" View in Artyfacts: https://artyfacts.dev/agents/" + credentials.agentId);
|
|
1365
|
+
console.log("");
|
|
1366
|
+
} catch (err) {
|
|
1367
|
+
console.error("\u274C Import failed:", err instanceof Error ? err.message : err);
|
|
1368
|
+
process.exit(1);
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
program.command("configure").description("Configure Artyfacts MCP tools for OpenClaw").option("--base-url <url>", "Artyfacts API base URL", DEFAULT_BASE_URL4).option("--openclaw-path <path>", "Path to openclaw CLI", "openclaw").action(async (options) => {
|
|
1372
|
+
const credentials = loadCredentials();
|
|
1373
|
+
if (!credentials) {
|
|
1374
|
+
console.log("\u274C Not authenticated. Run login first:");
|
|
1375
|
+
console.log(" npx @artyfacts/openclaw login");
|
|
1376
|
+
process.exit(1);
|
|
1377
|
+
}
|
|
1378
|
+
const success = configureMcp(credentials.apiKey, options.baseUrl, options.openclawPath);
|
|
1379
|
+
if (!success) {
|
|
1380
|
+
process.exit(1);
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
async function runAgent(options) {
|
|
1384
|
+
console.log("");
|
|
1385
|
+
console.log("\u{1F99E} Artyfacts OpenClaw Adapter");
|
|
1386
|
+
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");
|
|
1387
|
+
console.log("");
|
|
1388
|
+
const credentials = await getCredentials({ baseUrl: options.baseUrl });
|
|
1389
|
+
const executor = createExecutor({
|
|
1390
|
+
openclawPath: options.openclawPath,
|
|
1391
|
+
baseUrl: options.baseUrl,
|
|
1392
|
+
apiKey: credentials.apiKey
|
|
1393
|
+
});
|
|
1394
|
+
const installed = await executor.isInstalled();
|
|
1395
|
+
if (!installed) {
|
|
1396
|
+
console.error("\u274C OpenClaw CLI not found. Please install it:");
|
|
1397
|
+
console.error(" npm install -g openclaw");
|
|
1398
|
+
process.exit(1);
|
|
1399
|
+
}
|
|
1400
|
+
ensureMcpConfigured(credentials.apiKey, options.baseUrl, options.openclawPath);
|
|
1401
|
+
console.log("");
|
|
1402
|
+
const listener = createListener({
|
|
1403
|
+
baseUrl: options.baseUrl,
|
|
1404
|
+
apiKey: credentials.apiKey,
|
|
1405
|
+
agentId: credentials.agentId,
|
|
1406
|
+
onStateChange: (state) => {
|
|
1407
|
+
if (state === "disconnected") {
|
|
1408
|
+
console.log("\u26A0\uFE0F Disconnected from Artyfacts");
|
|
1409
|
+
} else if (state === "reconnecting") {
|
|
1410
|
+
console.log("\u{1F504} Reconnecting...");
|
|
1411
|
+
}
|
|
1412
|
+
},
|
|
1413
|
+
onError: (error) => {
|
|
1414
|
+
console.error("\u274C Listener error:", error.message);
|
|
1415
|
+
}
|
|
1416
|
+
});
|
|
1417
|
+
listener.on("task_assigned", async (event) => {
|
|
1418
|
+
const task = event.data;
|
|
1419
|
+
console.log("");
|
|
1420
|
+
console.log("\u{1F4CB} Task received:");
|
|
1421
|
+
console.log(` ${task.heading}`);
|
|
1422
|
+
console.log(` ID: ${task.sectionId}`);
|
|
1423
|
+
console.log(` Artifact: ${task.artifactTitle || task.artifactId}`);
|
|
1424
|
+
console.log("");
|
|
1425
|
+
if (options.dryRun) {
|
|
1426
|
+
console.log(" [DRY RUN] Would execute task");
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
console.log("\u{1F504} Executing task...");
|
|
1430
|
+
const taskContext = {
|
|
1431
|
+
taskId: task.sectionId,
|
|
1432
|
+
heading: task.heading,
|
|
1433
|
+
content: task.content,
|
|
1434
|
+
artifactId: task.artifactId,
|
|
1435
|
+
artifactTitle: task.artifactTitle,
|
|
1436
|
+
priority: task.priority
|
|
1437
|
+
};
|
|
1438
|
+
const result = await executor.execute(taskContext);
|
|
1439
|
+
if (result.success) {
|
|
1440
|
+
console.log("\u2705 Task completed");
|
|
1441
|
+
console.log(` Summary: ${result.summary}`);
|
|
1442
|
+
try {
|
|
1443
|
+
await reportTaskCompletion(options.baseUrl, credentials.apiKey, {
|
|
1444
|
+
taskId: task.sectionId,
|
|
1445
|
+
success: true,
|
|
1446
|
+
summary: result.summary,
|
|
1447
|
+
output: result.output
|
|
1448
|
+
});
|
|
1449
|
+
console.log(" \u{1F4E4} Result reported to Artyfacts");
|
|
1450
|
+
} catch (reportError) {
|
|
1451
|
+
console.error(" \u26A0\uFE0F Failed to report result:", reportError);
|
|
1452
|
+
}
|
|
1453
|
+
} else {
|
|
1454
|
+
console.log("\u274C Task failed");
|
|
1455
|
+
console.log(` Error: ${result.error}`);
|
|
1456
|
+
try {
|
|
1457
|
+
await reportTaskCompletion(options.baseUrl, credentials.apiKey, {
|
|
1458
|
+
taskId: task.sectionId,
|
|
1459
|
+
success: false,
|
|
1460
|
+
error: result.error
|
|
1461
|
+
});
|
|
1462
|
+
} catch (reportError) {
|
|
1463
|
+
console.error(" \u26A0\uFE0F Failed to report error:", reportError);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
});
|
|
1467
|
+
listener.on("mcp_connect_request", async (event) => {
|
|
1468
|
+
console.log("");
|
|
1469
|
+
console.log(`\u{1F50C} MCP connection request: ${event.data.platform}`);
|
|
1470
|
+
console.log(" (MCP connection handling not yet implemented for OpenClaw)");
|
|
1471
|
+
});
|
|
1472
|
+
listener.on("connected", (event) => {
|
|
1473
|
+
console.log("\u2705 Connected to Artyfacts");
|
|
1474
|
+
console.log("\u{1F3A7} Listening for tasks...");
|
|
1475
|
+
console.log("");
|
|
1476
|
+
});
|
|
1477
|
+
await listener.connect();
|
|
1478
|
+
process.on("SIGINT", () => {
|
|
1479
|
+
console.log("");
|
|
1480
|
+
console.log("\u{1F44B} Shutting down...");
|
|
1481
|
+
listener.disconnect();
|
|
1482
|
+
process.exit(0);
|
|
1483
|
+
});
|
|
1484
|
+
process.on("SIGTERM", () => {
|
|
1485
|
+
listener.disconnect();
|
|
1486
|
+
process.exit(0);
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
async function reportTaskCompletion(baseUrl, apiKey, result) {
|
|
1490
|
+
const endpoint = `${baseUrl}/tasks/${result.taskId}/complete`;
|
|
1491
|
+
const response = await fetch(endpoint, {
|
|
1492
|
+
method: "POST",
|
|
1493
|
+
headers: {
|
|
1494
|
+
"Content-Type": "application/json",
|
|
1495
|
+
"Authorization": `Bearer ${apiKey}`
|
|
1496
|
+
},
|
|
1497
|
+
body: JSON.stringify({
|
|
1498
|
+
status: result.success ? "done" : "failed",
|
|
1499
|
+
output_summary: result.summary || result.error,
|
|
1500
|
+
output_url: null
|
|
1501
|
+
})
|
|
1502
|
+
});
|
|
1503
|
+
if (!response.ok) {
|
|
1504
|
+
const errorText = await response.text();
|
|
1505
|
+
throw new Error(`Failed to report completion: ${errorText}`);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
program.parse();
|