@linkclaw/clawpool 0.2.1 → 0.2.3

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 +28 -0
  2. package/clawpool.ts +53 -23
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -98,6 +98,34 @@ Stored at `~/.clawpool/config.json`:
98
98
  }
99
99
  ```
100
100
 
101
+ ## Host Config Inheritance
102
+
103
+ When creating or restarting an instance, clawpool automatically merges settings from the host machine's OpenClaw installation (if present) into each container:
104
+
105
+ - **`~/.openclaw/openclaw.json`** — model providers, default model, env vars, and other settings are inherited. Gateway-specific settings (`gateway`, `meta`) are excluded and managed by clawpool per-instance.
106
+ - **`~/.openclaw/agents/main/agent/auth-profiles.json`** — OAuth auth profiles (e.g. `openai-codex`) are copied into each container, enabling provider access without per-instance login.
107
+
108
+ If OpenClaw is not installed on the host, instances start with a minimal config (gateway auth only). You can then configure providers via environment variables:
109
+
110
+ ```bash
111
+ clawpool create alpha -e "ANTHROPIC_API_KEY=sk-ant-..." -e "OPENAI_API_KEY=sk-..."
112
+ ```
113
+
114
+ ### Reusing auth profiles from another machine
115
+
116
+ If you want to copy auth profiles from a remote OpenClaw installation:
117
+
118
+ ```bash
119
+ # On the machine where OpenClaw is logged in:
120
+ cat ~/.openclaw/agents/main/agent/auth-profiles.json
121
+
122
+ # Copy the output to the clawpool host:
123
+ mkdir -p ~/.openclaw/agents/main/agent
124
+ # paste into ~/.openclaw/agents/main/agent/auth-profiles.json
125
+ ```
126
+
127
+ clawpool will pick up the file on the next `create` or `restart`.
128
+
101
129
  ## Environment
102
130
 
103
131
  | Variable | Purpose |
package/clawpool.ts CHANGED
@@ -63,8 +63,8 @@ function getLanIP(): string {
63
63
  return "localhost";
64
64
  }
65
65
 
66
- function dashboardURL(host: string, port: number, token: string): string {
67
- return `http://${host}:${port}?token=${token}`;
66
+ function dashboardURL(host: string, port: number): string {
67
+ return `http://${host}:${port}`;
68
68
  }
69
69
 
70
70
  function humanTime(iso: string): string {
@@ -154,7 +154,9 @@ async function syncStatus(
154
154
  }
155
155
  }
156
156
 
157
- const HOST_OC_CONFIG = join(homedir(), ".openclaw", "openclaw.json");
157
+ const HOST_OC_DIR = join(homedir(), ".openclaw");
158
+ const HOST_OC_CONFIG = join(HOST_OC_DIR, "openclaw.json");
159
+ const HOST_AUTH_PROFILES = join(HOST_OC_DIR, "agents", "main", "agent", "auth-profiles.json");
158
160
 
