clawmarketbot 0.1.2 → 0.1.4

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 +58 -28
  2. package/dist/index.js +117 -263
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # clawmarketbot
2
2
 
3
- CLI for [ClawMarket](https://clawmarket.cc) — download and install OpenClaw agent configs directly from the marketplace or from npm.
3
+ CLI for [ClawMarket](https://clawmarket.cc) — download and install OpenClaw agent configs directly from the marketplace.
4
4
 
5
5
  ## Requirements
6
6
 
7
7
  - Node.js 18+
8
8
  - OpenClaw installed and configured (`~/.openclaw/openclaw.json` must exist)
9
- - `unzip` or `tar` available in your PATH
9
+ - `unzip` available in your PATH (macOS and most Linux distros include it)
10
10
 
11
11
  ## Install
12
12
 
@@ -16,55 +16,85 @@ npm install -g clawmarketbot
16
16
 
17
17
  ## Usage
18
18
 
19
- ### Install a bot from an npm package
19
+ ### Install an agent from a download token
20
20
 
21
21
  ```bash
22
- clawmarketbot config install-npm @clawmarket/my-bot
22
+ clawmarketbot config install <token>
23
23
  ```
24
24
 
25
- Optionally pin a version:
25
+ Tokens are obtained from the ClawMarket download modal on the config detail page.
26
26
 
27
- ```bash
28
- clawmarketbot config install-npm @clawmarket/my-bot@1.2.0
29
- ```
27
+ The command will:
30
28
 
31
- ### Install a bot from a one-time download token
29
+ 1. Download the bot package from ClawMarket and verify its checksum
30
+ 2. Locate the `source/bot-config.json` manifest inside the zip
31
+ 3. Prompt you for an agent ID (a unique name for this instance)
32
+ 4. Resolve source → target install paths using the manifest
33
+ 5. Stage workspace and agent state into a temp directory
34
+ 6. Patch `openclaw.json` with the new agent entry (heartbeat, identity, tools.web)
35
+ 7. Prompt you to install any packaged cron jobs
36
+ 8. Atomically commit everything — workspace, agent state, memory DB, cron jobs, and config update — with full rollback on failure
37
+ 9. Prompt for any required API keys and save them to `openclaw.json` / `.env`
38
+ 10. Run an optional `installer.sh` post-hook if bundled in the package
39
+ 11. Validate required workspace files and directories
40
+ 12. Print a summary
41
+
42
+ ### Download a config artifact to disk (without installing)
32
43
 
33
44
  ```bash
34
- clawmarketbot config install <token>
45
+ clawmarketbot config download <token>
35
46
  ```
36
47
 
37
- Tokens are obtained from the ClawMarket download modal.
48
+ Saves the raw zip file to the current directory.
38
49
 
39
- ### Download a config artifact to disk
50
+ ### Authentication
40
51
 
41
52
  ```bash
42
- clawmarketbot config download <token>
53
+ clawmarketbot auth login # save a ClawMarket API key
54
+ clawmarketbot auth status # check current login status
55
+ clawmarketbot auth logout # clear saved credentials
43
56
  ```
44
57
 
45
- ### Auth
58
+ You can also pass the API key via stdin (recommended for automation):
46
59
 
47
60
  ```bash
48
- clawmarketbot auth login # save an API key
49
- clawmarketbot auth status # check current auth
50
- clawmarketbot auth logout # clear credentials
61
+ echo "$MY_API_KEY" | clawmarketbot auth login --stdin
51
62
  ```
52
63
 
53
64
  ## Environment Variables
54
65
 
55
66
  | Variable | Default | Purpose |
56
67
  |---|---|---|
57
- | `CLAWMARKET_API_URL` | `https://api.clawmarket.cc` | ClawMarket backend URL |
58
- | `OPENCLAW_HOME` | `os.homedir()` | OpenClaw home directory |
59
- | `OPENCLAW_STATE_DIR` | `~/.openclaw` | OpenClaw state directory |
60
- | `OPENCLAW_CONFIG_PATH` | `~/.openclaw/openclaw.json` | OpenClaw config file |
68
+ | `CLAWMARKET_API_URL` | *(required)* | ClawMarket backend base URL — set this to your ClawMarket instance |
69
+ | `OPENCLAW_HOME` | `os.homedir()` | Override the home directory used for tilde expansion |
70
+ | `OPENCLAW_STATE_DIR` | `~/.openclaw` | Override the OpenClaw state directory |
71
+ | `OPENCLAW_CONFIG_PATH` | `~/.openclaw/openclaw.json` | Override the OpenClaw config file path |
61
72
 
62
- ## How npm-based install works
73
+ Example:
63
74
 
64
- When you run `clawmarketbot config install-npm @clawmarket/some-bot`:
75
+ ```bash
76
+ export CLAWMARKET_API_URL=https://api.clawmarket.cc
77
+ clawmarketbot config install <token>
78
+ ```
79
+
80
+ ## What gets installed
81
+
82
+ When you run `config install`, the CLI installs:
83
+
84
+ | What | Where |
85
+ |---|---|
86
+ | Agent workspace | `~/.openclaw/workspace-<agent-id>/` |
87
+ | Agent state dir | `~/.openclaw/agents/<agent-id>/` |
88
+ | Memory database (SQLite) | `~/.openclaw/memory/<agent-id>.sqlite` *(if bundled)* |
89
+ | Cron jobs | `~/.openclaw/cron/jobs.json` *(merged, not overwritten)* |
90
+ | Agent registration | `~/.openclaw/openclaw.json` *(appended to agents.list)* |
91
+ | API keys | `~/.openclaw/openclaw.json` and/or `~/.openclaw/.env` |
65
92
 
66
- 1. Package metadata is fetched from the npm registry
67
- 2. The tarball is downloaded and its integrity verified
68
- 3. A `bot.zip` bundled inside the package is extracted
69
- 4. The bot's `installer.sh` is run, registering the agent in your OpenClaw config
70
- 5. All temp files are cleaned up automatically
93
+ All install paths are declared inside the package's `bot-config.json` manifest and may use `{agent_id}` template substitution.
94
+
95
+ ## After installing
96
+
97
+ ```bash
98
+ openclaw agents list # confirm the new agent appears
99
+ openclaw gateway restart # pick up the new config
100
+ ```
package/dist/index.js CHANGED
@@ -3,9 +3,27 @@
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
5
 
6
- // src/commands/auth.ts
6
+ // src/commands/config.ts
7
+ import fs3 from "fs/promises";
8
+ import path2 from "path";
9
+ import os2 from "os";
10
+ import { createHash, randomUUID } from "crypto";
11
+ import { spawn } from "child_process";
12
+ import JSON52 from "json5";
7
13
  import fetch from "node-fetch";
8
14
 
15
+ // ../../contracts/token.js
16
+ var CLAWMARKET_DOWNLOAD_TOKEN_PATTERN = "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
17
+ var CLAWMARKET_DOWNLOAD_TOKEN_REGEX = new RegExp(
18
+ `^${CLAWMARKET_DOWNLOAD_TOKEN_PATTERN}$`,
19
+ "i"
20
+ );
21
+ var CLAWMARKET_ARTIFACT_SHA256_HEADER = "x-clawmarket-sha256";
22
+ function isClawMarketDownloadToken(value) {
23
+ if (typeof value !== "string") return false;
24
+ return CLAWMARKET_DOWNLOAD_TOKEN_REGEX.test(value.trim());
25
+ }
26
+
9
27
  // src/lib/prompts.ts
10
28
  import { checkbox, confirm as promptConfirm, input, password, select } from "@inquirer/prompts";
11
29
 
@@ -44,10 +62,6 @@ async function askWithDefault(question, defaultValue) {
44
62
  });
45
63
  return value.trim() || defaultValue.trim();
46
64
  }
