@linkclaw/clawpool 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +21 -23
  2. package/clawpool.ts +134 -77
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  CLI tool to manage OpenClaw container instances on a local Docker host (Mac Mini + Colima).
4
4
 
5
- Each instance runs in its own Docker container with a dedicated Telegram bot token, persistent volume, and auto-assigned port.
5
+ Each instance gets its own Web Dashboard with an auto-generated auth token no Telegram or external accounts required.
6
6
 
7
7
  ## Prerequisites
8
8
 
@@ -19,8 +19,8 @@ brew services start colima
19
19
  ## Install
20
20
 
21
21
  ```bash
22
- # Via npx (no install needed)
23
- npx @linkclaw/clawpool list
22
+ # Via bunx (no install needed)
23
+ bunx @linkclaw/clawpool list
24
24
 
25
25
  # Or install globally
26
26
  npm i -g @linkclaw/clawpool
@@ -30,14 +30,14 @@ clawpool list
30
30
  ## Usage
31
31
 
32
32
  ```
33
- clawpool create <name> -t <telegram_bot_token> [-p <port>] [-e KEY=VAL ...]
33
+ clawpool create <name> [--telegram-token <token>] [-p <port>] [-e KEY=VAL ...]
34
34
  clawpool list [--json]
35
35
  clawpool start <name>
36
36
  clawpool stop <name>
37
37
  clawpool restart <name>
38
38
  clawpool delete <name> [--purge]
39
39
  clawpool rename <old_name> <new_name>
40
- clawpool config <name> [-t <new_token>] [-e KEY=VAL ...]
40
+ clawpool config <name> [--telegram-token <token>] [-e KEY=VAL ...]
41
41
  clawpool logs <name> [-f]
42
42
  clawpool status <name>
43
43
  clawpool shell <name>
@@ -47,35 +47,33 @@ clawpool image [set <image> | show]
47
47
  ## Quick Start
48
48
 
49
49
  ```bash
50
- # 1. Build or pull the OpenClaw image
51
- docker build -t openclaw:latest /path/to/apps/agent
50
+ # 1. Create an instance dashboard URL is printed automatically
51
+ clawpool create alpha
52
52
 
53
- # 2. Create an instance with a Telegram bot token
54
- clawpool create alpha -t "123456:AAHxxx..."
53
+ # 2. Add another with extra env vars
54
+ clawpool create beta -e "OPENAI_API_KEY=sk-..."
55
55
 
56
- # 3. Add another instance with extra env vars
57
- clawpool create beta -t "789012:BBXyyy..." -e "OPENAI_API_KEY=sk-..."
56
+ # 3. Optionally attach Telegram
57
+ clawpool create gamma --telegram-token "123456:AAH..."
58
58
 
59
- # 4. Check running instances
59
+ # 4. List all instances with dashboard URLs
60
60
  clawpool list
61
61
 
62
62
  # 5. View logs
63
63
  clawpool logs alpha -f
64
64
 
65
- # 6. Reconfigure
66
- clawpool config alpha -t "new_token_here"
67
-
68
- # 7. Clean up
65
+ # 6. Clean up
69
66
  clawpool delete beta --purge
70
67
  ```
71
68
 
72
69
  ## How It Works
73
70
 
74
- - **Config**: `~/.clawpool/config.json` stores instance metadata (name, port, token, env vars).
71
+ - **Dashboard**: Each instance exposes OpenClaw's built-in Web UI. Auth token is auto-generated and shown in `clawpool list`.
72
+ - **Config**: `~/.clawpool/config.json` stores instance metadata.
75
73
  - **Containers**: Each instance runs as `claw-<name>` with `--restart unless-stopped`.
76
74
  - **Volumes**: Data persisted in Docker named volumes (`claw-<name>-data` mounted at `/home/claw`).
77
- - **Ports**: Auto-assigned starting from 8081, or manually specified with `-p`.
78
- - **Telegram**: Each instance connects via long-polling (no public IP needed).
75
+ - **Ports**: Auto-assigned starting from 18789 with 20-port spacing (OpenClaw requirement), or manually specified with `-p`.
76
+ - **Telegram**: Optional pass `--telegram-token` to enable Telegram bot integration via long-polling.
79
77
 
80
78
  ## Config File
81
79
 
@@ -83,13 +81,13 @@ Stored at `~/.clawpool/config.json`:
83
81
 
