@drakon-systems/multi-clawd 1.8.8 → 1.8.10
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/index.js +18 -12
- package/dist/login-health.js +15 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/doctor.mjs +16 -11
package/README.md
CHANGED
|
@@ -369,7 +369,7 @@ openclaw plugins install (Get-Location).Path
|
|
|
369
369
|
**Or let your agent install it.** Running an OpenClaw assistant or Claude
|
|
370
370
|
Code on the target machine already? Paste it this and go make coffee:
|
|
371
371
|
|
|
372
|
-
> Read https://raw.githubusercontent.com/Drakon-Systems-Ltd/multi-clawd/v1.8.
|
|
372
|
+
> Read https://raw.githubusercontent.com/Drakon-Systems-Ltd/multi-clawd/v1.8.10/SETUP-AGENT.md
|
|
373
373
|
> and follow it to set up multi-clawd on this machine. I own a second
|
|
374
374
|
> Claude account — ask me when you need me to log in.
|
|
375
375
|
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { healthStateFile, clearAccountCredentialFailure } from "./credential-sta
|
|
|
18
18
|
export { healthStateFile, clearAccountCredentialFailure };
|
|
19
19
|
import { buildAccountChildEnv, tokenFileModeWarning, validateAccountTokenSources, } from "./account-env.js";
|
|
20
20
|
import { diffCatalogModels, formatNewModelNotice, } from "./model-currency.js";
|
|
21
|
-
import { checkAccountCredential, createRefProbeTracker, } from "./login-health.js";
|
|
21
|
+
import { checkAccountCredential, keychainServiceForConfigDir, createRefProbeTracker, } from "./login-health.js";
|
|
22
22
|
import { execFileSync } from "node:child_process";
|
|
23
23
|
const CLI_FRESH_WATCHDOG_DEFAULTS = {
|
|
24
24
|
noOutputTimeoutRatio: 0.8,
|
|
@@ -151,19 +151,19 @@ const LOGIN_PROBE_INTERVAL_MS = 15 * 60 * 1000;
|
|
|
151
151
|
const LOGIN_PROBE_INITIAL_DELAY_MS = 45 * 1000;
|
|
152
152
|
const realCredentialIo = {
|
|
153
153
|
readFile: (p) => readFileSync(expandHome(p), "utf8"),
|
|
154
|
-
keychainHasClaudeCredentials: () =>
|
|
155
|
-
|
|
156
|
-
execFileSync("security", ["find-generic-password", "-s", "Claude Code-credentials"], {
|
|
157
|
-
stdio: "ignore",
|
|
158
|
-
});
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
catch {
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
},
|
|
154
|
+
keychainHasClaudeCredentials: () => keychainHasService("Claude Code-credentials"),
|
|
155
|
+
keychainHasClaudeCredentialsForDir: (dir) => keychainHasService(keychainServiceForConfigDir(expandHome(dir))),
|
|
165
156
|
platform: process.platform,
|
|
166
157
|
};
|
|
158
|
+
function keychainHasService(service) {
|
|
159
|
+
try {
|
|
160
|
+
execFileSync("security", ["find-generic-password", "-s", service], { stdio: "ignore" });
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
167
|
const refProbeTrackers = new Map();
|
|
168
168
|
const lastProbeStatus = new Map();
|
|
169
169
|
function recordAccountCredentialFailure(accountId, reason, nowMs) {
|
|
@@ -437,6 +437,10 @@ async function buildAccountEnv(account, resolver) {
|
|
|
437
437
|
const token = await resolveTokenAsync(account, resolver);
|
|
438
438
|
return buildAccountChildEnv(account, token, healthStateFile(account.id));
|
|
439
439
|
}
|
|
440
|
+
export function isRuntimelessRegistration(api) {
|
|
441
|
+
const mode = api?.registrationMode;
|
|
442
|
+
return mode === "cli-metadata" || mode === "setup-only";
|
|
443
|
+
}
|
|
440
444
|
function buildCatalogProvider(account) {
|
|
441
445
|
return {
|
|
442
446
|
id: account.id,
|
|
@@ -466,6 +470,8 @@ export default definePluginEntry({
|
|
|
466
470
|
name: "multi-clawd",
|
|
467
471
|
description: "Register additional Claude Code logins as first-class OpenClaw CLI backends for cross-account failover.",
|
|
468
472
|
register(api) {
|
|
473
|
+
if (isRuntimelessRegistration(api))
|
|
474
|
+
return;
|
|
469
475
|
const runtimeConfigLoader = api.runtime?.config?.current
|
|
470
476
|
? () => api.runtime.config.current()
|
|
471
477
|
: undefined;
|
package/dist/login-health.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export function keychainServiceForConfigDir(absDir) {
|
|
3
|
+
const hash = createHash("sha256").update(absDir).digest("hex").slice(0, 8);
|
|
4
|
+
return `Claude Code-credentials-${hash}`;
|
|
5
|
+
}
|
|
1
6
|
export function createRefProbeTracker(options = {}) {
|
|
2
7
|
const deadAfterConsecutive = options.deadAfterConsecutive ?? 3;
|
|
3
8
|
const deadAfterMs = options.deadAfterMs ?? 10 * 60 * 1000;
|
|
@@ -87,7 +92,16 @@ export function checkAccountCredential(account, io) {
|
|
|
87
92
|
return checkCredentialsJson(io, "~/.claude");
|
|
88
93
|
}
|
|
89
94
|
if (account.configDir) {
|
|
90
|
-
|
|
95
|
+
if (io.platform === "darwin" && io.keychainHasClaudeCredentialsForDir(account.configDir)) {
|
|
96
|
+
return { status: "ok" };
|
|
97
|
+
}
|
|
98
|
+
const file = checkCredentialsJson(io, account.configDir);
|
|
99
|
+
if (file.status === "ok" || io.platform !== "darwin")
|
|
100
|
+
return file;
|
|
101
|
+
return {
|
|
102
|
+
status: "broken",
|
|
103
|
+
reason: `keychain has no Claude Code credentials for ${account.configDir} and ${file.reason}`,
|
|
104
|
+
};
|
|
91
105
|
}
|
|
92
106
|
return { status: "unknown" };
|
|
93
107
|
}
|
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.10",
|
|
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.10",
|
|
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/doctor.mjs
CHANGED
|
@@ -280,7 +280,10 @@ try {
|
|
|
280
280
|
|
|
281
281
|
// ── 4. account credentials (values never printed) ──────────────────────────
|
|
282
282
|
console.log("account credentials");
|
|
283
|
-
const { checkAccountCredential } = await importDist("login-health.js", [
|
|
283
|
+
const { checkAccountCredential, keychainServiceForConfigDir } = await importDist("login-health.js", [
|
|
284
|
+
"checkAccountCredential",
|
|
285
|
+
"keychainServiceForConfigDir",
|
|
286
|
+
]);
|
|
284
287
|
const { summarizeWindowUsage, classifyAccountHealth } = await importDist("health.js", [
|
|
285
288
|
"summarizeWindowUsage",
|
|
286
289
|
"classifyAccountHealth",
|
|
@@ -295,18 +298,20 @@ const { resolveAccountIdentity, describeIdentity, findDuplicateLogins, maskEmail
|
|
|
295
298
|
const { decideStickySelection } = await importDist("sticky.js", ["decideStickySelection"]);
|
|
296
299
|
const io = {
|
|
297
300
|
readFile: (p) => readFileSync(expandHome(p), "utf8"),
|
|
298
|
-
keychainHasClaudeCredentials: () =>
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
stdio: "ignore",
|
|
302
|
-
});
|
|
303
|
-
return true;
|
|
304
|
-
} catch {
|
|
305
|
-
return false;
|
|
306
|
-
}
|
|
307
|
-
},
|
|
301
|
+
keychainHasClaudeCredentials: () => keychainHasService("Claude Code-credentials"),
|
|
302
|
+
keychainHasClaudeCredentialsForDir: (dir) =>
|
|
303
|
+
keychainHasService(keychainServiceForConfigDir(expandHome(dir))),
|
|
308
304
|
platform: process.platform,
|
|
309
305
|
};
|
|
306
|
+
// Metadata-only probe (no `-w`): the secret is never read.
|
|
307
|
+
function keychainHasService(service) {
|
|
308
|
+
try {
|
|
309
|
+
execFileSync("security", ["find-generic-password", "-s", service], { stdio: "ignore" });
|
|
310
|
+
return true;
|
|
311
|
+
} catch {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
310
315
|
// A native account's child sets no CLAUDE_CONFIG_DIR, so it authenticates
|
|
311
316
|
// against whatever the default dir is in the ENV THE GATEWAY RUNS IN. Doctor
|
|
312
317
|
// reads the same variable and prints the path it used, so a box that exports
|