@drakon-systems/multi-clawd 1.8.0 → 1.8.1
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 +1 -1
- package/dist/credential-state.js +30 -0
- package/dist/index.js +3 -30
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/cli.mjs +41 -10
package/README.md
CHANGED
|
@@ -364,7 +364,7 @@ openclaw plugins install (Get-Location).Path
|
|
|
364
364
|
**Or let your agent install it.** Running an OpenClaw assistant or Claude
|
|
365
365
|
Code on the target machine already? Paste it this and go make coffee:
|
|
366
366
|
|
|
367
|
-
> Read https://raw.githubusercontent.com/Drakon-Systems-Ltd/multi-clawd/v1.8.
|
|
367
|
+
> Read https://raw.githubusercontent.com/Drakon-Systems-Ltd/multi-clawd/v1.8.1/SETUP-AGENT.md
|
|
368
368
|
> and follow it to set up multi-clawd on this machine. I own a second
|
|
369
369
|
> Claude account — ask me when you need me to log in.
|
|
370
370
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { parseStoredState, mergeHealthStates, clearCredentialFailure, } from "./shim-core.js";
|
|
5
|
+
export function healthStateFile(accountId) {
|
|
6
|
+
return join(homedir(), ".openclaw", "state", "multi-clawd", `${accountId}.json`);
|
|
7
|
+
}
|
|
8
|
+
export function clearAccountCredentialFailure(accountId) {
|
|
9
|
+
const file = healthStateFile(accountId);
|
|
10
|
+
let state;
|
|
11
|
+
try {
|
|
12
|
+
state = parseStoredState(readFileSync(file, "utf8")) ?? { accountId, windows: {} };
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if (state.credential?.status !== "failed")
|
|
18
|
+
return false;
|
|
19
|
+
const cleared = mergeHealthStates(state, clearCredentialFailure(state, Date.now()), Date.now());
|
|
20
|
+
try {
|
|
21
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
22
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
23
|
+
writeFileSync(tmp, JSON.stringify(cleared, null, 2), { mode: 0o600 });
|
|
24
|
+
renameSync(tmp, file);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -11,10 +11,12 @@ import { resolveExecMode, permissionModeArgs } from "./exec-policy.js";
|
|
|
11
11
|
import { resolveBaseModelIds } from "./catalog-source.js";
|
|
12
12
|
import { allCredentialFailed, classifyAccountHealth } from "./health.js";
|
|
13
13
|
import { decideStickySelection } from "./sticky.js";
|
|
14
|
-
import {
|
|
14
|
+
import { mergeHealthStates, parseStoredState, recordCredentialFailure, } from "./shim-core.js";
|
|
15
15
|
import { createTokenRefResolver, isSecretRefShape, } from "./token-resolution.js";
|
|
16
16
|
import { resolveSecretRefValues } from "openclaw/plugin-sdk/secret-ref-runtime";
|
|
17
17
|
import { addAlert, alertKeysWithPrefix, clearAlert, pendingAlertText, } from "./alerts.js";
|
|
18
|
+
import { healthStateFile, clearAccountCredentialFailure } from "./credential-state.js";
|
|
19
|
+
export { healthStateFile, clearAccountCredentialFailure };
|
|
18
20
|
import { buildAccountChildEnv, tokenFileModeWarning, validateAccountTokenSources, } from "./account-env.js";
|
|
19
21
|
import { diffCatalogModels, formatNewModelNotice, } from "./model-currency.js";
|
|
20
22
|
import { checkAccountCredential, createRefProbeTracker, } from "./login-health.js";
|
|
@@ -285,9 +287,6 @@ function buildRuntimeModel(account, modelId) {
|
|
|
285
287
|
};
|
|
286
288
|
}
|
|
287
289
|
const SHIM_PATH = fileURLToPath(new URL("./shim.js", import.meta.url));
|
|
288
|
-
export function healthStateFile(accountId) {
|
|
289
|
-
return join(homedir(), ".openclaw", "state", "multi-clawd", `${accountId}.json`);
|
|
290
|
-
}
|
|
291
290
|
function knownModelsFile() {
|
|
292
291
|
return join(homedir(), ".openclaw", "state", "multi-clawd", "known-models.json");
|
|
293
292
|
}
|
|
@@ -500,32 +499,6 @@ function readHealthState(accountId) {
|
|
|
500
499
|
return undefined;
|
|
501
500
|
}
|
|
502
501
|
}
|
|
503
|
-
export function clearAccountCredentialFailure(accountId) {
|
|
504
|
-
const file = healthStateFile(accountId);
|
|
505
|
-
let state;
|
|
506
|
-
try {
|
|
507
|
-
state = parseStoredState(readFileSync(file, "utf8")) ?? {
|
|
508
|
-
accountId,
|
|
509
|
-
windows: {},
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
catch {
|
|
513
|
-
return false;
|
|
514
|
-
}
|
|
515
|
-
if (state.credential?.status !== "failed")
|
|
516
|
-
return false;
|
|
517
|
-
const cleared = mergeHealthStates(state, clearCredentialFailure(state, Date.now()), Date.now());
|
|
518
|
-
try {
|
|
519
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
520
|
-
const tmp = `${file}.tmp-${process.pid}`;
|
|
521
|
-
writeFileSync(tmp, JSON.stringify(cleared, null, 2), { mode: 0o600 });
|
|
522
|
-
renameSync(tmp, file);
|
|
523
|
-
return true;
|
|
524
|
-
}
|
|
525
|
-
catch {
|
|
526
|
-
return false;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
502
|
function readStickyEntry(file) {
|
|
530
503
|
try {
|
|
531
504
|
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "multi-clawd",
|
|
3
3
|
"name": "multi-clawd",
|
|
4
|
-
"version": "1.8.
|
|
4
|
+
"version": "1.8.1",
|
|
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": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
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",
|
package/scripts/cli.mjs
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* has to remember `--pin --force`.
|
|
17
17
|
*/
|
|
18
18
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
19
|
-
import { readFileSync } from "node:fs";
|
|
19
|
+
import { readFileSync, existsSync as existsSyncEarly } from "node:fs";
|
|
20
20
|
import { dirname, join, resolve } from "node:path";
|
|
21
21
|
import { fileURLToPath } from "node:url";
|
|
22
22
|
import readline from "node:readline/promises";
|
|
@@ -92,6 +92,34 @@ async function askYes(question, dflt = true) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/** This package's own version (the CLI half). */
|
|
95
|
+
/**
|
|
96
|
+
* Why a `dist/` import failed, in the user's terms.
|
|
97
|
+
*
|
|
98
|
+
* The bare "reinstall the package" message was wrong in the one case that
|
|
99
|
+
* actually happens: this CLI installs globally, `openclaw` is a peerDependency,
|
|
100
|
+
* and on a box where the peer is not resolvable from this directory a perfectly
|
|
101
|
+
* complete build still throws ERR_MODULE_NOT_FOUND. Reinstalling cannot fix
|
|
102
|
+
* that, so "reinstall the package" sends people in circles — it did, on a Mac,
|
|
103
|
+
* 18 Aug 2026. Check the file exists first, then report the real cause.
|
|
104
|
+
*/
|
|
105
|
+
function distFailure(cmd, mod, err) {
|
|
106
|
+
const path = resolve(__dirname, "..", "dist", mod);
|
|
107
|
+
if (!existsSyncEarly(path)) {
|
|
108
|
+
return `${cmd}: built dist/${mod} is missing — reinstall the package.`;
|
|
109
|
+
}
|
|
110
|
+
const missingPeer = /Cannot find package '([^']+)'/.exec(String(err?.message ?? ""));
|
|
111
|
+
if (missingPeer) {
|
|
112
|
+
const peer = missingPeer[1];
|
|
113
|
+
return [
|
|
114
|
+
`${cmd}: dist/${mod} is present but cannot load — the "${peer}" package is not`,
|
|
115
|
+
`resolvable from this install (${resolve(__dirname, "..")}).`,
|
|
116
|
+
`Install "${peer}" globally alongside this CLI, or run the CLI with npx from a`,
|
|
117
|
+
`directory where "${peer}" resolves.`,
|
|
118
|
+
].join("\n ");
|
|
119
|
+
}
|
|
120
|
+
return `${cmd}: dist/${mod} failed to load — ${err?.message ?? err}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
95
123
|
function cliVersion() {
|
|
96
124
|
return JSON.parse(readFileSync(resolve(__dirname, "..", "package.json"), "utf8")).version;
|
|
97
125
|
}
|
|
@@ -134,8 +162,8 @@ async function chain(args = []) {
|
|
|
134
162
|
let ca;
|
|
135
163
|
try {
|
|
136
164
|
ca = await import(resolve(__dirname, "..", "dist", "chain-audit.js"));
|
|
137
|
-
} catch {
|
|
138
|
-
console.error("chain
|
|
165
|
+
} catch (err) {
|
|
166
|
+
console.error(distFailure("chain", "chain-audit.js", err));
|
|
139
167
|
process.exit(1);
|
|
140
168
|
}
|
|
141
169
|
|
|
@@ -227,8 +255,8 @@ async function update() {
|
|
|
227
255
|
let uc;
|
|
228
256
|
try {
|
|
229
257
|
uc = await import(resolve(__dirname, "..", "dist", "update-core.js"));
|
|
230
|
-
} catch {
|
|
231
|
-
console.error("update
|
|
258
|
+
} catch (err) {
|
|
259
|
+
console.error(distFailure("update", "update-core.js", err));
|
|
232
260
|
process.exit(1);
|
|
233
261
|
}
|
|
234
262
|
console.log(`\n${BOLD}🦞 multi-clawd update${RESET}\n`);
|
|
@@ -391,8 +419,8 @@ async function explain() {
|
|
|
391
419
|
ec = await import(resolve(__dirname, "..", "dist", "explain-core.js"));
|
|
392
420
|
health = await import(resolve(__dirname, "..", "dist", "health.js"));
|
|
393
421
|
shim = await import(resolve(__dirname, "..", "dist", "shim-core.js"));
|
|
394
|
-
} catch {
|
|
395
|
-
console.error("explain
|
|
422
|
+
} catch (err) {
|
|
423
|
+
console.error(distFailure("explain", "explain-core.js", err));
|
|
396
424
|
process.exit(1);
|
|
397
425
|
}
|
|
398
426
|
let config = {};
|
|
@@ -465,9 +493,12 @@ async function login() {
|
|
|
465
493
|
try {
|
|
466
494
|
lp = await import(resolve(__dirname, "..", "dist", "login-plan.js"));
|
|
467
495
|
ec = await import(resolve(__dirname, "..", "dist", "explain-core.js"));
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
496
|
+
// credential-state.js, NOT index.js: this is the one call login needs from
|
|
497
|
+
// the plugin side, and index.js drags in the `openclaw` peer, which is not
|
|
498
|
+
// resolvable from a global CLI install on every machine.
|
|
499
|
+
idx = await import(resolve(__dirname, "..", "dist", "credential-state.js"));
|
|
500
|
+
} catch (err) {
|
|
501
|
+
console.error(distFailure("login", "login-plan.js", err));
|
|
471
502
|
process.exit(1);
|
|
472
503
|
}
|
|
473
504
|
let config = {};
|