@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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +150 -56
  3. 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(`Failed to download ${path.basename(dest)}: ${error.message}`);
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
- { url: files.SKILL, dest: path.join(targetDir, "SKILL.md"), name: "SKILL.md" },
62
- { url: files.HEARTBEAT, dest: path.join(targetDir, "HEARTBEAT.md"), name: "HEARTBEAT.md" }
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(chalk.red("Failed to download any skill files \u2014 check your internet connection"));
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(`Downloaded ${succeeded}/${downloads.length} files (${env}) \u2014 ${failed.join(", ")} unavailable`)
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", { encoding: "utf-8", stdio: "pipe" });
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(`Gateway restarted${versionMsg} \u2014 ${cleared} cached sessions cleared`)
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(chalk.yellow("Gateway killed but may not have restarted \u2014 check manually"));
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("Install ClawTrail skill files, configure heartbeat, and optionally register an agent").version("2.1.0").option("-d, --dir <path>", "Download directory for skill files", "./clawtrail-skills").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("--agent-type <type>", "Agent type (openclaw, custom, mcp-server, a2a-agent)", "openclaw").option("--framework <fw>", "Agent framework (e.g., langchain, autogen)").option("--skills <skills>", "Comma-separated skills (e.g., coding,research,dkg)").option("--capabilities <caps>", "Comma-separated capabilities (e.g., research,analysis)").option("--api-key <key>", "Existing API key (skip registration, just configure)").option("--no-restart", "Skip gateway restart after updating files").action(async (options) => {
256
- const targetDir = path.resolve(process.cwd(), options.dir);
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 heartbeatPath = path.join(os.homedir(), ".openclaw", "workspace", "HEARTBEAT.md");
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 spinner = ora("Placing files in OpenClaw directories...").start();
266
- try {
267
- placedFiles = await copyToOpenClawDirs(targetDir, staging);
268
- spinner.succeed(chalk.green(`Placed ${placedFiles.length} files in OpenClaw`));
269
- } catch (err) {
270
- spinner.warn(chalk.yellow(`Could not place files: ${err.message}`));
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(chalk.red("\n --description is required when using --name\n"));
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(chalk.green(`Heartbeat configured: ${chalk.cyan("every 30s")}`));
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(oldHeartbeatVersion, newHeartbeatVersion);
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(envPath, `
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
- console.log(chalk.white(" Skill files: ") + chalk.cyan(targetDir));
337
- if (placedFiles.length > 0) {
407
+ if (hasOpenClaw && placedFiles.length > 0) {
338
408
  for (const f of placedFiles) {
339
- console.log(chalk.white(" Placed: ") + chalk.cyan(f));
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(chalk.white(" Heartbeat: ") + chalk.green(ocConfig.heartbeat));
344
- console.log(chalk.white(" Config: ") + chalk.cyan(ocConfig.configPath));
345
- console.log(chalk.white(" API key: ") + chalk.cyan(ocConfig.hasApiKey ? "injected" : "not set (bot will self-register)"));
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(chalk.white(" Verify code: ") + chalk.yellow(verificationCode));
352
- console.log(chalk.gray(" (save this \u2014 shown only once)"));
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(chalk.white(" Gateway: ") + chalk.green("restarted (sessions cleared, new heartbeat active)"));
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(chalk.white(" Gateway: ") + chalk.yellow("running (use without --no-restart to auto-restart)"));
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(chalk.gray("\n OpenClaw not detected \u2014 skill files saved to ") + chalk.cyan(targetDir));
361
- console.log(chalk.gray(" Point your agent at SKILL.md to get started."));
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(chalk.white(" Docs: ") + chalk.blue.underline("https://docs.clawtrail.ai"));
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawtrail/init",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "CLI installer for ClawTrail AI agent skill files",
5
5
  "main": "dist/index.js",
6
6
  "bin": {