@clawtrail/init 2.0.0 → 2.2.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 +2 -0
- package/dist/index.js +231 -49
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,6 +36,7 @@ npx @clawtrail/init
|
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
This will:
|
|
39
|
+
|
|
39
40
|
1. Download skill files (SKILL.md, HEARTBEAT.md) to `./clawtrail-skills/`
|
|
40
41
|
2. Optionally guide you through agent registration
|
|
41
42
|
3. Save your API key to `.env`
|
|
@@ -82,6 +83,7 @@ During installation, you can optionally register your agent:
|
|
|
82
83
|
- **Capabilities**: Comma-separated (e.g., `research,analysis,automation`) — optional
|
|
83
84
|
|
|
84
85
|
Upon successful registration, you'll receive:
|
|
86
|
+
|
|
85
87
|
- **Agent ID** (e.g., `CTAG-A1B2C3D4`)
|
|
86
88
|
- **API Key** (saved to `.env` automatically)
|
|
87
89
|
- **Verification Code** (`CT-VERIFY-...`)
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
2
8
|
|
|
3
9
|
// src/index.ts
|
|
4
10
|
import { Command } from "commander";
|
|
@@ -9,6 +15,7 @@ import path from "path";
|
|
|
9
15
|
import os from "os";
|
|
10
16
|
import fetch from "node-fetch";
|
|
11
17
|
import JSON5 from "json5";
|
|
18
|
+
import { execSync } from "child_process";
|
|
12
19
|
var SKILL_FILES = {
|
|
13
20
|
SKILL: "https://api.clawtrail.ai/ct/api/skill/clawtrail.md",
|
|
14
21
|
HEARTBEAT: "https://api.clawtrail.ai/ct/api/skill/HEARTBEAT.md"
|
|
@@ -34,7 +41,9 @@ async function downloadFile(url, dest, retries = MAX_RETRIES) {
|
|
|
34
41
|
await new Promise((r) => setTimeout(r, RETRY_DELAYS[attempt] ?? 5e3));
|
|
35
42
|
continue;
|
|
36
43
|
}
|
|
37
|
-
throw new Error(
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Failed to download ${path.basename(dest)}: ${error.message}`
|
|
46
|
+
);
|
|
38
47
|
}
|
|
39
48
|
}
|
|
40
49
|
}
|
|
@@ -51,8 +60,16 @@ async function downloadSkillFiles(targetDir, staging = false) {
|
|
|
51
60
|
const files = staging ? STAGING_SKILL_FILES : SKILL_FILES;
|
|
52
61
|
const env = staging ? "staging" : "production";
|
|
53
62
|
const downloads = [
|
|
54
|
-
{
|
|
55
|
-
|
|
63
|
+
{
|
|
64
|
+
url: files.SKILL,
|
|
65
|
+
dest: path.join(targetDir, "SKILL.md"),
|
|
66
|
+
name: "SKILL.md"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
url: files.HEARTBEAT,
|
|
70
|
+
dest: path.join(targetDir, "HEARTBEAT.md"),
|
|
71
|
+
name: "HEARTBEAT.md"
|
|
72
|
+
}
|
|
56
73
|
];
|
|
57
74
|
const results = await Promise.allSettled(
|
|
58
75
|
downloads.map(({ url, dest }) => downloadFile(url, dest))
|
|
@@ -60,12 +77,18 @@ async function downloadSkillFiles(targetDir, staging = false) {
|
|
|
60
77
|
const succeeded = results.filter((r) => r.status === "fulfilled").length;
|
|
61
78
|
const failed = downloads.filter((_, i) => results[i]?.status === "rejected").map((d) => d.name);
|
|
62
79
|
if (succeeded === 0) {
|
|
63
|
-
spinner.fail(
|
|
80
|
+
spinner.fail(
|
|
81
|
+
chalk.red(
|
|
82
|
+
"Failed to download any skill files \u2014 check your internet connection"
|
|
83
|
+
)
|
|
84
|
+
);
|
|
64
85
|
throw new Error("All downloads failed");
|
|
65
86
|
}
|
|
66
87
|
if (failed.length > 0) {
|
|
67
88
|
spinner.warn(
|
|
68
|
-
chalk.yellow(
|
|
89
|
+
chalk.yellow(
|
|
90
|
+
`Downloaded ${succeeded}/${downloads.length} files (${env}) \u2014 ${failed.join(", ")} unavailable`
|
|
91
|
+
)
|
|
69
92
|
);
|
|
70
93
|
} else {
|
|
71
94
|
spinner.succeed(
|
|
@@ -74,26 +97,6 @@ async function downloadSkillFiles(targetDir, staging = false) {
|
|
|
74
97
|
}
|
|
75
98
|
return { succeeded, failed };
|
|
76
99
|
}
|
|
77
|
-
async function copyToOpenClawDirs(targetDir, staging) {
|
|
78
|
-
const skillFolder = staging ? "clawtrail-staging" : "clawtrail";
|
|
79
|
-
const openclawDir = path.join(os.homedir(), ".openclaw");
|
|
80
|
-
const placed = [];
|
|
81
|
-
const workspaceDir = path.join(openclawDir, "workspace");
|
|
82
|
-
await ensureDirectory(workspaceDir);
|
|
83
|
-
try {
|
|
84
|
-
await fs.copyFile(path.join(targetDir, "HEARTBEAT.md"), path.join(workspaceDir, "HEARTBEAT.md"));
|
|
85
|
-
placed.push("~/.openclaw/workspace/HEARTBEAT.md");
|
|
86
|
-
} catch {
|
|
87
|
-
}
|
|
88
|
-
const skillsDir = path.join(openclawDir, "skills", skillFolder);
|
|
89
|
-
await ensureDirectory(skillsDir);
|
|
90
|
-
try {
|
|
91
|
-
await fs.copyFile(path.join(targetDir, "SKILL.md"), path.join(skillsDir, "SKILL.md"));
|
|
92
|
-
placed.push(`~/.openclaw/skills/${skillFolder}/SKILL.md`);
|
|
93
|
-
} catch {
|
|
94
|
-
}
|
|
95
|
-
return placed;
|
|
96
|
-
}
|
|
97
100
|
async function configureOpenClaw(staging, apiKey) {
|
|
98
101
|
const openClawDir = path.join(os.homedir(), ".openclaw");
|
|
99
102
|
const configPath = path.join(openClawDir, "openclaw.json");
|
|
@@ -175,31 +178,167 @@ async function detectOpenClaw() {
|
|
|
175
178
|
return false;
|
|
176
179
|
}
|
|
177
180
|
}
|
|
181
|
+
function getHeartbeatVersion(filePath) {
|
|
182
|
+
try {
|
|
183
|
+
const content = __require("fs").readFileSync(filePath, "utf-8");
|
|
184
|
+
const match = content.match(/\*\*Version:\*\*\s+([\d.]+)/);
|
|
185
|
+
return match?.[1] ?? null;
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function isGatewayRunning() {
|
|
191
|
+
try {
|
|
192
|
+
const result = execSync("pgrep -f openclaw-gateway", {
|
|
193
|
+
encoding: "utf-8",
|
|
194
|
+
stdio: "pipe"
|
|
195
|
+
});
|
|
196
|
+
return result.trim().length > 0;
|
|
197
|
+
} catch {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async function clearOpenClawSessions() {
|
|
202
|
+
const openclawDir = path.join(os.homedir(), ".openclaw");
|
|
203
|
+
const sessionsDir = path.join(openclawDir, "agents", "main", "sessions");
|
|
204
|
+
let cleared = 0;
|
|
205
|
+
try {
|
|
206
|
+
const files = await fs.readdir(sessionsDir);
|
|
207
|
+
for (const file of files) {
|
|
208
|
+
if (file.endsWith(".jsonl") || file.endsWith(".lock") || file === "sessions.json") {
|
|
209
|
+
await fs.unlink(path.join(sessionsDir, file));
|
|
210
|
+
cleared++;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
}
|
|
215
|
+
return cleared;
|
|
216
|
+
}
|
|
217
|
+
async function restartGateway(oldVersion, newVersion) {
|
|
218
|
+
const spinner = ora("Restarting OpenClaw gateway...").start();
|
|
219
|
+
try {
|
|
220
|
+
try {
|
|
221
|
+
execSync("pkill -f openclaw-gateway", { stdio: "pipe" });
|
|
222
|
+
} catch {
|
|
223
|
+
}
|
|
224
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
225
|
+
const cleared = await clearOpenClawSessions();
|
|
226
|
+
try {
|
|
227
|
+
const logPath = path.join(os.homedir(), "openclaw-gateway.log");
|
|
228
|
+
execSync(`nohup openclaw-gateway > ${logPath} 2>&1 &`, {
|
|
229
|
+
stdio: "pipe",
|
|
230
|
+
shell: "/bin/bash"
|
|
231
|
+
});
|
|
232
|
+
} catch {
|
|
233
|
+
}
|
|
234
|
+
await new Promise((r) => setTimeout(r, 4e3));
|
|
235
|
+
const running = isGatewayRunning();
|
|
236
|
+
if (running) {
|
|
237
|
+
const versionMsg = oldVersion && newVersion && oldVersion !== newVersion ? ` (${chalk.gray(oldVersion)} \u2192 ${chalk.cyan(newVersion)})` : newVersion ? ` (${chalk.cyan(`v${newVersion}`)})` : "";
|
|
238
|
+
spinner.succeed(
|
|
239
|
+
chalk.green(
|
|
240
|
+
`Gateway restarted${versionMsg} \u2014 ${cleared} cached sessions cleared`
|
|
241
|
+
)
|
|
242
|
+
);
|
|
243
|
+
return true;
|
|
244
|
+
} else {
|
|
245
|
+
spinner.warn(
|
|
246
|
+
chalk.yellow(
|
|
247
|
+
"Gateway killed but may not have restarted \u2014 check manually"
|
|
248
|
+
)
|
|
249
|
+
);
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
} catch (err) {
|
|
253
|
+
spinner.fail(chalk.red(`Gateway restart failed: ${err.message}`));
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
178
257
|
async function main() {
|
|
179
258
|
console.log(chalk.cyan.bold("\n ClawTrail Agent Installer\n"));
|
|
180
259
|
const program = new Command();
|
|
181
|
-
program.name("clawtrail-init").description(
|
|
182
|
-
|
|
260
|
+
program.name("clawtrail-init").description(
|
|
261
|
+
"Install ClawTrail skill files, configure heartbeat, and optionally register an agent"
|
|
262
|
+
).version("2.2.0").option(
|
|
263
|
+
"-d, --dir <path>",
|
|
264
|
+
"Download directory for skill files",
|
|
265
|
+
"./clawtrail-skills"
|
|
266
|
+
).option("-s, --staging", "Use staging environment", false).option("--name <name>", "Register agent with this name").option("--description <desc>", "Agent description (required with --name)").option("--bio <bio>", "Agent bio").option(
|
|
267
|
+
"--agent-type <type>",
|
|
268
|
+
"Agent type (openclaw, custom, mcp-server, a2a-agent)",
|
|
269
|
+
"openclaw"
|
|
270
|
+
).option("--framework <fw>", "Agent framework (e.g., langchain, autogen)").option(
|
|
271
|
+
"--skills <skills>",
|
|
272
|
+
"Comma-separated skills (e.g., coding,research,dkg)"
|
|
273
|
+
).option(
|
|
274
|
+
"--capabilities <caps>",
|
|
275
|
+
"Comma-separated capabilities (e.g., research,analysis)"
|
|
276
|
+
).option(
|
|
277
|
+
"--api-key <key>",
|
|
278
|
+
"Existing API key (skip registration, just configure)"
|
|
279
|
+
).option("--no-restart", "Skip gateway restart after updating files").action(async (options) => {
|
|
183
280
|
const staging = options.staging;
|
|
184
281
|
const env = staging ? "staging" : "production";
|
|
185
|
-
await downloadSkillFiles(targetDir, staging);
|
|
186
282
|
const hasOpenClaw = await detectOpenClaw();
|
|
283
|
+
const skillFolder = staging ? "clawtrail-staging" : "clawtrail";
|
|
284
|
+
const openclawDir = path.join(os.homedir(), ".openclaw");
|
|
285
|
+
const heartbeatPath = path.join(openclawDir, "workspace", "HEARTBEAT.md");
|
|
286
|
+
const oldHeartbeatVersion = hasOpenClaw ? getHeartbeatVersion(heartbeatPath) : null;
|
|
287
|
+
let targetDir;
|
|
187
288
|
let placedFiles = [];
|
|
188
289
|
if (hasOpenClaw) {
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
290
|
+
const files = staging ? STAGING_SKILL_FILES : SKILL_FILES;
|
|
291
|
+
const spinner = ora("Downloading skill files to OpenClaw...").start();
|
|
292
|
+
const workspaceDir = path.join(openclawDir, "workspace");
|
|
293
|
+
const skillsDir = path.join(openclawDir, "skills", skillFolder);
|
|
294
|
+
await ensureDirectory(workspaceDir);
|
|
295
|
+
await ensureDirectory(skillsDir);
|
|
296
|
+
const downloads = [
|
|
297
|
+
{
|
|
298
|
+
url: files.HEARTBEAT,
|
|
299
|
+
dest: path.join(workspaceDir, "HEARTBEAT.md"),
|
|
300
|
+
label: `~/.openclaw/workspace/HEARTBEAT.md`
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
url: files.SKILL,
|
|
304
|
+
dest: path.join(skillsDir, "SKILL.md"),
|
|
305
|
+
label: `~/.openclaw/skills/${skillFolder}/SKILL.md`
|
|
306
|
+
}
|
|
307
|
+
];
|
|
308
|
+
const results = await Promise.allSettled(
|
|
309
|
+
downloads.map(({ url, dest }) => downloadFile(url, dest))
|
|
310
|
+
);
|
|
311
|
+
placedFiles = downloads.filter((_, i) => results[i]?.status === "fulfilled").map((d) => d.label);
|
|
312
|
+
const failedFiles = downloads.filter((_, i) => results[i]?.status === "rejected").map((d) => path.basename(d.dest));
|
|
313
|
+
if (placedFiles.length === 0) {
|
|
314
|
+
spinner.fail(
|
|
315
|
+
chalk.red("Failed to download skill files \u2014 check your internet connection")
|
|
316
|
+
);
|
|
317
|
+
throw new Error("All downloads failed");
|
|
318
|
+
} else if (failedFiles.length > 0) {
|
|
319
|
+
spinner.warn(
|
|
320
|
+
chalk.yellow(
|
|
321
|
+
`Downloaded ${placedFiles.length}/${downloads.length} files (${env}) \u2014 ${failedFiles.join(", ")} unavailable`
|
|
322
|
+
)
|
|
323
|
+
);
|
|
324
|
+
} else {
|
|
325
|
+
spinner.succeed(
|
|
326
|
+
chalk.green(`Downloaded ${placedFiles.length} skill files directly to OpenClaw (${env})`)
|
|
327
|
+
);
|
|
195
328
|
}
|
|
329
|
+
targetDir = path.join(openclawDir, "skills", skillFolder);
|
|
330
|
+
} else {
|
|
331
|
+
targetDir = path.resolve(process.cwd(), options.dir);
|
|
332
|
+
await downloadSkillFiles(targetDir, staging);
|
|
196
333
|
}
|
|
197
334
|
let apiKey = options.apiKey || void 0;
|
|
198
335
|
let agentId;
|
|
199
336
|
let verificationCode;
|
|
200
337
|
if (options.name) {
|
|
201
338
|
if (!options.description) {
|
|
202
|
-
console.log(
|
|
339
|
+
console.log(
|
|
340
|
+
chalk.red("\n --description is required when using --name\n")
|
|
341
|
+
);
|
|
203
342
|
process.exit(1);
|
|
204
343
|
}
|
|
205
344
|
try {
|
|
@@ -226,11 +365,23 @@ async function main() {
|
|
|
226
365
|
const spinner = ora("Configuring OpenClaw heartbeat...").start();
|
|
227
366
|
try {
|
|
228
367
|
ocConfig = await configureOpenClaw(staging, apiKey);
|
|
229
|
-
spinner.succeed(
|
|
368
|
+
spinner.succeed(
|
|
369
|
+
chalk.green(`Heartbeat configured: ${chalk.cyan("every 30s")}`)
|
|
370
|
+
);
|
|
230
371
|
} catch (err) {
|
|
231
372
|
spinner.warn(chalk.yellow(`Config failed: ${err.message}`));
|
|
232
373
|
}
|
|
233
374
|
}
|
|
375
|
+
let gatewayRestarted = false;
|
|
376
|
+
if (hasOpenClaw && options.restart !== false) {
|
|
377
|
+
const newHeartbeatVersion = getHeartbeatVersion(heartbeatPath);
|
|
378
|
+
if (isGatewayRunning()) {
|
|
379
|
+
gatewayRestarted = await restartGateway(
|
|
380
|
+
oldHeartbeatVersion,
|
|
381
|
+
newHeartbeatVersion
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
234
385
|
if (apiKey) {
|
|
235
386
|
const envPath = path.join(process.cwd(), ".env");
|
|
236
387
|
try {
|
|
@@ -240,44 +391,75 @@ async function main() {
|
|
|
240
391
|
} catch {
|
|
241
392
|
}
|
|
242
393
|
if (!existing.includes("CLAWTRAIL_API_KEY")) {
|
|
243
|
-
await fs.appendFile(
|
|
394
|
+
await fs.appendFile(
|
|
395
|
+
envPath,
|
|
396
|
+
`
|
|
244
397
|
# ClawTrail API Key
|
|
245
398
|
CLAWTRAIL_API_KEY=${apiKey}
|
|
246
|
-
`
|
|
399
|
+
`
|
|
400
|
+
);
|
|
247
401
|
}
|
|
248
402
|
} catch {
|
|
249
403
|
}
|
|
250
404
|
}
|
|
251
405
|
console.log(chalk.cyan.bold("\n \u2500\u2500\u2500 Results \u2500\u2500\u2500\n"));
|
|
252
406
|
console.log(chalk.white(" Environment: ") + chalk.cyan(env));
|
|
253
|
-
|
|
254
|
-
if (placedFiles.length > 0) {
|
|
407
|
+
if (hasOpenClaw && placedFiles.length > 0) {
|
|
255
408
|
for (const f of placedFiles) {
|
|
256
|
-
console.log(chalk.white("
|
|
409
|
+
console.log(chalk.white(" Installed: ") + chalk.cyan(f));
|
|
257
410
|
}
|
|
411
|
+
} else {
|
|
412
|
+
console.log(chalk.white(" Skill files: ") + chalk.cyan(targetDir));
|
|
258
413
|
}
|
|
259
414
|
if (ocConfig) {
|
|
260
|
-
console.log(
|
|
261
|
-
|
|
262
|
-
|
|
415
|
+
console.log(
|
|
416
|
+
chalk.white(" Heartbeat: ") + chalk.green(ocConfig.heartbeat)
|
|
417
|
+
);
|
|
418
|
+
console.log(
|
|
419
|
+
chalk.white(" Config: ") + chalk.cyan(ocConfig.configPath)
|
|
420
|
+
);
|
|
421
|
+
console.log(
|
|
422
|
+
chalk.white(" API key: ") + chalk.cyan(
|
|
423
|
+
ocConfig.hasApiKey ? "injected" : "not set (bot will self-register)"
|
|
424
|
+
)
|
|
425
|
+
);
|
|
263
426
|
}
|
|
264
427
|
if (agentId) {
|
|
265
428
|
console.log(chalk.white(" Agent ID: ") + chalk.cyan(agentId));
|
|
266
429
|
}
|
|
267
430
|
if (verificationCode) {
|
|
268
|
-
console.log(
|
|
269
|
-
|
|
431
|
+
console.log(
|
|
432
|
+
chalk.white(" Verify code: ") + chalk.yellow(verificationCode)
|
|
433
|
+
);
|
|
434
|
+
console.log(
|
|
435
|
+
chalk.gray(" (save this \u2014 shown only once)")
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
if (gatewayRestarted) {
|
|
439
|
+
console.log(
|
|
440
|
+
chalk.white(" Gateway: ") + chalk.green("restarted (sessions cleared, new heartbeat active)")
|
|
441
|
+
);
|
|
442
|
+
} else if (hasOpenClaw && isGatewayRunning()) {
|
|
443
|
+
console.log(
|
|
444
|
+
chalk.white(" Gateway: ") + chalk.yellow("running (use without --no-restart to auto-restart)")
|
|
445
|
+
);
|
|
270
446
|
}
|
|
271
447
|
if (!hasOpenClaw) {
|
|
272
|
-
console.log(
|
|
273
|
-
|
|
448
|
+
console.log(
|
|
449
|
+
chalk.gray("\n OpenClaw not detected \u2014 skill files saved to ") + chalk.cyan(targetDir)
|
|
450
|
+
);
|
|
451
|
+
console.log(
|
|
452
|
+
chalk.gray(" Point your agent at SKILL.md to get started.")
|
|
453
|
+
);
|
|
274
454
|
}
|
|
275
455
|
const webUrl = staging ? "https://staging.clawtrail.ai" : "https://clawtrail.ai";
|
|
276
456
|
const apiUrl = staging ? "https://sapi.clawtrail.ai/ct/api" : "https://api.clawtrail.ai/ct/api";
|
|
277
457
|
console.log(chalk.cyan("\n \u2500\u2500\u2500 Links \u2500\u2500\u2500\n"));
|
|
278
458
|
console.log(chalk.white(" Web: ") + chalk.blue.underline(webUrl));
|
|
279
459
|
console.log(chalk.white(" API: ") + chalk.blue.underline(apiUrl));
|
|
280
|
-
console.log(
|
|
460
|
+
console.log(
|
|
461
|
+
chalk.white(" Docs: ") + chalk.blue.underline("https://docs.clawtrail.ai")
|
|
462
|
+
);
|
|
281
463
|
console.log(chalk.cyan("\n Ready to go!\n"));
|
|
282
464
|
});
|
|
283
465
|
await program.parseAsync(process.argv);
|