@clawtrail/init 2.1.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 +150 -56
- 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
|
@@ -41,7 +41,9 @@ async function downloadFile(url, dest, retries = MAX_RETRIES) {
|
|
|
41
41
|
await new Promise((r) => setTimeout(r, RETRY_DELAYS[attempt] ?? 5e3));
|
|
42
42
|
continue;
|
|
43
43
|
}
|
|
44
|
-
throw new Error(
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Failed to download ${path.basename(dest)}: ${error.message}`
|
|
46
|
+
);
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
49
|
}
|
|
@@ -58,8 +60,16 @@ async function downloadSkillFiles(targetDir, staging = false) {
|
|
|
58
60
|
const files = staging ? STAGING_SKILL_FILES : SKILL_FILES;
|
|
59
61
|
const env = staging ? "staging" : "production";
|
|
60
62
|
const downloads = [
|
|
61
|
-
{
|
|
62
|
-
|
|
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
|
+
}
|
|
63
73
|
];
|
|
64
74
|
const results = await Promise.allSettled(
|
|
65
75
|
downloads.map(({ url, dest }) => downloadFile(url, dest))
|
|
@@ -67,12 +77,18 @@ async function downloadSkillFiles(targetDir, staging = false) {
|
|
|
67
77
|
const succeeded = results.filter((r) => r.status === "fulfilled").length;
|
|
68
78
|
const failed = downloads.filter((_, i) => results[i]?.status === "rejected").map((d) => d.name);
|
|
69
79
|
if (succeeded === 0) {
|
|
70
|
-
spinner.fail(
|
|
80
|
+
spinner.fail(
|
|
81
|
+
chalk.red(
|
|
82
|
+
"Failed to download any skill files \u2014 check your internet connection"
|
|
83
|
+
)
|
|
84
|
+
);
|
|
71
85
|
throw new Error("All downloads failed");
|
|
72
86
|
}
|
|
73
87
|
if (failed.length > 0) {
|
|
74
88
|
spinner.warn(
|
|
75
|
-
chalk.yellow(
|
|
89
|
+
chalk.yellow(
|
|
90
|
+
`Downloaded ${succeeded}/${downloads.length} files (${env}) \u2014 ${failed.join(", ")} unavailable`
|
|
91
|
+
)
|
|
76
92
|
);
|
|
77
93
|
} else {
|
|
78
94
|
spinner.succeed(
|
|
@@ -81,26 +97,6 @@ async function downloadSkillFiles(targetDir, staging = false) {
|
|
|
81
97
|
}
|
|
82
98
|
return { succeeded, failed };
|
|
83
99
|
}
|
|
84
|
-
async function copyToOpenClawDirs(targetDir, staging) {
|
|
85
|
-
const skillFolder = staging ? "clawtrail-staging" : "clawtrail";
|
|
86
|
-
const openclawDir = path.join(os.homedir(), ".openclaw");
|
|
87
|
-
const placed = [];
|
|
88
|
-
const workspaceDir = path.join(openclawDir, "workspace");
|
|
89
|
-
await ensureDirectory(workspaceDir);
|
|
90
|
-
try {
|
|
91
|
-
await fs.copyFile(path.join(targetDir, "HEARTBEAT.md"), path.join(workspaceDir, "HEARTBEAT.md"));
|
|
92
|
-
placed.push("~/.openclaw/workspace/HEARTBEAT.md");
|
|
93
|
-
} catch {
|
|
94
|
-
}
|
|
95
|
-
const skillsDir = path.join(openclawDir, "skills", skillFolder);
|
|
96
|
-
await ensureDirectory(skillsDir);
|
|
97
|
-
try {
|
|
98
|
-
await fs.copyFile(path.join(targetDir, "SKILL.md"), path.join(skillsDir, "SKILL.md"));
|
|
99
|
-
placed.push(`~/.openclaw/skills/${skillFolder}/SKILL.md`);
|
|
100
|
-
} catch {
|
|
101
|
-
}
|
|
102
|
-
return placed;
|
|
103
|
-
}
|
|
104
100
|
async function configureOpenClaw(staging, apiKey) {
|
|
105
101
|
const openClawDir = path.join(os.homedir(), ".openclaw");
|
|
106
102
|
const configPath = path.join(openClawDir, "openclaw.json");
|
|
@@ -193,7 +189,10 @@ function getHeartbeatVersion(filePath) {
|
|
|
193
189
|
}
|
|
194
190
|
function isGatewayRunning() {
|
|
195
191
|
try {
|
|
196
|
-
const result = execSync("pgrep -f openclaw-gateway", {
|
|
192
|
+
const result = execSync("pgrep -f openclaw-gateway", {
|
|
193
|
+
encoding: "utf-8",
|
|
194
|
+
stdio: "pipe"
|
|
195
|
+
});
|
|
197
196
|
return result.trim().length > 0;
|
|
198
197
|
} catch {
|
|
199
198
|
return false;
|
|
@@ -237,11 +236,17 @@ async function restartGateway(oldVersion, newVersion) {
|
|
|
237
236
|
if (running) {
|
|
238
237
|
const versionMsg = oldVersion && newVersion && oldVersion !== newVersion ? ` (${chalk.gray(oldVersion)} \u2192 ${chalk.cyan(newVersion)})` : newVersion ? ` (${chalk.cyan(`v${newVersion}`)})` : "";
|
|
239
238
|
spinner.succeed(
|
|
240
|
-
chalk.green(
|
|
239
|
+
chalk.green(
|
|
240
|
+
`Gateway restarted${versionMsg} \u2014 ${cleared} cached sessions cleared`
|
|
241
|
+
)
|
|
241
242
|
);
|
|
242
243
|
return true;
|
|
243
244
|
} else {
|
|
244
|
-
spinner.warn(
|
|
245
|
+
spinner.warn(
|
|
246
|
+
chalk.yellow(
|
|
247
|
+
"Gateway killed but may not have restarted \u2014 check manually"
|
|
248
|
+
)
|
|
249
|
+
);
|
|
245
250
|
return false;
|
|
246
251
|
}
|
|
247
252
|
} catch (err) {
|
|
@@ -252,30 +257,88 @@ async function restartGateway(oldVersion, newVersion) {
|
|
|
252
257
|
async function main() {
|
|
253
258
|
console.log(chalk.cyan.bold("\n ClawTrail Agent Installer\n"));
|
|
254
259
|
const program = new Command();
|
|
255
|
-
program.name("clawtrail-init").description(
|
|
256
|
-
|
|
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) => {
|
|
257
280
|
const staging = options.staging;
|
|
258
281
|
const env = staging ? "staging" : "production";
|
|
259
|
-
await downloadSkillFiles(targetDir, staging);
|
|
260
282
|
const hasOpenClaw = await detectOpenClaw();
|
|
261
|
-
const
|
|
283
|
+
const skillFolder = staging ? "clawtrail-staging" : "clawtrail";
|
|
284
|
+
const openclawDir = path.join(os.homedir(), ".openclaw");
|
|
285
|
+
const heartbeatPath = path.join(openclawDir, "workspace", "HEARTBEAT.md");
|
|
262
286
|
const oldHeartbeatVersion = hasOpenClaw ? getHeartbeatVersion(heartbeatPath) : null;
|
|
287
|
+
let targetDir;
|
|
263
288
|
let placedFiles = [];
|
|
264
289
|
if (hasOpenClaw) {
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
+
);
|
|
271
328
|
}
|
|
329
|
+
targetDir = path.join(openclawDir, "skills", skillFolder);
|
|
330
|
+
} else {
|
|
331
|
+
targetDir = path.resolve(process.cwd(), options.dir);
|
|
332
|
+
await downloadSkillFiles(targetDir, staging);
|
|
272
333
|
}
|
|
273
334
|
let apiKey = options.apiKey || void 0;
|
|
274
335
|
let agentId;
|
|
275
336
|
let verificationCode;
|
|
276
337
|
if (options.name) {
|
|
277
338
|
if (!options.description) {
|
|
278
|
-
console.log(
|
|
339
|
+
console.log(
|
|
340
|
+
chalk.red("\n --description is required when using --name\n")
|
|
341
|
+
);
|
|
279
342
|
process.exit(1);
|
|
280
343
|
}
|
|
281
344
|
try {
|
|
@@ -302,7 +365,9 @@ async function main() {
|
|
|
302
365
|
const spinner = ora("Configuring OpenClaw heartbeat...").start();
|
|
303
366
|
try {
|
|
304
367
|
ocConfig = await configureOpenClaw(staging, apiKey);
|
|
305
|
-
spinner.succeed(
|
|
368
|
+
spinner.succeed(
|
|
369
|
+
chalk.green(`Heartbeat configured: ${chalk.cyan("every 30s")}`)
|
|
370
|
+
);
|
|
306
371
|
} catch (err) {
|
|
307
372
|
spinner.warn(chalk.yellow(`Config failed: ${err.message}`));
|
|
308
373
|
}
|
|
@@ -311,7 +376,10 @@ async function main() {
|
|
|
311
376
|
if (hasOpenClaw && options.restart !== false) {
|
|
312
377
|
const newHeartbeatVersion = getHeartbeatVersion(heartbeatPath);
|
|
313
378
|
if (isGatewayRunning()) {
|
|
314
|
-
gatewayRestarted = await restartGateway(
|
|
379
|
+
gatewayRestarted = await restartGateway(
|
|
380
|
+
oldHeartbeatVersion,
|
|
381
|
+
newHeartbeatVersion
|
|
382
|
+
);
|
|
315
383
|
}
|
|
316
384
|
}
|
|
317
385
|
if (apiKey) {
|
|
@@ -323,49 +391,75 @@ async function main() {
|
|
|
323
391
|
} catch {
|
|
324
392
|
}
|
|
325
393
|
if (!existing.includes("CLAWTRAIL_API_KEY")) {
|
|
326
|
-
await fs.appendFile(
|
|
394
|
+
await fs.appendFile(
|
|
395
|
+
envPath,
|
|
396
|
+
`
|
|
327
397
|
# ClawTrail API Key
|
|
328
398
|
CLAWTRAIL_API_KEY=${apiKey}
|
|
329
|
-
`
|
|
399
|
+
`
|
|
400
|
+
);
|
|
330
401
|
}
|
|
331
402
|
} catch {
|
|
332
403
|
}
|
|
333
404
|
}
|
|
334
405
|
console.log(chalk.cyan.bold("\n \u2500\u2500\u2500 Results \u2500\u2500\u2500\n"));
|
|
335
406
|
console.log(chalk.white(" Environment: ") + chalk.cyan(env));
|
|
336
|
-
|
|
337
|
-
if (placedFiles.length > 0) {
|
|
407
|
+
if (hasOpenClaw && placedFiles.length > 0) {
|
|
338
408
|
for (const f of placedFiles) {
|
|
339
|
-
console.log(chalk.white("
|
|
409
|
+
console.log(chalk.white(" Installed: ") + chalk.cyan(f));
|
|
340
410
|
}
|
|
411
|
+
} else {
|
|
412
|
+
console.log(chalk.white(" Skill files: ") + chalk.cyan(targetDir));
|
|
341
413
|
}
|
|
342
414
|
if (ocConfig) {
|
|
343
|
-
console.log(
|
|
344
|
-
|
|
345
|
-
|
|
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
|
+
);
|
|
346
426
|
}
|
|
347
427
|
if (agentId) {
|
|
348
428
|
console.log(chalk.white(" Agent ID: ") + chalk.cyan(agentId));
|
|
349
429
|
}
|
|
350
430
|
if (verificationCode) {
|
|
351
|
-
console.log(
|
|
352
|
-
|
|
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
|
+
);
|
|
353
437
|
}
|
|
354
438
|
if (gatewayRestarted) {
|
|
355
|
-
console.log(
|
|
439
|
+
console.log(
|
|
440
|
+
chalk.white(" Gateway: ") + chalk.green("restarted (sessions cleared, new heartbeat active)")
|
|
441
|
+
);
|
|
356
442
|
} else if (hasOpenClaw && isGatewayRunning()) {
|
|
357
|
-
console.log(
|
|
443
|
+
console.log(
|
|
444
|
+
chalk.white(" Gateway: ") + chalk.yellow("running (use without --no-restart to auto-restart)")
|
|
445
|
+
);
|
|
358
446
|
}
|
|
359
447
|
if (!hasOpenClaw) {
|
|
360
|
-
console.log(
|
|
361
|
-
|
|
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
|
+
);
|
|
362
454
|
}
|
|
363
455
|
const webUrl = staging ? "https://staging.clawtrail.ai" : "https://clawtrail.ai";
|
|
364
456
|
const apiUrl = staging ? "https://sapi.clawtrail.ai/ct/api" : "https://api.clawtrail.ai/ct/api";
|
|
365
457
|
console.log(chalk.cyan("\n \u2500\u2500\u2500 Links \u2500\u2500\u2500\n"));
|
|
366
458
|
console.log(chalk.white(" Web: ") + chalk.blue.underline(webUrl));
|
|
367
459
|
console.log(chalk.white(" API: ") + chalk.blue.underline(apiUrl));
|
|
368
|
-
console.log(
|
|
460
|
+
console.log(
|
|
461
|
+
chalk.white(" Docs: ") + chalk.blue.underline("https://docs.clawtrail.ai")
|
|
462
|
+
);
|
|
369
463
|
console.log(chalk.cyan("\n Ready to go!\n"));
|
|
370
464
|
});
|
|
371
465
|
await program.parseAsync(process.argv);
|