84
82
  ```jsonc
85
83
  {
86
- "image": "openclaw:latest", // default image for new instances
87
- "next_port": 8083, // next auto-assigned port
84
+ "image": "openclaw:latest",
85
+ "next_port": 18829,
88
86
  "instances": {
89
87
  "alpha": {
90
88
  "name": "alpha",
91
- "port": 8081,
92
- "telegram_bot_token": "123456:AAH...",
89
+ "port": 18789,
90
+ "gateway_token": "abc123...",
93
91
  "status": "running",
94
92
  "container_id": "a1b2c3...",
95
93
  "volume": "claw-alpha-data",
package/clawpool.ts CHANGED
@@ -1,19 +1,23 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import { existsSync, mkdirSync } from "fs";
4
- import { homedir } from "os";
4
+ import { homedir, networkInterfaces } from "os";
5
5
  import { join } from "path";
6
+ import { randomBytes } from "crypto";
6
7
 
7
8
  // ── Config ──────────────────────────────────────────────────────────────────
8
9
 
9
10
  const CLAWPOOL_DIR = process.env.CLAWPOOL_DIR ?? join(homedir(), ".clawpool");
10
11
  const CONFIG_FILE = join(CLAWPOOL_DIR, "config.json");
11
12
  const CONTAINER_PREFIX = "claw";
13
+ const BASE_PORT = 18789;
14
+ const PORT_STEP = 20; // OpenClaw requires at least 20-port spacing
12
15
 
13
16
  interface Instance {
14
17
  name: string;
15
18
  port: number;
16
- telegram_bot_token: string;
19
+ gateway_token: string;
20
+ telegram_bot_token?: string;
17
21
  status: "running" | "stopped" | "removed";
18
22
  container_id: string;
19
23
  volume: string;
@@ -35,6 +39,7 @@ const c = {
35
39
  yellow: (s: string) => `\x1b[33m${s}\x1b[0m`,
36
40
  cyan: (s: string) => `\x1b[36m${s}\x1b[0m`,
37
41
  bold: (s: string) => `\x1b[1m${s}\x1b[0m`,
42
+ dim: (s: string) => `\x1b[2m${s}\x1b[0m`,
38
43
  };
39
44
 
40
45
  const die = (msg: string): never => {
@@ -46,6 +51,21 @@ const ok = (msg: string) => console.log(`${c.green("✓")} ${msg}`);
46
51
 
47
52
  const containerName = (name: string) => `${CONTAINER_PREFIX}-${name}`;
48
53
  const volumeName = (name: string) => `${CONTAINER_PREFIX}-${name}-data`;
54
+ const generateToken = () => randomBytes(24).toString("base64url");
55
+
56
+ function getLanIP(): string {
57
+ const nets = networkInterfaces();
58
+ for (const addrs of Object.values(nets)) {
59
+ for (const addr of addrs ?? []) {
60
+ if (addr.family === "IPv4" && !addr.internal) return addr.address;
61
+ }
62
+ }
63
+ return "localhost";
64
+ }
65
+
66
+ function dashboardURL(host: string, port: number, token: string): string {
67
+ return `http://${host}:${port}?token=${token}`;
68
+ }
49
69
 
50
70
  function humanTime(iso: string): string {
51
71
  const diff = Math.floor((Date.now() - new Date(iso).getTime()) / 1000);
@@ -62,7 +82,7 @@ async function loadConfig(): Promise<Config> {
62
82
  if (!existsSync(CONFIG_FILE)) {
63
83
  const initial: Config = {
64
84
  image: "openclaw:latest",
65
- next_port: 8081,
85
+ next_port: BASE_PORT,
66
86
  instances: {},
67
87
  };
68
88
  await Bun.write(CONFIG_FILE, JSON.stringify(initial, null, 2));
@@ -91,7 +111,8 @@ async function docker(...args: string[]): Promise<string> {
91
111
  const stdout = await new Response(proc.stdout).text();
92
112
  const stderr = await new Response(proc.stderr).text();
93
113
  const code = await proc.exited;
94
- if (code !== 0) throw new Error(stderr.trim() || `docker ${args[0]} failed`);
114
+ if (code !== 0)
115
+ throw new Error(stderr.trim() || `docker ${args[0]} failed`);
95
116
  return stdout.trim();
96
117
  }
97
118
 
@@ -123,26 +144,67 @@ async function syncStatus(
123
144
  "{{.State.Status}}",
124
145
  containerName(name)
125
146
  );
126
- const status =
127
- state === "running" ? "running" : (("stopped" as const) satisfies string);
128
- config.instances[name].status = status as "running" | "stopped";
129
- return status as "running" | "stopped";
147
+ const status: "running" | "stopped" =
148
+ state === "running" ? "running" : "stopped";
149
+ config.instances[name].status = status;
150
+ return status;
130
151
  } catch {
131
152
  config.instances[name].status = "removed";
132
153
  return "removed";
133
154
  }
134
155
  }
135
156
 
136
- function buildEnvArgs(inst: Instance): string[] {
157
+ function openclawConfig(token: string): string {
158
+ return JSON.stringify({
159
+ gateway: {
160
+ auth: { mode: "token", token },
161
+ controlUi: { dangerouslyAllowHostHeaderOriginFallback: true },
162
+ },
163
+ });
164
+ }
165
+
166
+ function buildRunArgs(config: Config, inst: Instance): string[] {
167
+ const ocConfig = openclawConfig(inst.gateway_token);
168
+ // Init as root to fix volume permissions, write config, then drop to node user
169
+ const entrypoint = [
170
+ "sh",
171
+ "-c",
172
+ [
173
+ "mkdir -p /home/node/data/.openclaw",
174
+ "chown -R node:node /home/node/data",
175
+ `echo '${ocConfig}' > /home/node/data/.openclaw/openclaw.json`,
176
+ `exec su -s /bin/sh node -c 'exec node dist/index.js gateway --allow-unconfigured --port ${inst.port} --bind lan'`,
177
+ ].join(" && "),
178
+ ];
179
+
137
180
  const args = [
181
+ "run",
182
+ "-d",
183
+ "--name",
184
+ containerName(inst.name),
185
+ "--restart",
186
+ "unless-stopped",
187
+ "--user",
188
+ "root",
189
+ "-p",
190
+ `${inst.port}:${inst.port}`,
191
+ "-v",
192
+ `${inst.volume}:/home/node/data`,
138
193
  "-e",
139
- `TELEGRAM_BOT_TOKEN=${inst.telegram_bot_token}`,
194
+ `OPENCLAW_HOME=/home/node/data`,
140
195
  "-e",
141
196
  `INSTANCE_NAME=${inst.name}`,
142
197
  ];
198
+
199
+ if (inst.telegram_bot_token) {
200
+ args.push("-e", `TELEGRAM_BOT_TOKEN=${inst.telegram_bot_token}`);
201
+ }
202
+
143
203
  for (const [k, v] of Object.entries(inst.env)) {
144
204
  args.push("-e", `${k}=${v}`);
145
205
  }
206
+
207
+ args.push(config.image, ...entrypoint);
146
208
  return args;
147
209
  }
148
210
 
@@ -150,28 +212,9 @@ async function rebuildContainer(
150
212
  config: Config,
151
213
  name: string
152
214
  ): Promise<string> {
153
- const inst = config.instances[name];
154
215
  await dockerQuiet("rm", "-f", containerName(name));
155
-
156
- const cid = await docker(
157
- "run",
158
- "-d",
159
- "--name",
160
- containerName(name),
161
- "--restart",
162
- "unless-stopped",
163
- "-p",
164
- `${inst.port}:8080`,
165
- "-v",
166
- `${inst.volume}:/home/claw`,
167
- ...buildEnvArgs(inst),
168
- config.image,
169
- "node",
170
- "dist/index.js",
171
- "gateway",
172
- "--allow-unconfigured"
173
- );
174
-
216
+ const inst = config.instances[name];
217
+ const cid = await docker(...buildRunArgs(config, inst));
175
218
  inst.container_id = cid;
176
219
  inst.status = "running";
177
220
  return cid;
@@ -181,20 +224,19 @@ async function rebuildContainer(
181
224
 
182
225
  async function cmdCreate(config: Config, args: string[]) {
183
226
  let name = "";
184
- let token = "";
185
227
  let port = 0;
228
+ let telegramToken = "";
186
229
  const env: Record<string, string> = {};
187
230
 
188
231
  for (let i = 0; i < args.length; i++) {
189
232
  switch (args[i]) {
190
- case "-t":
191
- case "--token":
192
- token = args[++i];
193
- break;
194
233
  case "-p":
195
234
  case "--port":
196
235
  port = parseInt(args[++i]);
197
236
  break;
237
+ case "--telegram-token":
238
+ telegramToken = args[++i];
239
+ break;
198
240
  case "-e":
199
241
  case "--env": {
200
242
  const kv = args[++i];
@@ -210,12 +252,13 @@ async function cmdCreate(config: Config, args: string[]) {
210
252
  }
211
253
  }
212
254
 
213
- if (!name || !token) die("usage: clawpool create <name> -t <token>");
255
+ if (!name) die("usage: clawpool create <name> [--telegram-token <token>] [-p <port>] [-e KEY=VAL ...]");
214
256
  if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name))
215
257
  die("invalid name: use alphanumerics, dots, hyphens, underscores");
216
258
  if (config.instances[name]) die(`instance '${name}' already exists`);
217
259
 
218
260
  if (!port) port = config.next_port;
261
+ const gatewayToken = generateToken();
219
262
 
220
263
  info(`Creating instance '${name}' on port ${port}...`);
221
264
 
@@ -224,7 +267,7 @@ async function cmdCreate(config: Config, args: string[]) {
224
267
  const inst: Instance = {
225
268
  name,
226
269
  port,
227
- telegram_bot_token: token,
270
+ gateway_token: gatewayToken,
228
271
  status: "running",
229
272
  container_id: "",
230
273
  volume: volumeName(name),
@@ -232,13 +275,17 @@ async function cmdCreate(config: Config, args: string[]) {
232
275
  created_at: new Date().toISOString(),
233
276
  };
234
277
 
278
+ if (telegramToken) inst.telegram_bot_token = telegramToken;
279
+
235
280
  config.instances[name] = inst;
236
- if (config.next_port <= port) config.next_port = port + 1;
281
+ if (config.next_port <= port) config.next_port = port + PORT_STEP;
237
282
 
238
283
  const cid = await rebuildContainer(config, name);
239
284
  await saveConfig(config);
240
285
 
286
+ const host = getLanIP();
241
287
  ok(`Instance '${name}' created (port: ${port}, container: ${cid.slice(0, 12)})`);
288
+ console.log(` ${c.bold("Dashboard:")} ${dashboardURL(host, port, gatewayToken)}`);
242
289
  }
243
290
 
244
291
  async function cmdList(config: Config, args: string[]) {
@@ -247,7 +294,7 @@ async function cmdList(config: Config, args: string[]) {
247
294
 
248
295
  if (names.length === 0) {
249
296
  console.log(
250
- "No instances. Create one with: clawpool create <name> -t <token>"
297
+ "No instances. Create one with: clawpool create <name>"
251
298
  );
252
299
  return;
253
300
  }
@@ -260,7 +307,15 @@ async function cmdList(config: Config, args: string[]) {
260
307
  return;
261
308
  }
262
309
 
263
- const rows = names.map((name) => {
310
+ const host = getLanIP();
311
+
312
+ console.log(
313
+ `${"NAME".padEnd(16)} ${"STATUS".padEnd(10)} ${"PORT".padEnd(7)} DASHBOARD`
314
+ );
315
+ console.log(
316
+ `${"----".padEnd(16)} ${"------".padEnd(10)} ${"----".padEnd(7)} ---------`
317
+ );
318
+ for (const name of names) {
264
319
  const inst = config.instances[name];
265
320
  const statusCol =
266
321
  inst.status === "running"
@@ -268,19 +323,12 @@ async function cmdList(config: Config, args: string[]) {
268
323
  : inst.status === "stopped"
269
324
  ? c.red(inst.status)
270
325
  : c.yellow(inst.status);
271
- return { name, status: statusCol, port: inst.port, created: humanTime(inst.created_at) };
272
- });
273
-
274
- console.log(
275
- `${"NAME".padEnd(16)} ${"STATUS".padEnd(10)} ${"PORT".padEnd(6)} CREATED`
276
- );
277
- console.log(
278
- `${"----".padEnd(16)} ${"------".padEnd(10)} ${"----".padEnd(6)} -------`
279
- );
280
- for (const r of rows) {
281
- // status has ANSI codes so pad the raw length
326
+ const url =
327
+ inst.status === "running"
328
+ ? dashboardURL(host, inst.port, inst.gateway_token)
329
+ : c.dim("—");
282
330
  console.log(
283
- `${r.name.padEnd(16)} ${r.status.padEnd(21)} ${String(r.port).padEnd(6)} ${r.created}`
331
+ `${name.padEnd(16)} ${statusCol.padEnd(21)} ${String(inst.port).padEnd(7)} ${url}`
284
332
  );
285
333
  }
286
334
  }
@@ -292,7 +340,10 @@ async function cmdStart(config: Config, args: string[]) {
292
340
  await docker("start", containerName(name));
293
341
  config.instances[name].status = "running";
294
342
  await saveConfig(config);
343
+ const host = getLanIP();
344
+ const inst = config.instances[name];
295
345
  ok(`Instance '${name}' started`);
346
+ console.log(` ${c.bold("Dashboard:")} ${dashboardURL(host, inst.port, inst.gateway_token)}`);
296
347
  }
297
348
 
298
349
  async function cmdStop(config: Config, args: string[]) {
@@ -312,7 +363,10 @@ async function cmdRestart(config: Config, args: string[]) {
312
363
  await docker("restart", containerName(name));
313
364
  config.instances[name].status = "running";
314
365
  await saveConfig(config);
366
+ const host = getLanIP();
367
+ const inst = config.instances[name];
315
368
  ok(`Instance '${name}' restarted`);
369
+ console.log(` ${c.bold("Dashboard:")} ${dashboardURL(host, inst.port, inst.gateway_token)}`);
316
370
  }
317
371
 
318
372
  async function cmdDelete(config: Config, args: string[]) {
@@ -350,15 +404,12 @@ async function cmdRename(config: Config, args: string[]) {
350
404
 
351
405
  info(`Renaming '${oldName}' → '${newName}'...`);
352
406
 
353
- // Copy instance config
354
407
  const inst = { ...config.instances[oldName] };
355
408
  inst.name = newName;
356
409
  inst.volume = volumeName(newName);
357
410
 
358
- // Stop old container
359
411
  await dockerQuiet("rm", "-f", containerName(oldName));
360
412
 
361
- // Migrate volume
362
413
  const oldVol = volumeName(oldName);
363
414
  const newVol = volumeName(newName);
364
415
  await docker("volume", "create", newVol);
@@ -376,7 +427,6 @@ async function cmdRename(config: Config, args: string[]) {
376
427
  );
377
428
  await dockerQuiet("volume", "rm", oldVol);
378
429
 
379
- // Update config and rebuild
380
430
  delete config.instances[oldName];
381
431
  config.instances[newName] = inst;
382
432
  await rebuildContainer(config, newName);
@@ -388,7 +438,7 @@ async function cmdRename(config: Config, args: string[]) {
388
438
  async function cmdConfig(config: Config, args: string[]) {
389
439
  const name = args[0];
390
440
  if (!name || name.startsWith("-"))
391
- die("usage: clawpool config <name> [-t <token>] [-e KEY=VAL ...]");
441
+ die("usage: clawpool config <name> [--telegram-token <token>] [-e KEY=VAL ...]");
392
442
 
393
443
  requireInstance(config, name);
394
444
  const inst = config.instances[name];
@@ -396,10 +446,9 @@ async function cmdConfig(config: Config, args: string[]) {
396
446
 
397
447
  for (let i = 1; i < args.length; i++) {
398
448
  switch (args[i]) {
399
- case "-t":
400
- case "--token":
449
+ case "--telegram-token":
401
450
  inst.telegram_bot_token = args[++i];
402
- info("Token updated");
451
+ info("Telegram token updated");
403
452
  changed = true;
404
453
  break;
405
454
  case "-e":
@@ -417,7 +466,7 @@ async function cmdConfig(config: Config, args: string[]) {
417
466
  }
418
467
  }
419
468
 
420
- if (!changed) die("nothing to change — specify --token or --env");
469
+ if (!changed) die("nothing to change — specify --telegram-token or --env");
421
470
 
422
471
  info("Rebuilding container...");
423
472
  await rebuildContainer(config, name);
@@ -426,10 +475,10 @@ async function cmdConfig(config: Config, args: string[]) {
426
475
  }
427
476
 
428
477
  async function cmdLogs(config: Config, args: string[]) {
429
- const name = args.find((a): a is string => !a.startsWith("-"));
430
- if (!name) return die("usage: clawpool logs <name> [-f]");
478
+ const name = args[0];
479
+ if (!name || name.startsWith("-")) return die("usage: clawpool logs <name> [-f] [--tail N]");
431
480
  requireInstance(config, name);
432
- const flags = args.filter((a) => a.startsWith("-"));
481
+ const flags = args.slice(1);
433
482
  await dockerExec("logs", ...flags, containerName(name));
434
483
  }
435
484
 
@@ -447,14 +496,18 @@ async function cmdStatus(config: Config, args: string[]) {
447
496
  ? c.red(inst.status)
448
497
  : c.yellow(inst.status);
449
498
 
499
+ const host = getLanIP();
450
500
  console.log(`${c.bold("Instance:")} ${inst.name}`);
451
501
  console.log(`${c.bold("Status:")} ${statusCol}`);
452
502
  console.log(`${c.bold("Port:")} ${inst.port}`);
453
503
  console.log(`${c.bold("Volume:")} ${inst.volume}`);
454
504
  console.log(`${c.bold("Created:")} ${inst.created_at}`);
455
- console.log(
456
- `${c.bold("Token:")} ${inst.telegram_bot_token.slice(0, 20)}...`
457
- );
505
+ if (inst.status === "running") {
506
+ console.log(`${c.bold("Dashboard:")} ${dashboardURL(host, inst.port, inst.gateway_token)}`);
507
+ }
508
+ if (inst.telegram_bot_token) {
509
+ console.log(`${c.bold("Telegram:")} configured`);
510
+ }
458
511
 
459
512
  const envKeys = Object.keys(inst.env);
460
513
  if (envKeys.length > 0) {
@@ -500,31 +553,35 @@ function cmdHelp() {
500
553
  console.log(`clawpool — Manage OpenClaw container instances
501
554
 
502
555
  Usage:
503
- clawpool create <name> -t <telegram_bot_token> [-p <port>] [-e KEY=VAL ...]
556
+ clawpool create <name> [--telegram-token <token>] [-p <port>] [-e KEY=VAL ...]
504
557
  clawpool list [--json]
505
558
  clawpool start <name>
506
559
  clawpool stop <name>
507
560
  clawpool restart <name>
508
561
  clawpool delete <name> [--purge]
509
562
  clawpool rename <old_name> <new_name>
510
- clawpool config <name> [-t <new_token>] [-e KEY=VAL ...]
563
+ clawpool config <name> [--telegram-token <token>] [-e KEY=VAL ...]
511
564
  clawpool logs <name> [-f]
512
565
  clawpool status <name>
513
566
  clawpool shell <name>
514
567
  clawpool image [set <image> | show]
515
568
 
516
569
  Options:
517
- -t, --token Telegram bot token
518
- -p, --port Host port (auto-assigned if omitted)
519
- -e, --env Extra environment variable (repeatable)
520
- --purge Also remove data volume on delete
521
- --json Output in JSON format
570
+ --telegram-token Telegram bot token (optional)
571
+ -p, --port Host port (auto-assigned from ${BASE_PORT}, step ${PORT_STEP})
572
+ -e, --env Extra environment variable (repeatable)
573
+ --purge Also remove data volume on delete
574
+ --json Output in JSON format
575
+
576
+ Each instance gets a Web Dashboard URL with an auto-generated auth token.
577
+ Access it from any browser on your local network.
522
578
 
523
579
  Examples:
524
- clawpool create alpha -t "123456:AAH..." -e "OPENAI_API_KEY=sk-..."
580
+ clawpool create alpha
581
+ clawpool create beta -e "OPENAI_API_KEY=sk-..."
582
+ clawpool create gamma --telegram-token "123456:AAH..."
525
583
  clawpool list
526
584
  clawpool logs alpha -f
527
- clawpool config alpha -t "new_token"
528
585
  clawpool delete alpha --purge`);
529
586
  }
530
587
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linkclaw/clawpool",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "clawpool": "./clawpool.ts"
@@ -24,5 +24,5 @@
24
24
  "url": "https://github.com/linkclaw-lab/clawpool.git"
25
25
  },
26
26
  "license": "MIT",
27
- "description": "CLI tool to manage OpenClaw container instances with Telegram integration"
27
+ "description": "CLI tool to manage OpenClaw container instances with Web Dashboard"
28
28
  }