@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 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.0/SETUP-AGENT.md
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 { clearCredentialFailure, mergeHealthStates, parseStoredState, recordCredentialFailure, } from "./shim-core.js";
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"));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "multi-clawd",
3
3
  "name": "multi-clawd",
4
- "version": "1.8.0",
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.0",
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: built dist/ is missing — reinstall the package.");
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: built dist/ is missing — reinstall the package.");
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: built dist/ is missing — reinstall the package.");
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
- idx = await import(resolve(__dirname, "..", "dist", "index.js"));
469
- } catch {
470
- console.error("login: built dist/ is missing reinstall the package.");
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 = {};