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.
- package/README.md +58 -28
- package/dist/index.js +117 -263
- 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
|
|
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`
|
|
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
|
|
19
|
+
### Install an agent from a download token
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
clawmarketbot config install
|
|
22
|
+
clawmarketbot config install <token>
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Tokens are obtained from the ClawMarket download modal on the config detail page.
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
clawmarketbot config install-npm @clawmarket/my-bot@1.2.0
|
|
29
|
-
```
|
|
27
|
+
The command will:
|
|
30
28
|
|
|
31
|
-
|
|
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
|
|
45
|
+
clawmarketbot config download <token>
|
|
35
46
|
```
|
|
36
47
|
|
|
37
|
-
|
|
48
|
+
Saves the raw zip file to the current directory.
|
|
38
49
|
|
|
39
|
-
###
|
|
50
|
+
### Authentication
|
|
40
51
|
|
|
41
52
|
```bash
|
|
42
|
-
clawmarketbot
|
|
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
|
-
|
|
58
|
+
You can also pass the API key via stdin (recommended for automation):
|
|
46
59
|
|
|
47
60
|
```bash
|
|
48
|
-
clawmarketbot auth login
|
|
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` |
|
|
58
|
-
| `OPENCLAW_HOME` | `os.homedir()` |
|
|
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
|
-
|
|
73
|
+
Example:
|
|
63
74
|
|
|
64
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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/
|
|
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/
|
|
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
|
|
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 (
|
|
259
|
-
return
|
|
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
|
|
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,
|
|
292
|
-
const absPath =
|
|
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 =
|
|
215
|
+
const configDir = path.dirname(paths.configPath);
|
|
361
216
|
const defaultWorkspace = resolvePathLike(
|
|
362
217
|
defaults?.workspace,
|
|
363
|
-
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
428
|
-
const skillFilePath =
|
|
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 =
|
|
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(
|
|
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 =
|
|
384
|
+
const configDir = path.dirname(paths.configPath);
|
|
530
385
|
const cronStorePath = resolvePathLike(
|
|
531
386
|
cronConfig?.store,
|
|
532
|
-
|
|
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
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
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:
|
|
562
|
-
managedSkillsDir:
|
|
563
|
-
cronStorePath:
|
|
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, [
|
|
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 =
|
|
455
|
+
const resolved = path2.resolve(targetPath);
|
|
601
456
|
if (!await fileExists(resolved)) {
|
|
602
457
|
return { outputPath: resolved, renamed: false };
|
|
603
458
|
}
|
|
604
|
-
const parsed =
|
|
459
|
+
const parsed = path2.parse(resolved);
|
|
605
460
|
for (let i = 1; i <= 1e3; i += 1) {
|
|
606
|
-
const candidate =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
626
|
+
const targetAgentRoot = path2.join(stateDir, applyTemplate(tplAgentState));
|
|
772
627
|
return {
|
|
773
|
-
sourceWorkspace:
|
|
774
|
-
sourceAgentState:
|
|
775
|
-
sourceMemoryDb: srcMemoryDb ?
|
|
776
|
-
sourceCronJobs: srcCronJobs ?
|
|
777
|
-
targetWorkspace:
|
|
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:
|
|
780
|
-
targetMemoryDb: tplMemoryDb ?
|
|
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 = [
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
808
|
-
const entries = await
|
|
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 =
|
|
811
|
-
const destPath =
|
|
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
|
|
670
|
+
await fs3.copyFile(srcPath, destPath);
|
|
816
671
|
}
|
|
817
672
|
}
|
|
818
673
|
}
|
|
819
674
|
async function stripMacOsJunk(dir) {
|
|
820
|
-
const macosDir =
|
|
821
|
-
await
|
|
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
|
|
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 =
|
|
685
|
+
const full = path2.join(d, entry.name);
|
|
831
686
|
if (entry.isFile() && entry.name === ".DS_Store") {
|
|
832
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
799
|
+
await fs3.mkdir(path2.dirname(envPath), { recursive: true });
|
|
945
800
|
let envContent = "";
|
|
946
801
|
try {
|
|
947
|
-
envContent = await
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
1020
|
-
await
|
|
874
|
+
await fs3.mkdir(path2.dirname(input2.workspacePath), { recursive: true });
|
|
875
|
+
await fs3.rename(input2.stagedWorkspacePath, input2.workspacePath);
|
|
1021
876
|
movedWorkspace = true;
|
|
1022
|
-
await
|
|
1023
|
-
await
|
|
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
|
|
1027
|
-
await
|
|
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
|
|
1032
|
-
await
|
|
886
|
+
await fs3.mkdir(path2.dirname(input2.cronStorePath), { recursive: true });
|
|
887
|
+
await fs3.writeFile(input2.cronStorePath, input2.cronContent, "utf-8");
|
|
1033
888
|
}
|
|
1034
|
-
await
|
|
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
|
|
893
|
+
await fs3.writeFile(input2.cronStorePath, input2.cronBackupContent, "utf-8").catch(() => void 0);
|
|
1039
894
|
} else {
|
|
1040
|
-
await
|
|
895
|
+
await fs3.rm(input2.cronStorePath, { force: true }).catch(() => void 0);
|
|
1041
896
|
}
|
|
1042
897
|
}
|
|
1043
898
|
if (copiedMemoryDb && input2.memoryDbTargetPath) {
|
|
1044
|
-
await
|
|
899
|
+
await fs3.rm(input2.memoryDbTargetPath, { force: true }).catch(() => void 0);
|
|
1045
900
|
}
|
|
1046
|
-
await
|
|
901
|
+
await fs3.writeFile(input2.configPath, input2.configBackupContent, "utf-8").catch(() => void 0);
|
|
1047
902
|
if (movedWorkspace) {
|
|
1048
|
-
await
|
|
903
|
+
await fs3.rm(input2.workspacePath, { recursive: true, force: true }).catch(() => void 0);
|
|
1049
904
|
}
|
|
1050
905
|
if (movedAgentDir) {
|
|
1051
|
-
await
|
|
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
|
|
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 =
|
|
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
|
|
1076
|
-
await
|
|
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
|
|
1094
|
-
|
|
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 =
|
|
1098
|
-
const extractedPath =
|
|
1099
|
-
await
|
|
1100
|
-
await
|
|
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 =
|
|
1110
|
-
const botConfig = await readBotInstallerConfig(
|
|
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 =
|
|
1126
|
-
const stagedAgentDirPath =
|
|
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 =
|
|
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
|
|
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.
|
|
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();
|