47
- async function askSecret(question) {
48
- const value = await password({ message: question.trim() || "Secret", mask: true });
49
- return value.trim();
50
- }
51
65
  async function askOptional(question, hintUrl) {
52
66
  if (hintUrl) {
53
67
  console.log(` Get one at: ${hintUrl}`);
@@ -60,10 +74,11 @@ async function confirm(question, defaultYes = true) {
60
74
  return promptConfirm({ message: question, default: defaultYes });
61
75
  }
62
76
 
63
- // src/lib/auth-config.ts
77
+ // src/lib/openclaw.ts
64
78
  import path from "path";
65
79
  import fs2 from "fs/promises";
66
80
  import os from "os";
81
+ import JSON5 from "json5";
67
82
 
68
83
  // src/lib/fs.ts
69
84
  import fs from "fs/promises";
@@ -76,167 +91,7 @@ async function fileExists(filePath) {
76
91
  }
77
92
  }
78
93
 
79
- // src/lib/auth-config.ts
80
- function authConfigDir() {
81
- return path.join(os.homedir(), ".clawmarket");
82
- }
83
- function authConfigPath() {
84
- return path.join(authConfigDir(), "clawmarket.json");
85
- }
86
- async function readStoredApiKey() {
87
- const configPath = authConfigPath();
88
- if (!await fileExists(configPath)) return null;
89
- try {
90
- const raw = await fs2.readFile(configPath, "utf-8");
91
- const parsed = JSON.parse(raw);
92
- const apiKey = typeof parsed.api_key === "string" ? parsed.api_key.trim() : "";
93
- return apiKey.length > 0 ? apiKey : null;
94
- } catch {
95
- return null;
96
- }
97
- }
98
- async function writeStoredApiKey(apiKey) {
99
- const configDir = authConfigDir();
100
- const configPath = authConfigPath();
101
- await fs2.mkdir(configDir, { recursive: true, mode: 448 });
102
- await fs2.chmod(configDir, 448).catch(() => void 0);
103
- const payload = { api_key: apiKey };
104
- await fs2.writeFile(configPath, JSON.stringify(payload, null, 2), { mode: 384 });
105
- await fs2.chmod(configPath, 384).catch(() => void 0);
106
- }
107
- async function clearStoredApiKey() {
108
- const configPath = authConfigPath();
109
- if (!await fileExists(configPath)) return false;
110
- await fs2.unlink(configPath);
111
- return true;
112
- }
113
-
114
- // src/commands/auth.ts
115
- var DEFAULT_SERVER = process.env.CLAWMARKET_API_URL || "http://localhost:8000";
116
- function resolveUsername(payload) {
117
- return payload.data?.user?.username || payload.user?.username || "user";
118
- }
119
- function resolveError(payload, fallback) {
120
- return payload.error || payload.message || fallback;
121
- }
122
- async function readJsonSafe(response) {
123
- const text = await response.text();
124
- if (!text.trim()) return {};
125
- try {
126
- return JSON.parse(text);
127
- } catch {
128
- return { message: text };
129
- }
130
- }
131
- async function readApiKeyFromStdin() {
132
- if (process.stdin.isTTY) {
133
- throw new Error("`--stdin` requires piped input");
134
- }
135
- const chunks = [];
136
- for await (const chunk of process.stdin) {
137
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
138
- }
139
- return Buffer.concat(chunks).toString("utf-8").trim();
140
- }
141
- async function resolveApiKeyInput(apiKeyArg, fromStdin) {
142
- if (apiKeyArg && apiKeyArg.trim()) {
143
- console.warn("Warning: using `<api-key>` argument stores it in shell history. Prefer prompt input or `--stdin`.");
144
- return apiKeyArg.trim();
145
- }
146
- if (fromStdin) {
147
- const fromPipe = await readApiKeyFromStdin();
148
- if (!fromPipe) throw new Error("No API key received from stdin");
149
- return fromPipe;
150
- }
151
- const entered = await askSecret("API key");
152
- if (!entered) throw new Error("API key is required");
153
- return entered;
154
- }
155
- async function validateApiKey(server, apiKey) {
156
- const response = await fetch(`${server}/auth/api-key`, {
157
- method: "POST",
158
- headers: { "Content-Type": "application/json" },
159
- body: JSON.stringify({ api_key: apiKey })
160
- });
161
- const payload = await readJsonSafe(response);
162
- return { ok: response.ok, payload };
163
- }
164
- function registerAuthCommands(program2) {
165
- const authCommand = program2.command("auth").description("Manage authentication");
166
- authCommand.command("login").description("Login with your ClawMarket API key").argument("[api-key]", "API key (less secure; shows in shell history)").option("--stdin", "Read API key from stdin (recommended for automation)").option("-s, --server <url>", "Server URL", DEFAULT_SERVER).action(async (apiKeyArg, options) => {
167
- console.log("Validating API key...");
168
- try {
169
- const apiKey = await resolveApiKeyInput(apiKeyArg, Boolean(options.stdin));
170
- const validation = await validateApiKey(options.server, apiKey);
171
- if (!validation.ok) {
172
- console.error(`Error: ${resolveError(validation.payload, "Invalid API key")}`);
173
- process.exit(1);
174
- }
175
- const username = resolveUsername(validation.payload);
176
- await writeStoredApiKey(apiKey);
177
- console.log(`
178
- Hey ${username}! You're now logged in.
179
- `);
180
- } catch (error) {
181
- const message = error instanceof Error ? error.message : "";
182
- if (message.includes("`--stdin`") || message.includes("No API key received") || message.includes("API key is required")) {
183
- console.error(message);
184
- process.exit(1);
185
- }
186
- console.error("Failed to connect to server. Are you online?");
187
- process.exit(1);
188
- }
189
- });
190
- authCommand.command("status").description("Check authentication status").option("-s, --server <url>", "Server URL", DEFAULT_SERVER).action(async (options) => {
191
- const apiKey = await readStoredApiKey();
192
- if (!apiKey) {
193
- console.log("Not logged in. Run: clawmarketbot auth login");
194
- return;
195
- }
196
- try {
197
- const validation = await validateApiKey(options.server, apiKey);
198
- if (!validation.ok) {
199
- console.log("Session expired. Run: clawmarketbot auth login");
200
- return;
201
- }
202
- const username = resolveUsername(validation.payload);
203
- console.log(`Logged in as ${username}`);
204
- } catch {
205
- console.log("Logged in (could not verify with server)");
206
- }
207
- });
208
- authCommand.command("logout").description("Logout and clear credentials").action(async () => {
209
- const removed = await clearStoredApiKey();
210
- console.log(removed ? "Logged out successfully" : "Not logged in");
211
- });
212
- }
213
-
214
- // src/commands/config.ts
215
- import fs4 from "fs/promises";
216
- import path3 from "path";
217
- import os3 from "os";
218
- import { createHash, randomUUID } from "crypto";
219
- import { spawn } from "child_process";
220
- import JSON52 from "json5";
221
- import fetch2 from "node-fetch";
222
-
223
- // ../../contracts/token.js
224
- var CLAWMARKET_DOWNLOAD_TOKEN_PATTERN = "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
225
- var CLAWMARKET_DOWNLOAD_TOKEN_REGEX = new RegExp(
226
- `^${CLAWMARKET_DOWNLOAD_TOKEN_PATTERN}$`,
227
- "i"
228
- );
229
- var CLAWMARKET_ARTIFACT_SHA256_HEADER = "x-clawmarket-sha256";
230
- function isClawMarketDownloadToken(value) {
231
- if (typeof value !== "string") return false;
232
- return CLAWMARKET_DOWNLOAD_TOKEN_REGEX.test(value.trim());
233
- }
234
-
235
94
  // src/lib/openclaw.ts
236
- import path2 from "path";
237
- import fs3 from "fs/promises";
238
- import os2 from "os";
239
- import JSON5 from "json5";
240
95
  var MAX_INCLUDE_DEPTH = 10;
241
96
  var DEBUG_LOGGING = process.env.CLAWMARKET_DEBUG === "1";
242
97
  function debug(message) {
@@ -249,14 +104,14 @@ function asObject(value) {
249
104
  }
250
105
  function resolveTilde(inputPath, homeDir) {
251
106
  if (inputPath === "~") return homeDir;
252
- if (inputPath.startsWith("~/")) return path2.join(homeDir, inputPath.slice(2));
107
+ if (inputPath.startsWith("~/")) return path.join(homeDir, inputPath.slice(2));
253
108
  return inputPath;
254
109
  }
255
110
  function resolvePathLike(value, fallback, homeDir, baseDir = process.cwd()) {
256
111
  if (typeof value !== "string" || value.trim().length === 0) return fallback;
257
112
  const expanded = resolveTilde(value, homeDir);
258
- if (path2.isAbsolute(expanded)) return path2.normalize(expanded);
259
- return path2.resolve(baseDir, expanded);
113
+ if (path.isAbsolute(expanded)) return path.normalize(expanded);
114
+ return path.resolve(baseDir, expanded);
260
115
  }
261
116
  function isPlainObject(value) {
262
117
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -274,7 +129,7 @@ function deepMerge(base, override) {
274
129
  return result;
275
130
  }
276
131
  async function parseJson5File(filePath) {
277
- const content = await fs3.readFile(filePath, "utf-8");
132
+ const content = await fs2.readFile(filePath, "utf-8");
278
133
  let parsed;
279
134
  try {
280
135
  parsed = JSON5.parse(content);
@@ -288,8 +143,8 @@ async function parseJson5File(filePath) {
288
143
  return parsed;
289
144
  }
290
145
  async function resolveIncludeTarget(includePath, includingFilePath, depth, stack) {
291
- const rawPath = resolveTilde(includePath, os2.homedir());
292
- const absPath = path2.isAbsolute(rawPath) ? path2.normalize(rawPath) : path2.resolve(path2.dirname(includingFilePath), rawPath);
146
+ const rawPath = resolveTilde(includePath, os.homedir());
147
+ const absPath = path.isAbsolute(rawPath) ? path.normalize(rawPath) : path.resolve(path.dirname(includingFilePath), rawPath);
293
148
  if (!absPath) {
294
149
  throw new Error(`Invalid include path "${includePath}" in "${includingFilePath}"`);
295
150
  }
@@ -357,10 +212,10 @@ function parseAgentName(agentConfig) {
357
212
  function parseAgents(config, paths) {
358
213
  const agentsRoot = asObject(config.agents);
359
214
  const defaults = asObject(agentsRoot?.defaults);
360
- const configDir = path2.dirname(paths.configPath);
215
+ const configDir = path.dirname(paths.configPath);
361
216
  const defaultWorkspace = resolvePathLike(
362
217
  defaults?.workspace,
363
- path2.join(paths.stateDir, "workspace"),
218
+ path.join(paths.stateDir, "workspace"),
364
219
  paths.homeDir,
365
220
  configDir
366
221
  );
@@ -370,7 +225,7 @@ function parseAgents(config, paths) {
370
225
  id: "main",
371
226
  name: "Main Agent",
372
227
  workspace: defaultWorkspace,
373
- agentDir: path2.join(paths.stateDir, "agents", "main", "agent"),
228
+ agentDir: path.join(paths.stateDir, "agents", "main", "agent"),
374
229
  isDefault: true
375
230
  };
376
231
  return { agents: [defaultAgent], defaultAgentId: defaultAgent.id };
@@ -382,7 +237,7 @@ function parseAgents(config, paths) {
382
237
  if (!id) continue;
383
238
  const workspaceFallback = defaultWorkspace;
384
239
  const workspace = resolvePathLike(item.workspace, workspaceFallback, paths.homeDir, configDir);
385
- const agentDirFallback = path2.join(paths.stateDir, "agents", id, "agent");
240
+ const agentDirFallback = path.join(paths.stateDir, "agents", id, "agent");
386
241
  const agentDir = resolvePathLike(item.agentDir, agentDirFallback, paths.homeDir, configDir);
387
242
  const name = parseAgentName(item) || id;
388
243
  const isDefault = item.default === true;
@@ -401,7 +256,7 @@ function parseAgents(config, paths) {
401
256
  }
402
257
  async function parseSkillName(skillFilePath, fallbackName) {
403
258
  try {
404
- const content = await fs3.readFile(skillFilePath, "utf-8");
259
+ const content = await fs2.readFile(skillFilePath, "utf-8");
405
260
  if (!content.startsWith("---")) return fallbackName;
406
261
  const lines = content.split(/\r?\n/).slice(1);
407
262
  for (const line of lines) {
@@ -420,12 +275,12 @@ async function parseSkillName(skillFilePath, fallbackName) {
420
275
  }
421
276
  async function discoverSkillsInDirectory(dirPath, source) {
422
277
  if (!await fileExists(dirPath)) return [];
423
- const entries = await fs3.readdir(dirPath, { withFileTypes: true }).catch(() => []);
278
+ const entries = await fs2.readdir(dirPath, { withFileTypes: true }).catch(() => []);
424
279
  const skills = [];
425
280
  for (const entry of entries) {
426
281
  if (!entry.isDirectory()) continue;
427
- const skillDirPath = path2.join(dirPath, entry.name);
428
- const skillFilePath = path2.join(skillDirPath, "SKILL.md");
282
+ const skillDirPath = path.join(dirPath, entry.name);
283
+ const skillFilePath = path.join(skillDirPath, "SKILL.md");
429
284
  if (!await fileExists(skillFilePath)) continue;
430
285
  const key = entry.name;
431
286
  const name = await parseSkillName(skillFilePath, key);
@@ -456,10 +311,10 @@ function skillPriority(source) {
456
311
  async function discoverSkillsForAgent(agent, config, paths) {
457
312
  const skillsRoot = asObject(config.skills);
458
313
  const loadConfig = asObject(skillsRoot?.load);
459
- const configDir = path2.dirname(paths.configPath);
314
+ const configDir = path.dirname(paths.configPath);
460
315
  const extraDirs = Array.isArray(loadConfig?.extraDirs) ? loadConfig.extraDirs.filter((v) => typeof v === "string") : [];
461
316
  const aggregated = [];
462
- aggregated.push(...await discoverSkillsInDirectory(path2.join(agent.workspace, "skills"), "workspace"));
317
+ aggregated.push(...await discoverSkillsInDirectory(path.join(agent.workspace, "skills"), "workspace"));
463
318
  aggregated.push(...await discoverSkillsInDirectory(paths.managedSkillsDir, "managed"));
464
319
  for (const dir of extraDirs) {
465
320
  const resolvedDir = resolvePathLike(dir, dir, paths.homeDir, configDir);
@@ -526,16 +381,16 @@ function parseCronJobs(rawCron) {
526
381
  }
527
382
  async function readCronJobs(config, paths) {
528
383
  const cronConfig = asObject(config.cron);
529
- const configDir = path2.dirname(paths.configPath);
384
+ const configDir = path.dirname(paths.configPath);
530
385
  const cronStorePath = resolvePathLike(
531
386
  cronConfig?.store,
532
- path2.join(paths.stateDir, "cron", "jobs.json"),
387
+ path.join(paths.stateDir, "cron", "jobs.json"),
533
388
  paths.homeDir,
534
389
  configDir
535
390
  );
536
391
  if (!await fileExists(cronStorePath)) return [];
537
392
  try {
538
- const raw = await fs3.readFile(cronStorePath, "utf-8");
393
+ const raw = await fs2.readFile(cronStorePath, "utf-8");
539
394
  const parsed = JSON5.parse(raw);
540
395
  return parseCronJobs(parsed);
541
396
  } catch (error) {
@@ -545,12 +400,12 @@ async function readCronJobs(config, paths) {
545
400
  }
546
401
  }
547
402
  function resolveOpenClawPaths() {
548
- const osHome = os2.homedir();
403
+ const osHome = os.homedir();
549
404
  const homeDir = resolveTilde(process.env.OPENCLAW_HOME || osHome, osHome);
550
- const stateDir = resolvePathLike(process.env.OPENCLAW_STATE_DIR, path2.join(homeDir, ".openclaw"), osHome, osHome);
405
+ const stateDir = resolvePathLike(process.env.OPENCLAW_STATE_DIR, path.join(homeDir, ".openclaw"), osHome, osHome);
551
406
  const configPath = resolvePathLike(
552
407
  process.env.OPENCLAW_CONFIG_PATH,
553
- path2.join(stateDir, "openclaw.json"),
408
+ path.join(stateDir, "openclaw.json"),
554
409
  osHome,
555
410
  osHome
556
411
  );
@@ -558,9 +413,9 @@ function resolveOpenClawPaths() {
558
413
  homeDir,
559
414
  stateDir,
560
415
  configPath,
561
- envPath: path2.join(stateDir, ".env"),
562
- managedSkillsDir: path2.join(stateDir, "skills"),
563
- cronStorePath: path2.join(stateDir, "cron", "jobs.json")
416
+ envPath: path.join(stateDir, ".env"),
417
+ managedSkillsDir: path.join(stateDir, "skills"),
418
+ cronStorePath: path.join(stateDir, "cron", "jobs.json")
564
419
  };
565
420
  }
566
421
  async function loadOpenClawContext() {
@@ -569,7 +424,7 @@ async function loadOpenClawContext() {
569
424
  throw new Error(`OpenClaw config not found at "${paths.configPath}". Is OpenClaw installed?`);
570
425
  }
571
426
  const rawConfig = await parseJson5File(paths.configPath);
572
- const resolvedConfig = await resolveIncludes(rawConfig, paths.configPath, 0, [path2.resolve(paths.configPath)]);
427
+ const resolvedConfig = await resolveIncludes(rawConfig, paths.configPath, 0, [path.resolve(paths.configPath)]);
573
428
  const { agents, defaultAgentId } = parseAgents(resolvedConfig, paths);
574
429
  const skillsByAgent = {};
575
430
  for (const agent of agents) {
@@ -597,13 +452,13 @@ function assertValidDownloadToken(token) {
597
452
  }
598
453
  }
599
454
  async function resolveSafeDownloadPath(targetPath) {
600
- const resolved = path3.resolve(targetPath);
455
+ const resolved = path2.resolve(targetPath);
601
456
  if (!await fileExists(resolved)) {
602
457
  return { outputPath: resolved, renamed: false };
603
458
  }
604
- const parsed = path3.parse(resolved);
459
+ const parsed = path2.parse(resolved);
605
460
  for (let i = 1; i <= 1e3; i += 1) {
606
- const candidate = path3.join(parsed.dir, `${parsed.name}-${i}${parsed.ext}`);
461
+ const candidate = path2.join(parsed.dir, `${parsed.name}-${i}${parsed.ext}`);
607
462
  if (!await fileExists(candidate)) {
608
463
  return { outputPath: candidate, renamed: true };
609
464
  }
@@ -631,7 +486,7 @@ function sanitizeSlugPart(value) {
631
486
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
632
487
  }
633
488
  function sanitizeFileName(value) {
634
- const base = path3.basename(value || "").replace(/[^A-Za-z0-9._-]/g, "-");
489
+ const base = path2.basename(value || "").replace(/[^A-Za-z0-9._-]/g, "-");
635
490
  return base || "download.bin";
636
491
  }
637
492
  function parseContentDispositionFileName(headerValue) {
@@ -678,7 +533,7 @@ async function readJsonError(response) {
678
533
  }
679
534
  }
680
535
  async function fetchDownloadArtifact(server, token) {
681
- const response = await fetch2(`${server}/download/${encodeURIComponent(token)}?format=file`, {
536
+ const response = await fetch(`${server}/download/${encodeURIComponent(token)}?format=file`, {
682
537
  method: "GET"
683
538
  });
684
539
  if (!response.ok) {
@@ -749,7 +604,7 @@ async function readBotInstallerConfig(configPath) {
749
604
  if (!await fileExists(configPath)) {
750
605
  throw new Error(`Package is missing required config file: ${configPath}`);
751
606
  }
752
- const raw = await fs4.readFile(configPath, "utf-8");
607
+ const raw = await fs3.readFile(configPath, "utf-8");
753
608
  try {
754
609
  return JSON.parse(raw);
755
610
  } catch (error) {
@@ -768,68 +623,68 @@ function resolveInstallPaths(botConfig, agentId, stateDir, sourceBase) {
768
623
  const tplWorkspace = botConfig.paths?.targetWorkspace ?? "workspace-{agent_id}";
769
624
  const tplAgentState = botConfig.paths?.targetAgentState ?? "agents/{agent_id}";
770
625
  const tplMemoryDb = botConfig.paths?.targetMemoryDb ?? null;
771
- const targetAgentRoot = path3.join(stateDir, applyTemplate(tplAgentState));
626
+ const targetAgentRoot = path2.join(stateDir, applyTemplate(tplAgentState));
772
627
  return {
773
- sourceWorkspace: path3.join(sourceBase, srcWorkspace),
774
- sourceAgentState: path3.join(sourceBase, srcAgentState),
775
- sourceMemoryDb: srcMemoryDb ? path3.join(sourceBase, srcMemoryDb) : null,
776
- sourceCronJobs: srcCronJobs ? path3.join(sourceBase, srcCronJobs) : null,
777
- targetWorkspace: path3.join(stateDir, applyTemplate(tplWorkspace)),
628
+ sourceWorkspace: path2.join(sourceBase, srcWorkspace),
629
+ sourceAgentState: path2.join(sourceBase, srcAgentState),
630
+ sourceMemoryDb: srcMemoryDb ? path2.join(sourceBase, srcMemoryDb) : null,
631
+ sourceCronJobs: srcCronJobs ? path2.join(sourceBase, srcCronJobs) : null,
632
+ targetWorkspace: path2.join(stateDir, applyTemplate(tplWorkspace)),
778
633
  targetAgentRoot,
779
- targetAgentDir: path3.join(targetAgentRoot, "agent"),
780
- targetMemoryDb: tplMemoryDb ? path3.join(stateDir, applyTemplate(tplMemoryDb)) : null
634
+ targetAgentDir: path2.join(targetAgentRoot, "agent"),
635
+ targetMemoryDb: tplMemoryDb ? path2.join(stateDir, applyTemplate(tplMemoryDb)) : null
781
636
  };
782
637
  }
783
638
  async function findSourceRoot(extractedDir) {
784
- const queue = [path3.resolve(extractedDir)];
639
+ const queue = [path2.resolve(extractedDir)];
785
640
  const seen = /* @__PURE__ */ new Set();
786
641
  while (queue.length > 0) {
787
642
  const current = queue.shift();
788
643
  if (!current || seen.has(current)) continue;
789
644
  seen.add(current);
790
- if (await fileExists(path3.join(current, "source", "bot-config.json"))) {
645
+ if (await fileExists(path2.join(current, "source", "bot-config.json"))) {
791
646
  return current;
792
647
  }
793
648
  let entries;
794
649
  try {
795
- entries = await fs4.readdir(current, { withFileTypes: true });
650
+ entries = await fs3.readdir(current, { withFileTypes: true });
796
651
  } catch {
797
652
  continue;
798
653
  }
799
654
  for (const entry of entries) {
800
655
  if (!entry.isDirectory() || entry.name === "__MACOSX") continue;
801
- queue.push(path3.join(current, entry.name));
656
+ queue.push(path2.join(current, entry.name));
802
657
  }
803
658
  }
804
659
  return null;
805
660
  }
806
661
  async function copyDirRecursive(src, dest) {
807
- await fs4.mkdir(dest, { recursive: true });
808
- const entries = await fs4.readdir(src, { withFileTypes: true });
662
+ await fs3.mkdir(dest, { recursive: true });
663
+ const entries = await fs3.readdir(src, { withFileTypes: true });
809
664
  for (const entry of entries) {
810
- const srcPath = path3.join(src, entry.name);
811
- const destPath = path3.join(dest, entry.name);
665
+ const srcPath = path2.join(src, entry.name);
666
+ const destPath = path2.join(dest, entry.name);
812
667
  if (entry.isDirectory()) {
813
668
  await copyDirRecursive(srcPath, destPath);
814
669
  } else {
815
- await fs4.copyFile(srcPath, destPath);
670
+ await fs3.copyFile(srcPath, destPath);
816
671
  }
817
672
  }
818
673
  }
819
674
  async function stripMacOsJunk(dir) {
820
- const macosDir = path3.join(dir, "__MACOSX");
821
- await fs4.rm(macosDir, { recursive: true, force: true }).catch(() => void 0);
675
+ const macosDir = path2.join(dir, "__MACOSX");
676
+ await fs3.rm(macosDir, { recursive: true, force: true }).catch(() => void 0);
822
677
  async function removeDsStore(d) {
823
678
  let entries;
824
679
  try {
825
- entries = await fs4.readdir(d, { withFileTypes: true });
680
+ entries = await fs3.readdir(d, { withFileTypes: true });
826
681
  } catch {
827
682
  return;
828
683
  }
829
684
  for (const entry of entries) {
830
- const full = path3.join(d, entry.name);
685
+ const full = path2.join(d, entry.name);
831
686
  if (entry.isFile() && entry.name === ".DS_Store") {
832
- await fs4.rm(full, { force: true }).catch(() => void 0);
687
+ await fs3.rm(full, { force: true }).catch(() => void 0);
833
688
  } else if (entry.isDirectory()) {
834
689
  await removeDsStore(full);
835
690
  }
@@ -838,7 +693,7 @@ async function stripMacOsJunk(dir) {
838
693
  await removeDsStore(dir);
839
694
  }
840
695
  async function buildOpenClawConfigPatch(configPath, agentId, agentName, botConfig, installPaths) {
841
- const raw = await fs4.readFile(configPath, "utf-8");
696
+ const raw = await fs3.readFile(configPath, "utf-8");
842
697
  const config = JSON52.parse(raw);
843
698
  const agentEntry = {
844
699
  id: agentId,
@@ -875,7 +730,7 @@ async function buildOpenClawConfigPatch(configPath, agentId, agentName, botConfi
875
730
  }
876
731
  async function prepareCronJobsFromBundle(sourceCronPath, cronStorePath, agentId) {
877
732
  if (!sourceCronPath || !await fileExists(sourceCronPath)) return null;
878
- const raw = await fs4.readFile(sourceCronPath, "utf-8");
733
+ const raw = await fs3.readFile(sourceCronPath, "utf-8");
879
734
  const bundleJobs = normalizeCronJobs(JSON.parse(raw));
880
735
  if (bundleJobs.length === 0) return null;
881
736
  const shouldInstall = await resolveCronChoice(true);
@@ -891,7 +746,7 @@ async function prepareCronJobsFromBundle(sourceCronPath, cronStorePath, agentId)
891
746
  let existingJobs = [];
892
747
  if (await fileExists(cronStorePath)) {
893
748
  existed = true;
894
- backupContent = await fs4.readFile(cronStorePath, "utf-8");
749
+ backupContent = await fs3.readFile(cronStorePath, "utf-8");
895
750
  existingJobs = normalizeCronJobs(JSON52.parse(backupContent));
896
751
  }
897
752
  const existingKeys = new Set(
@@ -924,7 +779,7 @@ async function setupApiKeysFromConfig(botConfig, configPath, envPath) {
924
779
  console.log("");
925
780
  const key = await askOptional(brave.prompt ?? "Brave Search API key", brave.url);
926
781
  if (key) {
927
- const raw = await fs4.readFile(configPath, "utf-8");
782
+ const raw = await fs3.readFile(configPath, "utf-8");
928
783
  const config = JSON52.parse(raw);
929
784
  const tools = config.tools ?? {};
930
785
  const web = tools.web ?? {};
@@ -933,7 +788,7 @@ async function setupApiKeysFromConfig(botConfig, configPath, envPath) {
933
788
  web.search = search;
934
789
  tools.web = web;
935
790
  config.tools = tools;
936
- await fs4.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
791
+ await fs3.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
937
792
  console.log(" Brave Search key saved to openclaw.json");
938
793
  } else {
939
794
  console.log(" Skipped Brave Search key.");
@@ -941,10 +796,10 @@ async function setupApiKeysFromConfig(botConfig, configPath, envPath) {
941
796
  }
942
797
  const envKeys = botConfig.apiKeys.env ?? [];
943
798
  if (envKeys.length > 0) {
944
- await fs4.mkdir(path3.dirname(envPath), { recursive: true });
799
+ await fs3.mkdir(path2.dirname(envPath), { recursive: true });
945
800
  let envContent = "";
946
801
  try {
947
- envContent = await fs4.readFile(envPath, "utf-8");
802
+ envContent = await fs3.readFile(envPath, "utf-8");
948
803
  } catch {
949
804
  }
950
805
  let keysWritten = 0;
@@ -963,7 +818,7 @@ ${keyDef.key}=${val}`;
963
818
  }
964
819
  }
965
820
  if (keysWritten > 0) {
966
- await fs4.writeFile(envPath, envContent.trim() + "\n", "utf-8");
821
+ await fs3.writeFile(envPath, envContent.trim() + "\n", "utf-8");
967
822
  console.log(`
968
823
  ${keysWritten} key(s) saved to .env`);
969
824
  }
@@ -975,13 +830,13 @@ async function validateInstalledWorkspace(workspacePath, botConfig) {
975
830
  if (requiredFiles.length === 0 && requiredDirs.length === 0) return;
976
831
  const warnings = [];
977
832
  for (const file of requiredFiles) {
978
- if (!await fileExists(path3.join(workspacePath, file))) {
833
+ if (!await fileExists(path2.join(workspacePath, file))) {
979
834
  warnings.push(`Missing expected file: ${file}`);
980
835
  }
981
836
  }
982
837
  for (const dir of requiredDirs) {
983
838
  try {
984
- const stat = await fs4.stat(path3.join(workspacePath, dir));
839
+ const stat = await fs3.stat(path2.join(workspacePath, dir));
985
840
  if (!stat.isDirectory()) warnings.push(`Expected directory but found file: ${dir}`);
986
841
  } catch {
987
842
  warnings.push(`Missing expected directory: ${dir}`);
@@ -1016,39 +871,39 @@ async function commitInstallTransaction(input2) {
1016
871
  let movedAgentDir = false;
1017
872
  let copiedMemoryDb = false;
1018
873
  try {
1019
- await fs4.mkdir(path3.dirname(input2.workspacePath), { recursive: true });
1020
- await fs4.rename(input2.stagedWorkspacePath, input2.workspacePath);
874
+ await fs3.mkdir(path2.dirname(input2.workspacePath), { recursive: true });
875
+ await fs3.rename(input2.stagedWorkspacePath, input2.workspacePath);
1021
876
  movedWorkspace = true;
1022
- await fs4.mkdir(path3.dirname(input2.agentDirPath), { recursive: true });
1023
- await fs4.rename(input2.stagedAgentDirPath, input2.agentDirPath);
877
+ await fs3.mkdir(path2.dirname(input2.agentDirPath), { recursive: true });
878
+ await fs3.rename(input2.stagedAgentDirPath, input2.agentDirPath);
1024
879
  movedAgentDir = true;
1025
880
  if (input2.memoryDbStagedPath && input2.memoryDbTargetPath && await fileExists(input2.memoryDbStagedPath)) {
1026
- await fs4.mkdir(path3.dirname(input2.memoryDbTargetPath), { recursive: true });
1027
- await fs4.copyFile(input2.memoryDbStagedPath, input2.memoryDbTargetPath);
881
+ await fs3.mkdir(path2.dirname(input2.memoryDbTargetPath), { recursive: true });
882
+ await fs3.copyFile(input2.memoryDbStagedPath, input2.memoryDbTargetPath);
1028
883
  copiedMemoryDb = true;
1029
884
  }
1030
885
  if (input2.cronContent !== null) {
1031
- await fs4.mkdir(path3.dirname(input2.cronStorePath), { recursive: true });
1032
- await fs4.writeFile(input2.cronStorePath, input2.cronContent, "utf-8");
886
+ await fs3.mkdir(path2.dirname(input2.cronStorePath), { recursive: true });
887
+ await fs3.writeFile(input2.cronStorePath, input2.cronContent, "utf-8");
1033
888
  }
1034
- await fs4.writeFile(input2.configPath, input2.configContent, "utf-8");
889
+ await fs3.writeFile(input2.configPath, input2.configContent, "utf-8");
1035
890
  } catch (error) {
1036
891
  if (input2.cronContent !== null) {
1037
892
  if (input2.cronExisted && input2.cronBackupContent !== null) {
1038
- await fs4.writeFile(input2.cronStorePath, input2.cronBackupContent, "utf-8").catch(() => void 0);
893
+ await fs3.writeFile(input2.cronStorePath, input2.cronBackupContent, "utf-8").catch(() => void 0);
1039
894
  } else {
1040
- await fs4.rm(input2.cronStorePath, { force: true }).catch(() => void 0);
895
+ await fs3.rm(input2.cronStorePath, { force: true }).catch(() => void 0);
1041
896
  }
1042
897
  }
1043
898
  if (copiedMemoryDb && input2.memoryDbTargetPath) {
1044
- await fs4.rm(input2.memoryDbTargetPath, { force: true }).catch(() => void 0);
899
+ await fs3.rm(input2.memoryDbTargetPath, { force: true }).catch(() => void 0);
1045
900
  }
1046
- await fs4.writeFile(input2.configPath, input2.configBackupContent, "utf-8").catch(() => void 0);
901
+ await fs3.writeFile(input2.configPath, input2.configBackupContent, "utf-8").catch(() => void 0);
1047
902
  if (movedWorkspace) {
1048
- await fs4.rm(input2.workspacePath, { recursive: true, force: true }).catch(() => void 0);
903
+ await fs3.rm(input2.workspacePath, { recursive: true, force: true }).catch(() => void 0);
1049
904
  }
1050
905
  if (movedAgentDir) {
1051
- await fs4.rm(input2.agentRootPath, { recursive: true, force: true }).catch(() => void 0);
906
+ await fs3.rm(input2.agentRootPath, { recursive: true, force: true }).catch(() => void 0);
1052
907
  }
1053
908
  const message = error instanceof Error ? error.message : "unknown failure";
1054
909
  throw new Error(`Install failed and was rolled back: ${message}`);
@@ -1060,7 +915,7 @@ function registerConfigCommands(program2) {
1060
915
  try {
1061
916
  assertValidDownloadToken(token);
1062
917
  const server = getDefaultServer();
1063
- const response = await fetch2(`${server}/download/${encodeURIComponent(token)}?format=file`, {
918
+ const response = await fetch(`${server}/download/${encodeURIComponent(token)}?format=file`, {
1064
919
  method: "GET"
1065
920
  });
1066
921
  if (!response.ok) {
@@ -1068,12 +923,12 @@ function registerConfigCommands(program2) {
1068
923
  }
1069
924
  const fileFromHeader = parseContentDispositionFileName(response.headers.get("content-disposition"));
1070
925
  const fallbackFile = `clawmarket-${token}.bin`;
1071
- const preferredPath = path3.resolve(fileFromHeader || fallbackFile);
926
+ const preferredPath = path2.resolve(fileFromHeader || fallbackFile);
1072
927
  const { outputPath, renamed } = await resolveSafeDownloadPath(preferredPath);
1073
928
  const bytes = Buffer.from(await response.arrayBuffer());
1074
929
  verifyDownloadedArtifactChecksum(bytes, response.headers.get(CLAWMARKET_ARTIFACT_SHA256_HEADER));
1075
- await fs4.mkdir(path3.dirname(outputPath), { recursive: true });
1076
- await fs4.writeFile(outputPath, bytes);
930
+ await fs3.mkdir(path2.dirname(outputPath), { recursive: true });
931
+ await fs3.writeFile(outputPath, bytes);
1077
932
  if (renamed) {
1078
933
  console.log(`Existing file detected, saved as: ${outputPath}`);
1079
934
  }
@@ -1090,14 +945,14 @@ function registerConfigCommands(program2) {
1090
945
  const server = getDefaultServer();
1091
946
  const context = await loadOpenClawContext();
1092
947
  const artifact = await fetchDownloadArtifact(server, token);
1093
- const tempRoot = await fs4.mkdtemp(
1094
- path3.join(os3.tmpdir(), `.clawmarket-install-${randomUUID().slice(0, 8)}-`)
948
+ const tempRoot = await fs3.mkdtemp(
949
+ path2.join(os2.tmpdir(), `.clawmarket-install-${randomUUID().slice(0, 8)}-`)
1095
950
  );
1096
951
  try {
1097
- const artifactPath = path3.join(tempRoot, sanitizeFileName(artifact.fileName));
1098
- const extractedPath = path3.join(tempRoot, "extracted");
1099
- await fs4.mkdir(extractedPath, { recursive: true });
1100
- await fs4.writeFile(artifactPath, artifact.bytes);
952
+ const artifactPath = path2.join(tempRoot, sanitizeFileName(artifact.fileName));
953
+ const extractedPath = path2.join(tempRoot, "extracted");
954
+ await fs3.mkdir(extractedPath, { recursive: true });
955
+ await fs3.writeFile(artifactPath, artifact.bytes);
1101
956
  await extractZipArtifact(artifactPath, extractedPath);
1102
957
  await stripMacOsJunk(extractedPath);
1103
958
  const sourceRoot = await findSourceRoot(extractedPath);
@@ -1106,8 +961,8 @@ function registerConfigCommands(program2) {
1106
961
  "Downloaded artifact is not an installable bot package (missing source/bot-config.json)."
1107
962
  );
1108
963
  }
1109
- const sourceBase = path3.join(sourceRoot, "source");
1110
- const botConfig = await readBotInstallerConfig(path3.join(sourceBase, "bot-config.json"));
964
+ const sourceBase = path2.join(sourceRoot, "source");
965
+ const botConfig = await readBotInstallerConfig(path2.join(sourceBase, "bot-config.json"));
1111
966
  if (typeof botConfig.installerSpecVersion === "number" && botConfig.installerSpecVersion !== 1) {
1112
967
  console.warn(
1113
968
  `Warning: installerSpecVersion=${botConfig.installerSpecVersion} (this CLI currently expects version 1).`
@@ -1122,8 +977,8 @@ function registerConfigCommands(program2) {
1122
977
  throw new Error(`Agent id "${agentId}" already exists. Choose another id.`);
1123
978
  }
1124
979
  const installPaths = resolveInstallPaths(botConfig, agentId, context.paths.stateDir, sourceBase);
1125
- const stagedWorkspacePath = path3.join(tempRoot, "staged-workspace");
1126
- const stagedAgentDirPath = path3.join(tempRoot, "staged-agent");
980
+ const stagedWorkspacePath = path2.join(tempRoot, "staged-workspace");
981
+ const stagedAgentDirPath = path2.join(tempRoot, "staged-agent");
1127
982
  await copyDirRecursive(installPaths.sourceWorkspace, stagedWorkspacePath);
1128
983
  await copyDirRecursive(installPaths.sourceAgentState, stagedAgentDirPath);
1129
984
  const { configContent, configBackupContent } = await buildOpenClawConfigPatch(
@@ -1156,7 +1011,7 @@ function registerConfigCommands(program2) {
1156
1011
  memoryDbTargetPath: installPaths.targetMemoryDb
1157
1012
  });
1158
1013
  await setupApiKeysFromConfig(botConfig, context.paths.configPath, context.paths.envPath);
1159
- const installerShPath = path3.join(sourceBase, "installer.sh");
1014
+ const installerShPath = path2.join(sourceBase, "installer.sh");
1160
1015
  if (await fileExists(installerShPath)) {
1161
1016
  console.log("\nRunning post-install hook (installer.sh)...");
1162
1017
  await runCommand("bash", [installerShPath, context.paths.stateDir], {
@@ -1187,7 +1042,7 @@ function registerConfigCommands(program2) {
1187
1042
  console.log(" 1) openclaw agents list");
1188
1043
  console.log(" 2) openclaw gateway restart");
1189
1044
  } finally {
1190
- await fs4.rm(tempRoot, { recursive: true, force: true }).catch(() => void 0);
1045
+ await fs3.rm(tempRoot, { recursive: true, force: true }).catch(() => void 0);
1191
1046
  }
1192
1047
  } catch (error) {
1193
1048
  const message = error instanceof Error ? error.message : "Install failed";
@@ -1199,7 +1054,6 @@ function registerConfigCommands(program2) {
1199
1054
 
1200
1055
  // src/index.ts
1201
1056
  var program = new Command();
1202
- program.name("clawmarketbot").description("CLI tool for ClawMarket - discover, download, and install OpenClaw configs").version("0.1.0");
1203
- registerAuthCommands(program);
1057
+ program.name("clawmarketbot").description("CLI tool for ClawMarket - discover, download, and install OpenClaw configs").version("0.1.4");
1204
1058
  registerConfigCommands(program);
1205
1059
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmarketbot",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "description": "CLI tool for ClawMarket - discover, download, and install OpenClaw configs",
6
6
  "type": "module",