@clawtrail/init 1.5.0 → 2.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.
Files changed (2) hide show
  1. package/dist/index.js +228 -365
  2. package/package.json +1 -3
package/dist/index.js CHANGED
@@ -1,8 +1,13 @@
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";
5
- import inquirer from "inquirer";
6
11
  import chalk from "chalk";
7
12
  import ora from "ora";
8
13
  import fs from "fs/promises";
@@ -10,6 +15,7 @@ import path from "path";
10
15
  import os from "os";
11
16
  import fetch from "node-fetch";
12
17
  import JSON5 from "json5";
18
+ import { execSync } from "child_process";
13
19
  var SKILL_FILES = {
14
20
  SKILL: "https://api.clawtrail.ai/ct/api/skill/clawtrail.md",
15
21
  HEARTBEAT: "https://api.clawtrail.ai/ct/api/skill/HEARTBEAT.md"
@@ -66,107 +72,36 @@ async function downloadSkillFiles(targetDir, staging = false) {
66
72
  }
67
73
  if (failed.length > 0) {
68
74
  spinner.warn(
69
- chalk.yellow(`Downloaded ${succeeded}/${downloads.length} skill files to ${chalk.cyan(targetDir)} (${env}) \u2014 ${failed.join(", ")} unavailable`)
75
+ chalk.yellow(`Downloaded ${succeeded}/${downloads.length} files (${env}) \u2014 ${failed.join(", ")} unavailable`)
70
76
  );
71
77
  } else {
72
78
  spinner.succeed(
73
- chalk.green(`Downloaded ${succeeded} skill files to ${chalk.cyan(targetDir)} (${env})`)
79
+ chalk.green(`Downloaded ${succeeded} skill files (${env})`)
74
80
  );
75
81
  }
82
+ return { succeeded, failed };
76
83
  }
77
- var REGISTRATION_ERROR_HINTS = {
78
- "unable to register another agent": "IP rate limit: only 1 agent registration per IP per 24 hours. Try again tomorrow or use a different network.",
79
- "agent name already exists": "An agent with this name is already registered. Choose a different name.",
80
- "name already taken": "An agent with this name is already registered. Choose a different name.",
81
- "wallet already registered": "This wallet address is already associated with an agent."
82
- };
83
- function getRegistrationErrorHint(message) {
84
- const lower = message.toLowerCase();
85
- for (const [pattern, hint] of Object.entries(REGISTRATION_ERROR_HINTS)) {
86
- if (lower.includes(pattern)) return hint;
87
- }
88
- return void 0;
89
- }
90
- async function registerAgent(data, staging = false) {
91
- const apiUrl = staging ? "https://sapi.clawtrail.ai/ct/api" : "https://api.clawtrail.ai/ct/api";
92
- const spinner = ora("Registering agent with ClawTrail...").start();
93
- try {
94
- const response = await fetch(`${apiUrl}/agents/register`, {
95
- method: "POST",
96
- headers: {
97
- "Content-Type": "application/json"
98
- },
99
- body: JSON.stringify(data)
100
- });
101
- if (!response.ok) {
102
- const error = await response.json();
103
- const rawMessage = error.error || "Registration failed";
104
- const hint = getRegistrationErrorHint(rawMessage);
105
- throw new Error(hint || rawMessage);
106
- }
107
- const result = await response.json();
108
- spinner.succeed(chalk.green("Agent registered successfully!"));
109
- return {
110
- agentId: result.agentId,
111
- apiKey: result.apiKey,
112
- verificationCode: result.verificationCode,
113
- statusUrl: result.statusUrl
114
- };
115
- } catch (error) {
116
- spinner.fail(chalk.red(`Registration failed: ${error.message}`));
117
- throw error;
118
- }
119
- }
120
- async function checkExistingApiKey() {
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);
121
90
  try {
122
- const envPath = path.join(process.cwd(), ".env");
123
- const content = await fs.readFile(envPath, "utf-8");
124
- return content.includes("CLAWTRAIL_API_KEY=");
91
+ await fs.copyFile(path.join(targetDir, "HEARTBEAT.md"), path.join(workspaceDir, "HEARTBEAT.md"));
92
+ placed.push("~/.openclaw/workspace/HEARTBEAT.md");
125
93
  } catch {
126
- return false;
127
- }
128
- }
129
- async function saveToEnv(apiKey) {
130
- const envPath = path.join(process.cwd(), ".env");
131
- const envContent = `
132
- # ClawTrail API Key
133
- CLAWTRAIL_API_KEY=${apiKey}
134
- `;
135
- try {
136
- let existingContent = "";
137
- try {
138
- existingContent = await fs.readFile(envPath, "utf-8");
139
- } catch {
140
- }
141
- if (existingContent.includes("CLAWTRAIL_API_KEY")) {
142
- console.log(
143
- chalk.yellow(
144
- " .env already contains CLAWTRAIL_API_KEY, not overwriting"
145
- )
146
- );
147
- return;
148
- }
149
- await fs.appendFile(envPath, envContent);
150
- console.log(chalk.green(` Saved API key to ${chalk.cyan(".env")}`));
151
- } catch (error) {
152
- console.log(
153
- chalk.yellow(` Could not save to .env: ${error.message}`)
154
- );
155
- console.log(
156
- chalk.gray(" You can manually add it: CLAWTRAIL_API_KEY=<your-key>")
157
- );
158
94
  }
159
- }
160
- async function detectOpenClaw() {
161
- const openClawDir = path.join(os.homedir(), ".openclaw");
95
+ const skillsDir = path.join(openclawDir, "skills", skillFolder);
96
+ await ensureDirectory(skillsDir);
162
97
  try {
163
- await fs.access(openClawDir);
164
- return true;
98
+ await fs.copyFile(path.join(targetDir, "SKILL.md"), path.join(skillsDir, "SKILL.md"));
99
+ placed.push(`~/.openclaw/skills/${skillFolder}/SKILL.md`);
165
100
  } catch {
166
- return false;
167
101
  }
102
+ return placed;
168
103
  }
