@drakon-systems/multi-clawd 2.1.1 → 2.1.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.
- package/README.md +16 -14
- package/SECURITY.md +22 -16
- package/dist/runtime-auth.js +13 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -284,16 +284,15 @@ claude-cli/claude-fable-5 # main login
|
|
|
284
284
|
|
|
285
285
|
## Platform support
|
|
286
286
|
|
|
287
|
-
multi-clawd
|
|
288
|
-
|
|
289
|
-
Code backend runs.
|
|
287
|
+
multi-clawd uses Node without native modules. External commands are run with
|
|
288
|
+
argument arrays, never through a shell string. Launch mode varies by platform.
|
|
290
289
|
|
|
291
290
|
| Platform | Status |
|
|
292
291
|
|---|---|
|
|
293
292
|
| Linux | ✅ Verified in production (x64 and arm64) |
|
|
294
|
-
| macOS | ✅ Supported —
|
|
293
|
+
| macOS | ✅ Supported — reads Claude logins from the Keychain through the `security` CLI and schedules the optional watchdog with a launchd plist in `~/Library/LaunchAgents/` |
|
|
295
294
|
| Windows (WSL2) | ✅ Supported — OpenClaw's recommended gateway runtime on Windows; follow the Linux instructions inside WSL |
|
|
296
|
-
| Windows (native) | ⚠️
|
|
295
|
+
| Windows (native) | ⚠️ Not yet verified by us — verified launch is not implemented on Windows; it falls back to the legacy node + shim launch, so the v2.1.0 verified-launch fix for OpenClaw 2026.10.2 setup authority ("Ask OpenClaw") does not apply there. Reports welcome. |
|
|
297
296
|
|
|
298
297
|
## Install
|
|
299
298
|
|
|
@@ -375,6 +374,8 @@ cd multi-clawd && npm install && npm run build
|
|
|
375
374
|
openclaw plugins install "$(pwd)"
|
|
376
375
|
```
|
|
377
376
|
|
|
377
|
+
npm 10 and 11 both work here because the lockfile is committed.
|
|
378
|
+
|
|
378
379
|
```powershell
|
|
379
380
|
# Windows (native, PowerShell)
|
|
380
381
|
git clone https://github.com/Drakon-Systems-Ltd/multi-clawd.git
|
|
@@ -385,7 +386,7 @@ openclaw plugins install (Get-Location).Path
|
|
|
385
386
|
**Or let your agent install it.** Running an OpenClaw assistant or Claude
|
|
386
387
|
Code on the target machine already? Paste it this and go make coffee:
|
|
387
388
|
|
|
388
|
-
> Read https://raw.githubusercontent.com/Drakon-Systems-Ltd/multi-clawd/v2.1.
|
|
389
|
+
> Read https://raw.githubusercontent.com/Drakon-Systems-Ltd/multi-clawd/v2.1.3/SETUP-AGENT.md
|
|
389
390
|
> and follow it to set up multi-clawd on this machine. I own a second
|
|
390
391
|
> Claude account — ask me when you need me to log in.
|
|
391
392
|
|
|
@@ -745,9 +746,10 @@ What to know:
|
|
|
745
746
|
|
|
746
747
|
- **Why a native or config-dir login needs its own token.** That login is a
|
|
747
748
|
rotating one-time OAuth grant, and a copy of it breaks on the next refresh.
|
|
748
|
-
multi-clawd never
|
|
749
|
-
|
|
750
|
-
|
|
749
|
+
multi-clawd never copies that login into a direct profile. (The usage poll
|
|
750
|
+
and login checks do read Claude's credential files, but never copy or
|
|
751
|
+
rewrite them.) Run `claude setup-token` signed in as that account and store
|
|
752
|
+
the token. `setup` walks you through it.
|
|
751
753
|
- **Already stored a profile yourself?** Adopt it with
|
|
752
754
|
`"direct": { "profileId": "anthropic:<id>" }`. It gets ordered, never
|
|
753
755
|
rewritten.
|
|
@@ -849,11 +851,11 @@ callsite (the ones static scanners flag) and why each exists.
|
|
|
849
851
|
|
|
850
852
|
| Surface | What multi-clawd does |
|
|
851
853
|
|---|---|
|
|
852
|
-
| Network |
|
|
853
|
-
| Credentials | Reads each account's
|
|
854
|
-
| Files read | `~/.openclaw/openclaw.json
|
|
855
|
-
| Files written | `~/.openclaw/state/multi-clawd
|
|
856
|
-
| Processes |
|
|
854
|
+
| Network | The gateway plugin polls `https://api.anthropic.com/api/oauth/usage` for eligible pooled accounts on an interval (default 120000 ms / 2 minutes); `pool.usagePoll.enabled: false` disables that poll. `multi-clawd usage` requests the same endpoint on demand. `doctor` runs `npm view` when its registry cache is stale; install and update commands contact the package registry. Claude Code subprocesses make their own provider requests. No plugin telemetry or analytics endpoint. |
|
|
855
|
+
| Credentials | Reads declared setup-token files or gateway-resolved `oauthTokenRef` values for Claude child launches; reads each eligible account's `.credentials.json` access token for the usage poll; reads Claude login credentials from files or the macOS Keychain via the `security` CLI for login checks and runtime auth; reads account identity for file-backed logins from `.claude.json` (`~/.claude.json` for a native login, or the account config dir). Setup tokens are passed to Claude through the child environment. |
|
|
856
|
+
| Files read | `~/.openclaw/openclaw.json`, declared account config dirs and token files, Claude `.credentials.json` and `.claude.json` files as described above, and local state files. |
|
|
857
|
+
| Files written | Under `~/.openclaw/state/multi-clawd/` by default: `<id>.json`, `<id>.usage.json`, `pool-<id>.sticky.json`, `direct-order.sticky.json`, `known-models.json`, `alerts-spool.jsonl`, `watchdog.json`, `registry-check.json`, `auth-profiles-<agent>.json`, `runtime/<hash>/` (copied runtime files), and `watchdog-launcher.mjs`. The watchdog state file can be redirected with `MULTI_CLAWD_WATCHDOG_STATE`. Setup writes `~/.openclaw/openclaw.json` and a dated backup. On session handover, transcript `.jsonl` files are copied between account config dirs. The optional watchdog installs `~/.config/systemd/user/multi-clawd-eviction-watchdog.service` and `.timer` on Linux, or `~/Library/LaunchAgents/com.multi-clawd.eviction-watchdog.plist` on macOS; the unit points at the state-dir launcher. |
|
|
858
|
+
| Processes | Launches Claude Code and the node + shim transport; CLI, setup and doctor run npm, OpenClaw, Claude and OS scheduler commands; the optional watchdog checks OpenClaw logs and may restart the gateway; Hermes integration runs its Python bridge. See [SECURITY.md](SECURITY.md) for the process-call table. |
|
|
857
859
|
| Consent | The wizard asks before every write, merges non-destructively, and never overwrites an existing account entry. `--dry-run` previews everything. |
|
|
858
860
|
|
|
859
861
|
Housekeeping:
|
package/SECURITY.md
CHANGED
|
@@ -15,9 +15,9 @@ credit you (unless you'd rather we didn't), and note it in the CHANGELOG.
|
|
|
15
15
|
|
|
16
16
|
## How credentials are handled
|
|
17
17
|
|
|
18
|
-
- **Tokens are never logged and never appear in a command line.**
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
- **Tokens are never logged and never appear in a command line.** Claude CLI
|
|
19
|
+
child launches receive setup tokens through their environment; direct-route
|
|
20
|
+
OpenClaw commands receive them on stdin.
|
|
21
21
|
- **Secret references are preferred over token files.** `oauthTokenRef` uses
|
|
22
22
|
the same `{source, provider, id}` shape as the rest of `openclaw.json` and
|
|
23
23
|
resolves through the gateway's own configured secret providers (1Password,
|
|
@@ -34,8 +34,9 @@ credit you (unless you'd rather we didn't), and note it in the CHANGELOG.
|
|
|
34
34
|
environment variables covering authentication, runtime, and telemetry are
|
|
35
35
|
cleared before each launch, so one account's credential cannot bleed into
|
|
36
36
|
another account's session.
|
|
37
|
-
- **
|
|
38
|
-
contain rate-limit
|
|
37
|
+
- **Per-account health and usage files are written `0600`,** using temporary
|
|
38
|
+
files and rename, and contain rate-limit data rather than credentials. Other
|
|
39
|
+
state files include model, alert, registry and watchdog data.
|
|
39
40
|
|
|
40
41
|
## Permissions
|
|
41
42
|
|
|
@@ -48,20 +49,25 @@ nothing and Claude keeps its default prompt-honouring behaviour.
|
|
|
48
49
|
## Why static scanners flag this package
|
|
49
50
|
|
|
50
51
|
Automated scans flag multi-clawd for `dangerous_exec`. That is expected and
|
|
51
|
-
correct: starting Claude Code processes *is* the product. Every
|
|
52
|
-
is listed here so you can check
|
|
52
|
+
correct: starting Claude Code processes *is* the product. Every source file
|
|
53
|
+
that calls a child-process API is listed here so you can check it yourself.
|
|
53
54
|
|
|
54
55
|
| Location | What it runs | Why |
|
|
55
56
|
| --- | --- | --- |
|
|
56
|
-
| `dist/shim.js` |
|
|
57
|
-
| `dist/
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `dist/openclaw-runner.js` | the `openclaw` CLI (or `directRoute.openclawCommand`) | Direct route only
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
| `dist/shim.js` | selected `claude` executable | Launches Claude Code for the selected account and, when an in-turn retry is armed, relaunches it for a sibling account. |
|
|
58
|
+
| `dist/shim-transport.js` | `node <shim>` | Runs the shim for an execute-transport turn. |
|
|
59
|
+
| `dist/credential-store.js` | `security find-generic-password` / `security dump-keychain` | Reads a macOS Keychain login or inventories Keychain item attributes for credential checks. |
|
|
60
|
+
| `dist/watchdog-schedule.js` | generated `watchdog-launcher.mjs` runs `node <bundled eviction-watchdog.mjs>` | Renders the state-dir launcher that a systemd user timer or launchd agent runs. The launcher resolves the current install before spawning its bundled watchdog script. |
|
|
61
|
+
| `dist/openclaw-runner.js` | the `openclaw` CLI (or `directRoute.openclawCommand`) | Direct route only: reads and writes the `anthropic` auth profiles and order through OpenClaw subcommands. A setup token travels on the child's stdin. |
|
|
62
|
+
| `scripts/cli.mjs` | `npm view` / `npm i -g`, `openclaw --version`, `openclaw` plugin install and gateway restart, `claude` login/status commands, `launchctl` / `systemctl`, and `node <bundled script>` | Checks and updates the CLI/plugin, handles account login, dispatches subcommands and repairs watchdog scheduling. |
|
|
63
|
+
| `scripts/setup.mjs` | `claude --version`, `launchctl` / `systemctl` | Checks the Claude CLI and loads or repairs optional watchdog scheduling. |
|
|
64
|
+
| `scripts/doctor.mjs` | `npm view`, `claude --version`, `openclaw models auth list` / `openclaw agent`, `launchctl` / `systemctl` | Checks registry version when its cache is stale, inspects auth and watchdog status, and optionally probes accounts. |
|
|
65
|
+
| `scripts/eviction-watchdog.mjs` | `openclaw logs --plain` / `openclaw gateway restart` | Detects the eviction signature and restarts the gateway when the watchdog's conditions are met. |
|
|
66
|
+
| `scripts/hermes.mjs` | Python interpreter with `-c` / bundled bridge | Checks Python availability and runs the Hermes bridge. |
|
|
67
|
+
|
|
68
|
+
None of these pass a command string to a shell: child-process calls use an
|
|
69
|
+
executable and argument array. The launcher generated by `watchdog-schedule`
|
|
70
|
+
uses the same form.
|
|
65
71
|
|
|
66
72
|
## Things you should decide for yourself
|
|
67
73
|
|
package/dist/runtime-auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ACCESS_TOKEN_EXPIRY_SKEW_MS, CLAUDE_KEYCHAIN_SERVICE, assessAccountCredential, keychainServiceForConfigDir, } from "./login-health.js";
|
|
1
|
+
import { ACCESS_TOKEN_EXPIRY_SKEW_MS, AUTH_PROOF_FRESH_MS, CLAUDE_KEYCHAIN_SERVICE, assessAccountCredential, keychainServiceForConfigDir, } from "./login-health.js";
|
|
2
2
|
export const RUNTIME_AUTH_MARKER_PREFIX = "oauth:";
|
|
3
3
|
export const LOGIN_AUTH_CACHE_TTL_MS = 30 * 1000;
|
|
4
4
|
export function runtimeAuthMarker(providerId) {
|
|
@@ -61,10 +61,20 @@ export function createLoginAuthCache(options) {
|
|
|
61
61
|
const tokenBound = check.status === "ok" && check.expiresAt !== undefined
|
|
62
62
|
? check.expiresAt - ACCESS_TOKEN_EXPIRY_SKEW_MS
|
|
63
63
|
: undefined;
|
|
64
|
-
|
|
64
|
+
let hardUntil = nowMs + ttlMs;
|
|
65
|
+
if (tokenBound !== undefined && tokenBound > nowMs) {
|
|
66
|
+
hardUntil = tokenBound;
|
|
67
|
+
}
|
|
68
|
+
else if (check.status === "ok") {
|
|
69
|
+
const lastSuccessAt = health(accountId).lastSuccessAt;
|
|
70
|
+
if (lastSuccessAt !== undefined) {
|
|
71
|
+
hardUntil = Math.min(nowMs + 2 * ttlMs, lastSuccessAt + AUTH_PROOF_FRESH_MS);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
65
74
|
cache.set(accountId, { check, freshUntil: nowMs + ttlMs, hardUntil });
|
|
66
75
|
};
|
|
67
76
|
const judge = async (account, nowMs) => {
|
|
77
|
+
await Promise.resolve();
|
|
68
78
|
let io = options.io;
|
|
69
79
|
if (io.platform === "darwin" && options.readKeychainItemAsync) {
|
|
70
80
|
const dir = account.native ? "~/.claude" : account.configDir;
|
|
@@ -103,7 +113,7 @@ export function createLoginAuthCache(options) {
|
|
|
103
113
|
if (health(account.id).failed)
|
|
104
114
|
return undefined;
|
|
105
115
|
const hit = cache.get(account.id);
|
|
106
|
-
if (!hit || hit.freshUntil <= nowMs)
|
|
116
|
+
if (!hit || hit.freshUntil <= nowMs || hit.hardUntil <= nowMs)
|
|
107
117
|
void refresh(account, nowMs);
|
|
108
118
|
if (!hit || hit.hardUntil <= nowMs)
|
|
109
119
|
return undefined;
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "multi-clawd",
|
|
3
3
|
"name": "multi-clawd",
|
|
4
|
-
"version": "2.1.
|
|
4
|
+
"version": "2.1.3",
|
|
5
5
|
"description": "Register additional Claude Code logins (Max/Pro accounts) as first-class OpenClaw CLI backends for cross-account failover, keeping the full skills/MCP harness on every account.",
|
|
6
6
|
"cliBackends": [
|
|
7
7
|
"claw1",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drakon-systems/multi-clawd",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3",
|
|
4
4
|
"description": "Multi-account Claude Code failover for OpenClaw — register additional Claude (Max/Pro) logins as first-class CLI backends and keep the full skills/MCP harness across every account. Also imports those accounts' setup tokens into Hermes Agent's Anthropic credential pool.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"setup": "node scripts/setup.mjs",
|
|
62
62
|
"sync-manifest": "node -e \"const fs=require('fs');const m=JSON.parse(fs.readFileSync('openclaw.plugin.json','utf8'));m.version=process.env.npm_package_version||JSON.parse(fs.readFileSync('package.json','utf8')).version;fs.writeFileSync('openclaw.plugin.json',JSON.stringify(m,null,2)+'\\n')\"",
|
|
63
63
|
"sync-readme-tag": "node scripts/sync-readme-tag.mjs",
|
|
64
|
-
"version": "npm run sync-manifest && npm run sync-readme-tag && git add openclaw.plugin.json README.md"
|
|
64
|
+
"version": "npm run sync-manifest && npm run sync-readme-tag && git add openclaw.plugin.json README.md package-lock.json"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"openclaw": ">=2026.8.1"
|