159
161
  function loadHostOpenClawConfig(): Record<string, unknown> {
160
162
  if (!existsSync(HOST_OC_CONFIG)) return {};
@@ -188,13 +190,26 @@ function deepMerge(target: Record<string, unknown>, source: Record<string, unkno
188
190
  return result;
189
191
  }
190
192
 
193
+ function loadHostAuthProfiles(): string | null {
194
+ if (!existsSync(HOST_AUTH_PROFILES)) return null;
195
+ try {
196
+ return require("fs").readFileSync(HOST_AUTH_PROFILES, "utf-8").trim();
197
+ } catch {
198
+ return null;
199
+ }
200
+ }
201
+
191
202
  function buildOpenClawConfig(token: string): string {
192
203
  const hostConfig = loadHostOpenClawConfig();
193
204
  // Gateway config is always overridden per-instance
194
205
  const instanceOverrides: Record<string, unknown> = {
195
206
  gateway: {
196
207
  auth: { mode: "token", token },
197
- controlUi: { dangerouslyAllowHostHeaderOriginFallback: true },
208
+ controlUi: {
209
+ dangerouslyAllowHostHeaderOriginFallback: true,
210
+ allowInsecureAuth: true,
211
+ dangerouslyDisableDeviceAuth: true,
212
+ },
198
213
  },
199
214
  };
200
215
  // Strip host gateway settings (we manage those), keep everything else
@@ -205,18 +220,28 @@ function buildOpenClawConfig(token: string): string {
205
220
 
206
221
  function buildRunArgs(config: Config, inst: Instance): string[] {
207
222
  const ocConfig = buildOpenClawConfig(inst.gateway_token);
208
- // Always write config on start — merges host ~/.openclaw/openclaw.json with per-instance gateway auth
209
- const entrypoint = [
210
- "sh",
211
- "-c",
212
- [
213
- "mkdir -p /home/node/data/.openclaw",
214
- "chown -R node:node /home/node/data",
215
- `echo '${ocConfig}' > /home/node/data/.openclaw/openclaw.json`,
216
- `exec su -s /bin/sh node -c 'exec node dist/index.js gateway --allow-unconfigured --port ${inst.port} --bind lan'`,
217
- ].join(" && "),
223
+ const authProfiles = loadHostAuthProfiles();
224
+
225
+ const initSteps = [
226
+ "mkdir -p /home/node/data/.openclaw/agents/main/agent",
227
+ "chown -R node:node /home/node/data",
228
+ `echo '${ocConfig}' > /home/node/data/.openclaw/openclaw.json`,
218
229
  ];
219
230
 
231
+ if (authProfiles) {
232
+ // Escape single quotes in JSON for shell embedding
233
+ const escaped = authProfiles.replace(/'/g, "'\\''");
234
+ initSteps.push(
235
+ `echo '${escaped}' > /home/node/data/.openclaw/agents/main/agent/auth-profiles.json`
236
+ );
237
+ }
238
+
239
+ initSteps.push(
240
+ `exec su -s /bin/sh node -c 'exec node dist/index.js gateway --allow-unconfigured --port ${inst.port} --bind lan'`
241
+ );
242
+
243
+ const entrypoint = ["sh", "-c", initSteps.join(" && ")];
244
+
220
245
  const args = [
221
246
  "run",
222
247
  "-d",
@@ -227,7 +252,7 @@ function buildRunArgs(config: Config, inst: Instance): string[] {
227
252
  "--user",
228
253
  "root",
229
254
  "-p",
230
- `${inst.port}:${inst.port}`,
255
+ `127.0.0.1:${inst.port}:${inst.port}`,
231
256
  "-v",
232
257
  `${inst.volume}:/home/node/data`,
233
258
  "-e",
@@ -325,7 +350,8 @@ async function cmdCreate(config: Config, args: string[]) {
325
350
 
326
351
  const host = getLanIP();
327
352
  ok(`Instance '${name}' created (port: ${port}, container: ${cid.slice(0, 12)})`);
328
- console.log(` ${c.bold("Dashboard:")} ${dashboardURL(host, port, gatewayToken)}`);
353
+ console.log(` ${c.bold("Dashboard:")} ${dashboardURL(host, port)}`);
354
+ console.log(` ${c.bold("Token:")} ${gatewayToken}`);
329
355
  }
330
356
 
331
357
  async function cmdList(config: Config, args: string[]) {
@@ -350,10 +376,10 @@ async function cmdList(config: Config, args: string[]) {
350
376
  const host = getLanIP();
351
377
 
352
378
  console.log(
353
- `${"NAME".padEnd(16)} ${"STATUS".padEnd(10)} ${"PORT".padEnd(7)} DASHBOARD`
379
+ `${"NAME".padEnd(16)} ${"STATUS".padEnd(10)} ${"PORT".padEnd(7)} ${"TOKEN".padEnd(34)} URL`
354
380
  );
355
381
  console.log(
356
- `${"----".padEnd(16)} ${"------".padEnd(10)} ${"----".padEnd(7)} ---------`
382
+ `${"----".padEnd(16)} ${"------".padEnd(10)} ${"----".padEnd(7)} ${"-----".padEnd(34)} ---`
357
383
  );
358
384
  for (const name of names) {
359
385
  const inst = config.instances[name];
@@ -365,10 +391,14 @@ async function cmdList(config: Config, args: string[]) {
365
391
  : c.yellow(inst.status);
366
392
  const url =
367
393
  inst.status === "running"
368
- ? dashboardURL(host, inst.port, inst.gateway_token)
394
+ ? dashboardURL(host, inst.port)
395
+ : c.dim("—");
396
+ const token =
397
+ inst.status === "running"
398
+ ? inst.gateway_token
369
399
  : c.dim("—");
370
400
  console.log(
371
- `${name.padEnd(16)} ${statusCol.padEnd(21)} ${String(inst.port).padEnd(7)} ${url}`
401
+ `${name.padEnd(16)} ${statusCol.padEnd(21)} ${String(inst.port).padEnd(7)} ${token.padEnd(34)} ${url}`
372
402
  );
373
403
  }
374
404
  }
@@ -383,7 +413,7 @@ async function cmdStart(config: Config, args: string[]) {
383
413
  const host = getLanIP();
384
414
  const inst = config.instances[name];
385
415
  ok(`Instance '${name}' started`);
386
- console.log(` ${c.bold("Dashboard:")} ${dashboardURL(host, inst.port, inst.gateway_token)}`);
416
+ console.log(` ${c.bold("Dashboard:")} ${dashboardURL(host, inst.port)}`);
387
417
  }
388
418
 
389
419
  async function cmdStop(config: Config, args: string[]) {
@@ -406,7 +436,7 @@ async function cmdRestart(config: Config, args: string[]) {
406
436
  const host = getLanIP();
407
437
  const inst = config.instances[name];
408
438
  ok(`Instance '${name}' restarted`);
409
- console.log(` ${c.bold("Dashboard:")} ${dashboardURL(host, inst.port, inst.gateway_token)}`);
439
+ console.log(` ${c.bold("Dashboard:")} ${dashboardURL(host, inst.port)}`);
410
440
  }
411
441
 
412
442
  async function cmdDelete(config: Config, args: string[]) {
@@ -543,7 +573,7 @@ async function cmdStatus(config: Config, args: string[]) {
543
573
  console.log(`${c.bold("Volume:")} ${inst.volume}`);
544
574
  console.log(`${c.bold("Created:")} ${inst.created_at}`);
545
575
  if (inst.status === "running") {
546
- console.log(`${c.bold("Dashboard:")} ${dashboardURL(host, inst.port, inst.gateway_token)}`);
576
+ console.log(`${c.bold("Dashboard:")} ${dashboardURL(host, inst.port)}`);
547
577
  }
548
578
  if (inst.telegram_bot_token) {
549
579
  console.log(`${c.bold("Telegram:")} configured`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linkclaw/clawpool",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "clawpool": "./clawpool.ts"