@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.
- package/dist/index.js +228 -365
- 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}
|
|
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
|
|
79
|
+
chalk.green(`Downloaded ${succeeded} skill files (${env})`)
|
|
74
80
|
);
|
|
75
81
|
}
|
|
82
|
+
return { succeeded, failed };
|
|
76
83
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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.
|
|
164
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
215
|
-
|
|
137
|
+
return {
|
|
138
|
+
configPath: "~/.openclaw/openclaw.json",
|
|
139
|
+
heartbeat: "every 30s",
|
|
140
|
+
hasApiKey: !!apiKey
|
|
141
|
+
};
|
|
216
142
|
}
|
|
217
|
-
async function
|
|
218
|
-
const
|
|
219
|
-
const
|
|
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
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
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.
|
|
256
|
-
|
|
179
|
+
await fs.access(path.join(os.homedir(), ".openclaw"));
|
|
180
|
+
return true;
|
|
257
181
|
} catch {
|
|
182
|
+
return false;
|
|
258
183
|
}
|
|
259
|
-
|
|
260
|
-
|
|
184
|
+
}
|
|
185
|
+
function getHeartbeatVersion(filePath) {
|
|
261
186
|
try {
|
|
262
|
-
|
|
263
|
-
|
|
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("
|
|
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
|
-
|
|
265
|
+
const spinner = ora("Placing files in OpenClaw directories...").start();
|
|
279
266
|
try {
|
|
280
|
-
await
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
);
|
|
291
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
293
|
+
);
|
|
294
|
+
apiKey = result.apiKey;
|
|
295
|
+
agentId = result.agentId;
|
|
296
|
+
verificationCode = result.verificationCode;
|
|
297
|
+
} catch {
|
|
303
298
|
}
|
|
304
299
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
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
|
|
460
|
-
console.log(
|
|
461
|
-
|
|
462
|
-
)
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
);
|
|
469
|
-
|
|
470
|
-
chalk.white("
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
)
|
|
478
|
-
|
|
479
|
-
if (
|
|
480
|
-
console.log(
|
|
481
|
-
|
|
482
|
-
);
|
|
483
|
-
|
|
484
|
-
|
|
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
|
|
494
|
-
console.log(
|
|
495
|
-
|
|
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.
|
|
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"
|