169
- async function configureOpenClaw(apiKey, staging) {
104
+ async function configureOpenClaw(staging, apiKey) {
170
105
  const openClawDir = path.join(os.homedir(), ".openclaw");
171
106
  const configPath = path.join(openClawDir, "openclaw.json");
172
107
  const apiUrl = staging ? "https://sapi.clawtrail.ai/ct" : "https://api.clawtrail.ai/ct";
@@ -176,27 +111,13 @@ async function configureOpenClaw(apiKey, staging) {
176
111
  const existing = await fs.readFile(configPath, "utf-8");
177
112
  config = JSON5.parse(existing);
178
113
  } catch (err) {
179
- if (err.code === "ENOENT") {
180
- } else {
181
- console.log(
182
- chalk.yellow(` Could not parse existing openclaw.json \u2014 backing up and starting fresh`)
183
- );
114
+ if (err.code !== "ENOENT") {
184
115
  try {
185
116
  await fs.copyFile(configPath, configPath + ".bak");
186
- console.log(chalk.gray(` Backup saved to ${configPath}.bak`));
187
117
  } catch {
188
118
  }
189
119
  }
190
120
  }
191
- config.plugins ??= {};
192
- config.plugins.entries ??= {};
193
- config.plugins.entries.clawtrail = {
194
- enabled: true,
195
- config: {
196
- apiKey,
197
- apiUrl
198
- }
199
- };
200
121
  config.agents ??= {};
201
122
  config.agents.defaults ??= {};
202
123
  config.agents.defaults.heartbeat = {
@@ -208,302 +129,244 @@ async function configureOpenClaw(apiKey, staging) {
208
129
  config.skills.entries.clawtrail ??= {};
209
130
  config.skills.entries.clawtrail.enabled = true;
210
131
  config.skills.entries.clawtrail.env ??= {};
211
- config.skills.entries.clawtrail.env.CLAWTRAIL_API_KEY = apiKey;
212
132
  config.skills.entries.clawtrail.env.CLAWTRAIL_API_URL = apiUrl;
133
+ if (apiKey) {
134
+ config.skills.entries.clawtrail.env.CLAWTRAIL_API_KEY = apiKey;
135
+ }
213
136
  await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
214
- console.log(chalk.green(` Configured ClawTrail in ${chalk.cyan("~/.openclaw/openclaw.json")}`));
215
- console.log(chalk.green(` Heartbeat: ${chalk.cyan("every 30s")} \u2014 agent will be autonomously active`));
137
+ return {
138
+ configPath: "~/.openclaw/openclaw.json",
139
+ heartbeat: "every 30s",
140
+ hasApiKey: !!apiKey
141
+ };
216
142
  }
217
- async function configureOpenClawHeartbeat(staging) {
218
- const openClawDir = path.join(os.homedir(), ".openclaw");
219
- const configPath = path.join(openClawDir, "openclaw.json");
220
- await ensureDirectory(openClawDir);
221
- let config = {};
143
+ async function registerAgent(name, description, staging, opts = {}) {
144
+ const apiUrl = staging ? "https://sapi.clawtrail.ai/ct/api" : "https://api.clawtrail.ai/ct/api";
145
+ const spinner = ora("Registering agent...").start();
222
146
  try {
223
- const existing = await fs.readFile(configPath, "utf-8");
224
- config = JSON5.parse(existing);
225
- } catch (err) {
226
- if (err.code !== "ENOENT") {
227
- try {
228
- await fs.copyFile(configPath, configPath + ".bak");
229
- } catch {
230
- }
147
+ const response = await fetch(`${apiUrl}/agents/register`, {
148
+ method: "POST",
149
+ headers: { "Content-Type": "application/json" },
150
+ body: JSON.stringify({
151
+ name,
152
+ description,
153
+ bio: opts.bio || void 0,
154
+ agentType: opts.agentType || "openclaw",
155
+ framework: opts.framework || void 0,
156
+ skills: opts.skills?.length ? opts.skills : void 0,
157
+ capabilities: opts.capabilities?.length ? opts.capabilities : ["general"]
158
+ })
159
+ });
160
+ if (!response.ok) {
161
+ const error = await response.json();
162
+ throw new Error(error.error || "Registration failed");
231
163
  }
164
+ const result = await response.json();
165
+ spinner.succeed(chalk.green("Agent registered"));
166
+ return {
167
+ agentId: result.agentId,
168
+ apiKey: result.apiKey,
169
+ verificationCode: result.verificationCode,
170
+ statusUrl: result.statusUrl
171
+ };
172
+ } catch (error) {
173
+ spinner.fail(chalk.red(`Registration failed: ${error.message}`));
174
+ throw error;
232
175
  }
233
- config.agents ??= {};
234
- config.agents.defaults ??= {};
235
- config.agents.defaults.heartbeat = {
236
- every: "30s",
237
- target: "last"
238
- };
239
- const apiUrl = staging ? "https://sapi.clawtrail.ai/ct" : "https://api.clawtrail.ai/ct";
240
- config.skills ??= {};
241
- config.skills.entries ??= {};
242
- config.skills.entries.clawtrail ??= {};
243
- config.skills.entries.clawtrail.enabled = true;
244
- config.skills.entries.clawtrail.env ??= {};
245
- config.skills.entries.clawtrail.env.CLAWTRAIL_API_URL = apiUrl;
246
- await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
247
- console.log(chalk.green(` Heartbeat configured: ${chalk.cyan("every 30s")} in ${chalk.cyan("~/.openclaw/openclaw.json")}`));
248
176
  }
249
- async function copyToOpenClawSkills(targetDir, staging) {
250
- const skillFolder = staging ? "clawtrail-staging" : "clawtrail";
251
- const openclawDir = path.join(os.homedir(), ".openclaw");
252
- const workspaceDir = path.join(openclawDir, "workspace");
253
- await ensureDirectory(workspaceDir);
177
+ async function detectOpenClaw() {
254
178
  try {
255
- await fs.copyFile(path.join(targetDir, "HEARTBEAT.md"), path.join(workspaceDir, "HEARTBEAT.md"));
256
- console.log(chalk.green(" HEARTBEAT.md -> ") + chalk.cyan("~/.openclaw/workspace/HEARTBEAT.md"));
179
+ await fs.access(path.join(os.homedir(), ".openclaw"));
180
+ return true;
257
181
  } catch {
182
+ return false;
258
183
  }
259
- const skillsDir = path.join(openclawDir, "skills", skillFolder);
260
- await ensureDirectory(skillsDir);
184
+ }
185
+ function getHeartbeatVersion(filePath) {
261
186
  try {
262
- await fs.copyFile(path.join(targetDir, "SKILL.md"), path.join(skillsDir, "SKILL.md"));
263
- console.log(chalk.green(" SKILL.md -> ") + chalk.cyan(`~/.openclaw/skills/${skillFolder}/SKILL.md`));
187
+ const content = __require("fs").readFileSync(filePath, "utf-8");
188
+ const match = content.match(/\*\*Version:\*\*\s+([\d.]+)/);
189
+ return match?.[1] ?? null;
190
+ } catch {
191
+ return null;
192
+ }
193
+ }
194
+ function isGatewayRunning() {
195
+ try {
196
+ const result = execSync("pgrep -f openclaw-gateway", { encoding: "utf-8", stdio: "pipe" });
197
+ return result.trim().length > 0;
198
+ } catch {
199
+ return false;
200
+ }
201
+ }
202
+ async function clearOpenClawSessions() {
203
+ const openclawDir = path.join(os.homedir(), ".openclaw");
204
+ const sessionsDir = path.join(openclawDir, "agents", "main", "sessions");
205
+ let cleared = 0;
206
+ try {
207
+ const files = await fs.readdir(sessionsDir);
208
+ for (const file of files) {
209
+ if (file.endsWith(".jsonl") || file.endsWith(".lock") || file === "sessions.json") {
210
+ await fs.unlink(path.join(sessionsDir, file));
211
+ cleared++;
212
+ }
213
+ }
264
214
  } catch {
265
215
  }
216
+ return cleared;
217
+ }
218
+ async function restartGateway(oldVersion, newVersion) {
219
+ const spinner = ora("Restarting OpenClaw gateway...").start();
220
+ try {
221
+ try {
222
+ execSync("pkill -f openclaw-gateway", { stdio: "pipe" });
223
+ } catch {
224
+ }
225
+ await new Promise((r) => setTimeout(r, 2e3));
226
+ const cleared = await clearOpenClawSessions();
227
+ try {
228
+ const logPath = path.join(os.homedir(), "openclaw-gateway.log");
229
+ execSync(`nohup openclaw-gateway > ${logPath} 2>&1 &`, {
230
+ stdio: "pipe",
231
+ shell: "/bin/bash"
232
+ });
233
+ } catch {
234
+ }
235
+ await new Promise((r) => setTimeout(r, 4e3));
236
+ const running = isGatewayRunning();
237
+ if (running) {
238
+ const versionMsg = oldVersion && newVersion && oldVersion !== newVersion ? ` (${chalk.gray(oldVersion)} \u2192 ${chalk.cyan(newVersion)})` : newVersion ? ` (${chalk.cyan(`v${newVersion}`)})` : "";
239
+ spinner.succeed(
240
+ chalk.green(`Gateway restarted${versionMsg} \u2014 ${cleared} cached sessions cleared`)
241
+ );
242
+ return true;
243
+ } else {
244
+ spinner.warn(chalk.yellow("Gateway killed but may not have restarted \u2014 check manually"));
245
+ return false;
246
+ }
247
+ } catch (err) {
248
+ spinner.fail(chalk.red(`Gateway restart failed: ${err.message}`));
249
+ return false;
250
+ }
266
251
  }
267
252
  async function main() {
268
- console.log(
269
- chalk.cyan.bold("\n ClawTrail Agent Skill Installer\n")
270
- );
253
+ console.log(chalk.cyan.bold("\n ClawTrail Agent Installer\n"));
271
254
  const program = new Command();
272
- program.name("clawtrail-init").description("Initialize ClawTrail skill files for AI agents").version("1.5.0").option("-d, --dir <path>", "Target directory", "./clawtrail-skills").option("-s, --staging", "Use staging environment", false).option("--no-register", "Skip agent registration").option("--no-interactive", "Skip interactive prompts").action(async (options) => {
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) => {
273
256
  const targetDir = path.resolve(process.cwd(), options.dir);
274
257
  const staging = options.staging;
258
+ const env = staging ? "staging" : "production";
275
259
  await downloadSkillFiles(targetDir, staging);
276
260
  const hasOpenClaw = await detectOpenClaw();
261
+ const heartbeatPath = path.join(os.homedir(), ".openclaw", "workspace", "HEARTBEAT.md");
262
+ const oldHeartbeatVersion = hasOpenClaw ? getHeartbeatVersion(heartbeatPath) : null;
263
+ let placedFiles = [];
277
264
  if (hasOpenClaw) {
278
- console.log(chalk.cyan(" OpenClaw detected!\n"));
265
+ const spinner = ora("Placing files in OpenClaw directories...").start();
279
266
  try {
280
- await copyToOpenClawSkills(targetDir, staging);
281
- } catch (copyErr) {
282
- console.log(chalk.yellow(` Could not copy to OpenClaw workspace: ${copyErr.message}`));
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}`));
283
271
  }
284
272
  }
285
- if (options.register && options.interactive) {
286
- const hasExistingKey = await checkExistingApiKey();
287
- if (hasExistingKey) {
288
- console.log(
289
- chalk.yellow("\n An existing CLAWTRAIL_API_KEY was found in .env")
290
- );
291
- const { registerAnyway } = await inquirer.prompt([
273
+ let apiKey = options.apiKey || void 0;
274
+ let agentId;
275
+ let verificationCode;
276
+ if (options.name) {
277
+ if (!options.description) {
278
+ console.log(chalk.red("\n --description is required when using --name\n"));
279
+ process.exit(1);
280
+ }
281
+ try {
282
+ const result = await registerAgent(
283
+ options.name.trim(),
284
+ options.description.trim(),
285
+ staging,
292
286
  {
293
- type: "confirm",
294
- name: "registerAnyway",
295
- message: "Register a new agent anyway? (existing key will not be overwritten)",
296
- default: false
287
+ bio: options.bio?.trim(),
288
+ agentType: options.agentType,
289
+ framework: options.framework?.trim(),
290
+ skills: options.skills?.split(",").map((s) => s.trim()).filter(Boolean),
291
+ capabilities: options.capabilities?.split(",").map((s) => s.trim()).filter(Boolean)
297
292
  }
298
- ]);
299
- if (!registerAnyway) {
300
- console.log(chalk.gray(" Skipping registration \u2014 using existing credentials.\n"));
301
- options.register = false;
302
- }
293
+ );
294
+ apiKey = result.apiKey;
295
+ agentId = result.agentId;
296
+ verificationCode = result.verificationCode;
297
+ } catch {
303
298
  }
304
299
  }
305
- if (options.register && options.interactive) {
306
- console.log(chalk.cyan("\n Agent Registration (Optional)\n"));
307
- const { registrationChoice } = await inquirer.prompt([
308
- {
309
- type: "list",
310
- name: "registrationChoice",
311
- message: "How would you like to register your agent?",
312
- choices: [
313
- { name: "Let my bot register itself (recommended for OpenClaw)", value: "bot" },
314
- { name: "Enter agent info manually now", value: "manual" },
315
- { name: "Skip registration", value: "skip" }
316
- ],
317
- default: hasOpenClaw ? "bot" : "manual"
318
- }
319
- ]);
320
- if (registrationChoice === "bot") {
321
- console.log(chalk.green("\n Got it! Your bot will register itself.\n"));
322
- console.log(chalk.gray(" Your bot can register using the SKILL.md instructions."));
323
- console.log(chalk.gray(" It will call POST /api/agents/register with its own info."));
324
- if (hasOpenClaw) {
325
- console.log(chalk.gray(" The ClawTrail skill file has been placed in your OpenClaw workspace."));
326
- console.log(chalk.gray(" Just start your bot \u2014 it will read the skill and register automatically."));
327
- const { configureHeartbeat } = await inquirer.prompt([
328
- {
329
- type: "confirm",
330
- name: "configureHeartbeat",
331
- message: "Configure heartbeat (30s autonomous loop) in OpenClaw?",
332
- default: true
333
- }
334
- ]);
335
- if (configureHeartbeat) {
336
- try {
337
- await configureOpenClawHeartbeat(staging);
338
- } catch (hbErr) {
339
- console.log(chalk.yellow(` Heartbeat config failed: ${hbErr.message}`));
340
- }
341
- }
342
- console.log();
343
- } else {
344
- console.log(chalk.gray(" Point your agent at the SKILL.md file to get started.\n"));
345
- }
346
- } else if (registrationChoice === "manual") {
347
- const answers = await inquirer.prompt([
348
- {
349
- type: "input",
350
- name: "name",
351
- message: "Agent name:",
352
- validate: (input) => {
353
- if (!input.trim()) return "Name is required";
354
- if (input.trim().length < 2) return "Name must be at least 2 characters";
355
- if (input.trim().length > 100) return "Name must be under 100 characters";
356
- return true;
357
- }
358
- },
359
- {
360
- type: "input",
361
- name: "description",
362
- message: "Agent description:",
363
- validate: (input) => {
364
- if (!input.trim()) return "Description is required";
365
- if (input.trim().length < 10) return "Description must be at least 10 characters";
366
- return true;
367
- }
368
- },
369
- {
370
- type: "input",
371
- name: "bio",
372
- message: "Short bio (optional):"
373
- },
374
- {
375
- type: "list",
376
- name: "agentType",
377
- message: "Agent type:",
378
- choices: [
379
- { name: "OpenClaw Agent", value: "openclaw" },
380
- { name: "Custom Agent", value: "custom" },
381
- { name: "MCP Server", value: "mcp-server" },
382
- { name: "A2A Agent", value: "a2a-agent" },
383
- { name: "ERC-8004 Agent", value: "erc8004" }
384
- ],
385
- default: hasOpenClaw ? "openclaw" : "custom"
386
- },
387
- {
388
- type: "input",
389
- name: "framework",
390
- message: "Framework (optional, e.g., langchain, autogen):"
391
- },
392
- {
393
- type: "input",
394
- name: "skills",
395
- message: "Skills (optional, comma-separated, e.g., coding,research,dkg):"
396
- },
397
- {
398
- type: "input",
399
- name: "capabilities",
400
- message: "Capabilities (optional, comma-separated, e.g., research,analysis,automation):"
401
- }
402
- ]);
300
+ let ocConfig;
301
+ if (hasOpenClaw) {
302
+ const spinner = ora("Configuring OpenClaw heartbeat...").start();
303
+ try {
304
+ ocConfig = await configureOpenClaw(staging, apiKey);
305
+ spinner.succeed(chalk.green(`Heartbeat configured: ${chalk.cyan("every 30s")}`));
306
+ } catch (err) {
307
+ spinner.warn(chalk.yellow(`Config failed: ${err.message}`));
308
+ }
309
+ }
310
+ let gatewayRestarted = false;
311
+ if (hasOpenClaw && options.restart !== false) {
312
+ const newHeartbeatVersion = getHeartbeatVersion(heartbeatPath);
313
+ if (isGatewayRunning()) {
314
+ gatewayRestarted = await restartGateway(oldHeartbeatVersion, newHeartbeatVersion);
315
+ }
316
+ }
317
+ if (apiKey) {
318
+ const envPath = path.join(process.cwd(), ".env");
319
+ try {
320
+ let existing = "";
403
321
  try {
404
- const { agentId, apiKey, verificationCode, statusUrl } = await registerAgent(
405
- {
406
- name: answers.name.trim(),
407
- description: answers.description.trim(),
408
- bio: answers.bio?.trim() || void 0,
409
- agentType: answers.agentType,
410
- framework: answers.framework?.trim() || void 0,
411
- skills: answers.skills ? answers.skills.split(",").map((s) => s.trim()).filter(Boolean) : [],
412
- capabilities: answers.capabilities ? answers.capabilities.split(",").map((s) => s.trim()).filter(Boolean) : ["general"]
413
- },
414
- staging
415
- );
416
- console.log(chalk.green("\n Registration Complete!\n"));
417
- console.log(chalk.white("Agent ID: ") + chalk.cyan(agentId));
418
- console.log(
419
- chalk.white("Verification Code: ") + chalk.yellow(verificationCode)
420
- );
421
- console.log(
422
- chalk.white("DKG Status: ") + chalk.yellow("pending \u2014 minting queued (~30s)")
423
- );
424
- console.log(
425
- chalk.white("Status URL: ") + chalk.blue.underline(statusUrl)
426
- );
427
- await saveToEnv(apiKey);
428
- console.log(chalk.yellow("\n IMPORTANT: Your API key has been saved to .env"));
429
- console.log(chalk.yellow(" The verification code above will NOT be shown again.\n"));
430
- if (hasOpenClaw && answers.agentType === "openclaw") {
431
- const { configureOC } = await inquirer.prompt([
432
- {
433
- type: "confirm",
434
- name: "configureOC",
435
- message: "Auto-configure ClawTrail in OpenClaw (~/.openclaw/openclaw.json)?",
436
- default: true
437
- }
438
- ]);
439
- if (configureOC) {
440
- try {
441
- await configureOpenClaw(apiKey, staging);
442
- } catch (ocErr) {
443
- console.log(chalk.yellow(` OpenClaw config failed: ${ocErr.message}`));
444
- }
445
- }
446
- }
447
- } catch (error) {
448
- console.log(
449
- chalk.red(`
450
- Registration failed: ${error.message}
451
- `)
452
- );
453
- console.log(
454
- chalk.gray("You can register later via the ClawTrail API.\n")
455
- );
322
+ existing = await fs.readFile(envPath, "utf-8");
323
+ } catch {
456
324
  }
325
+ if (!existing.includes("CLAWTRAIL_API_KEY")) {
326
+ await fs.appendFile(envPath, `
327
+ # ClawTrail API Key
328
+ CLAWTRAIL_API_KEY=${apiKey}
329
+ `);
330
+ }
331
+ } catch {
457
332
  }
458
333
  }
459
- console.log(chalk.cyan.bold("\n Next Steps:\n"));
460
- console.log(
461
- chalk.white("1. ") + chalk.gray(`Read the skill files in ${chalk.cyan(targetDir)}`)
462
- );
463
- console.log(
464
- chalk.white("2. ") + chalk.gray("Use the API key to authenticate your agent:")
465
- );
466
- console.log(
467
- chalk.gray(` ${chalk.white("Authorization:")} Bearer YOUR_API_KEY`)
468
- );
469
- console.log(
470
- chalk.white("3. ") + chalk.gray(
471
- "Start interacting with ClawTrail (discussions, bounties, etc.)"
472
- )
473
- );
474
- console.log(
475
- chalk.white("4. ") + chalk.gray(
476
- "Have your human operator claim you using the verification code"
477
- )
478
- );
479
- if (hasOpenClaw) {
480
- console.log(
481
- chalk.white("5. ") + chalk.gray("(OpenClaw) Heartbeat loop: ") + chalk.cyan("every 30s") + chalk.gray(" \u2014 starts on next session")
482
- );
483
- console.log(
484
- chalk.white("6. ") + chalk.gray("(OpenClaw) Config at ") + chalk.cyan("~/.openclaw/openclaw.json")
485
- );
486
- console.log(
487
- chalk.white("7. ") + chalk.gray("(OpenClaw) Skill installed to ") + chalk.cyan("~/.openclaw/skills/")
488
- );
334
+ console.log(chalk.cyan.bold("\n \u2500\u2500\u2500 Results \u2500\u2500\u2500\n"));
335
+ console.log(chalk.white(" Environment: ") + chalk.cyan(env));
336
+ console.log(chalk.white(" Skill files: ") + chalk.cyan(targetDir));
337
+ if (placedFiles.length > 0) {
338
+ for (const f of placedFiles) {
339
+ console.log(chalk.white(" Placed: ") + chalk.cyan(f));
340
+ }
341
+ }
342
+ 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)"));
346
+ }
347
+ if (agentId) {
348
+ console.log(chalk.white(" Agent ID: ") + chalk.cyan(agentId));
349
+ }
350
+ if (verificationCode) {
351
+ console.log(chalk.white(" Verify code: ") + chalk.yellow(verificationCode));
352
+ console.log(chalk.gray(" (save this \u2014 shown only once)"));
353
+ }
354
+ if (gatewayRestarted) {
355
+ console.log(chalk.white(" Gateway: ") + chalk.green("restarted (sessions cleared, new heartbeat active)"));
356
+ } else if (hasOpenClaw && isGatewayRunning()) {
357
+ console.log(chalk.white(" Gateway: ") + chalk.yellow("running (use without --no-restart to auto-restart)"));
358
+ }
359
+ 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."));
489
362
  }
490
- const env = staging ? "staging" : "production";
491
363
  const webUrl = staging ? "https://staging.clawtrail.ai" : "https://clawtrail.ai";
492
364
  const apiUrl = staging ? "https://sapi.clawtrail.ai/ct/api" : "https://api.clawtrail.ai/ct/api";
493
- console.log(chalk.cyan("\n Resources:\n"));
494
- console.log(
495
- chalk.white("Web: ") + chalk.blue.underline(webUrl)
496
- );
497
- console.log(
498
- chalk.white("API: ") + chalk.blue.underline(apiUrl)
499
- );
500
- console.log(
501
- chalk.white("Docs: ") + chalk.blue.underline("https://docs.clawtrail.ai")
502
- );
503
- console.log(
504
- chalk.white("Environment: ") + chalk.cyan(env)
505
- );
506
- console.log(chalk.cyan("\n Happy building with ClawTrail!\n"));
365
+ console.log(chalk.cyan("\n \u2500\u2500\u2500 Links \u2500\u2500\u2500\n"));
366
+ console.log(chalk.white(" Web: ") + chalk.blue.underline(webUrl));
367
+ console.log(chalk.white(" API: ") + chalk.blue.underline(apiUrl));
368
+ console.log(chalk.white(" Docs: ") + chalk.blue.underline("https://docs.clawtrail.ai"));
369
+ console.log(chalk.cyan("\n Ready to go!\n"));
507
370
  });
508
371
  await program.parseAsync(process.argv);
509
372
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawtrail/init",
3
- "version": "1.5.0",
3
+ "version": "2.1.0",
4
4
  "description": "CLI installer for ClawTrail AI agent skill files",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -23,14 +23,12 @@
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
25
  "commander": "^12.0.0",
26
- "inquirer": "^9.2.15",
27
26
  "chalk": "^5.3.0",
28
27
  "ora": "^8.0.1",
29
28
  "node-fetch": "^3.3.2",
30
29
  "json5": "^2.2.3"
31
30
  },
32
31
  "devDependencies": {
33
- "@types/inquirer": "^9.0.7",
34
32
  "@types/node": "^22.0.0",
35
33
  "tsup": "^8.5.0",
36
34
  "typescript": "^5.7.2"