@shadowob/connector 1.1.6 → 1.1.7
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 +22 -2
- package/dist/cli.js +650 -22
- package/dist/index.cjs +10 -5
- package/dist/index.js +10 -5
- package/package.json +2 -1
- package/skills/shadowob/SKILL.md +451 -0
package/dist/cli.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
5
|
-
import { cpSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
5
|
+
import { chmodSync as chmodSync2, cpSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
6
6
|
import { homedir as homedir2 } from "os";
|
|
7
7
|
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
|
+
import { parse as parseToml2 } from "smol-toml";
|
|
10
|
+
import { parse as parseYaml2 } from "yaml";
|
|
9
11
|
|
|
10
12
|
// src/cc-connect-installer.ts
|
|
11
13
|
import { execFileSync, spawnSync } from "child_process";
|
|
@@ -249,6 +251,15 @@ function binaryLooksUsable(path) {
|
|
|
249
251
|
return false;
|
|
250
252
|
}
|
|
251
253
|
}
|
|
254
|
+
function getCcConnectBinaryStatus() {
|
|
255
|
+
const override = process.env.SHADOW_CC_CONNECT_BIN?.trim();
|
|
256
|
+
if (override) {
|
|
257
|
+
const binaryPath2 = expandHome(override);
|
|
258
|
+
return { binaryPath: binaryPath2, usable: binaryLooksUsable(binaryPath2), source: "env" };
|
|
259
|
+
}
|
|
260
|
+
const binaryPath = cachedBinaryPath();
|
|
261
|
+
return { binaryPath, usable: binaryLooksUsable(binaryPath), source: "cache" };
|
|
262
|
+
}
|
|
252
263
|
async function ensureCcConnectFork(options) {
|
|
253
264
|
const override = process.env.SHADOW_CC_CONNECT_BIN?.trim();
|
|
254
265
|
if (override) {
|
|
@@ -522,7 +533,7 @@ function buildOpenClawPlan(input) {
|
|
|
522
533
|
return {
|
|
523
534
|
target: "openclaw",
|
|
524
535
|
title: "OpenClaw",
|
|
525
|
-
summary: "Install the Shadow channel plugin and
|
|
536
|
+
summary: "Install the Shadow channel plugin, Shadow CLI bin/skills, and a Buddy CLI profile for OpenClaw.",
|
|
526
537
|
connectCommand,
|
|
527
538
|
quickCommand,
|
|
528
539
|
commands,
|
|
@@ -533,6 +544,9 @@ function buildOpenClawPlan(input) {
|
|
|
533
544
|
`Shadow server URL: ${serverUrl}`,
|
|
534
545
|
`Buddy token: ${token}`,
|
|
535
546
|
"",
|
|
547
|
+
`Preferred one-line setup: ${connectCommand}`,
|
|
548
|
+
"The connector installs/configures the Shadow CLI, official Shadow skill files, and the Buddy profile before applying the OpenClaw channel config.",
|
|
549
|
+
"",
|
|
536
550
|
"Run these steps in order:",
|
|
537
551
|
...commands.map((item, index) => `${index + 1}. ${item.command}`),
|
|
538
552
|
"",
|
|
@@ -617,7 +631,7 @@ function buildHermesPlan(input) {
|
|
|
617
631
|
return {
|
|
618
632
|
target: "hermes",
|
|
619
633
|
title: "Hermes Agent",
|
|
620
|
-
summary: "Install the ShadowOB Hermes platform plugin
|
|
634
|
+
summary: "Install the ShadowOB Hermes platform plugin, Shadow CLI bin/skills, and a Buddy CLI profile.",
|
|
621
635
|
connectCommand,
|
|
622
636
|
quickCommand: commands.map((item) => item.command).join(" && "),
|
|
623
637
|
commands,
|
|
@@ -631,7 +645,8 @@ function buildHermesPlan(input) {
|
|
|
631
645
|
`Shadow server URL: ${serverUrl}`,
|
|
632
646
|
`Buddy token: ${token}`,
|
|
633
647
|
"",
|
|
634
|
-
|
|
648
|
+
`Preferred one-line setup: ${connectCommand}`,
|
|
649
|
+
"The connector installs/configures the Shadow CLI, official Shadow skill files, and the Buddy profile before writing Hermes config. The plugin resolves the Buddy agent id and channel policy from Shadow at runtime."
|
|
635
650
|
].join("\n"),
|
|
636
651
|
docsUrl: "https://hermes-agent.nousresearch.com/docs/user-guide/messaging",
|
|
637
652
|
capabilities: [
|
|
@@ -709,7 +724,7 @@ function buildCcConnectPlan(input) {
|
|
|
709
724
|
return {
|
|
710
725
|
target: "cc-connect",
|
|
711
726
|
title: "cc-connect",
|
|
712
|
-
summary: `Use ${CC_CONNECT_FORK_REPO}@${CC_CONNECT_FORK_SHORT_REF} with ShadowOB Socket.IO
|
|
727
|
+
summary: `Use ${CC_CONNECT_FORK_REPO}@${CC_CONNECT_FORK_SHORT_REF} with ShadowOB Socket.IO support, Shadow CLI bin/skills, and a Buddy CLI profile.`,
|
|
713
728
|
connectCommand: startCommand,
|
|
714
729
|
quickCommand: startCommand,
|
|
715
730
|
commands,
|
|
@@ -722,7 +737,8 @@ function buildCcConnectPlan(input) {
|
|
|
722
737
|
`Project work_dir: ${workDir}`,
|
|
723
738
|
`Agent type: ${agentType}`,
|
|
724
739
|
"",
|
|
725
|
-
`
|
|
740
|
+
`Preferred one-line setup: ${startCommand}`,
|
|
741
|
+
`Install ${CC_CONNECT_FORK_REPO}@${CC_CONNECT_FORK_SHORT_REF}, install/configure the Shadow CLI and official Shadow skill files, add the TOML platform block, and start cc-connect.`
|
|
726
742
|
].join("\n"),
|
|
727
743
|
docsUrl: CC_CONNECT_FORK_DOCS_URL,
|
|
728
744
|
capabilities: [
|
|
@@ -752,6 +768,10 @@ function createConnectorPlan(input) {
|
|
|
752
768
|
|
|
753
769
|
// src/cli.ts
|
|
754
770
|
var TARGETS = /* @__PURE__ */ new Set(["openclaw", "hermes", "cc-connect"]);
|
|
771
|
+
var COMMANDS = /* @__PURE__ */ new Set(["plan", "connect", "update", "doctor", "fix", "status", "scan"]);
|
|
772
|
+
var ALL_TARGETS = ["openclaw", "hermes", "cc-connect"];
|
|
773
|
+
var SHADOW_CLI_PACKAGE = "@shadowob/cli@latest";
|
|
774
|
+
var SHADOW_CONNECTOR_PACKAGE = "@shadowob/connector@latest";
|
|
755
775
|
function readOption(args, name) {
|
|
756
776
|
const prefix = `${name}=`;
|
|
757
777
|
const inline = args.find((arg) => arg.startsWith(prefix));
|
|
@@ -768,8 +788,14 @@ function usage() {
|
|
|
768
788
|
"Usage:",
|
|
769
789
|
" shadowob-connector plan --target <openclaw|hermes|cc-connect> --server-url <url> --token <token>",
|
|
770
790
|
" shadowob-connector connect --target <openclaw|hermes|cc-connect> --server-url <url> --token <token>",
|
|
791
|
+
" shadowob-connector update --target <openclaw|hermes|cc-connect> --server-url <url> --token <token>",
|
|
792
|
+
" shadowob-connector fix --target <openclaw|hermes|cc-connect> --server-url <url> --token <token>",
|
|
793
|
+
" shadowob-connector scan [--target <openclaw|hermes|cc-connect>] [--server-url <url>] [--token <token>]",
|
|
794
|
+
" shadowob-connector doctor [--target <openclaw|hermes|cc-connect>]",
|
|
795
|
+
" shadowob-connector status [--target <openclaw|hermes|cc-connect>]",
|
|
771
796
|
"",
|
|
772
797
|
"Options:",
|
|
798
|
+
" --server-url <url> Shadow server URL, default https://shadowob.com",
|
|
773
799
|
" --openclaw-config <path> OpenClaw JSON config, default $OPENCLAW_CONFIG or ~/.shadowob/openclaw.json",
|
|
774
800
|
" --hermes-home <path> Hermes config directory, default $HERMES_HOME or ~/.hermes",
|
|
775
801
|
" --work-dir <path> cc-connect project work directory",
|
|
@@ -777,25 +803,33 @@ function usage() {
|
|
|
777
803
|
" --agent-type <type> cc-connect agent type, default codex",
|
|
778
804
|
" --json Print the full plan as JSON",
|
|
779
805
|
" --force Overwrite target config files when needed",
|
|
780
|
-
" --install Install
|
|
781
|
-
" --no-install Skip
|
|
806
|
+
" --install Install connector runtime dependencies",
|
|
807
|
+
" --no-install Skip connector runtime dependency installation",
|
|
782
808
|
" --start Start Hermes gateway or cc-connect after setup",
|
|
783
809
|
" --dry-run Show what would be applied without changing files"
|
|
784
810
|
].join("\n");
|
|
785
811
|
}
|
|
812
|
+
function requireTarget(options) {
|
|
813
|
+
if (!options.target) throw new Error("Missing or invalid --target");
|
|
814
|
+
return options.target;
|
|
815
|
+
}
|
|
786
816
|
function parseArgs(args) {
|
|
787
817
|
if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
|
|
788
818
|
console.log(usage());
|
|
789
819
|
process.exit(0);
|
|
790
820
|
}
|
|
791
821
|
const commandArg = args[0];
|
|
792
|
-
const
|
|
793
|
-
const
|
|
822
|
+
const hasCommand = commandArg ? COMMANDS.has(commandArg) : false;
|
|
823
|
+
const command = hasCommand ? commandArg : "plan";
|
|
824
|
+
const optionArgs = hasCommand ? args.slice(1) : args;
|
|
794
825
|
const target = readOption(optionArgs, "--target");
|
|
795
|
-
if (
|
|
826
|
+
if (target && !TARGETS.has(target)) {
|
|
827
|
+
throw new Error("Missing or invalid --target");
|
|
828
|
+
}
|
|
829
|
+
if (!target && command !== "doctor" && command !== "status" && command !== "scan") {
|
|
796
830
|
throw new Error("Missing or invalid --target");
|
|
797
831
|
}
|
|
798
|
-
const install = target === "cc-connect" ? hasFlag(optionArgs, "--install") : !hasFlag(optionArgs, "--no-install");
|
|
832
|
+
const install = command === "fix" || command === "update" ? !hasFlag(optionArgs, "--no-install") : target === "cc-connect" ? hasFlag(optionArgs, "--install") : !hasFlag(optionArgs, "--no-install");
|
|
799
833
|
return {
|
|
800
834
|
command,
|
|
801
835
|
target,
|
|
@@ -814,7 +848,8 @@ function parseArgs(args) {
|
|
|
814
848
|
};
|
|
815
849
|
}
|
|
816
850
|
function printPlan(options) {
|
|
817
|
-
const
|
|
851
|
+
const target = requireTarget(options);
|
|
852
|
+
const plan = createConnectorPlan({ ...options, target });
|
|
818
853
|
if (options.json) {
|
|
819
854
|
console.log(JSON.stringify(plan, null, 2));
|
|
820
855
|
return;
|
|
@@ -873,6 +908,570 @@ function normalizeServerUrl2(value) {
|
|
|
873
908
|
const trimmed = value.trim() || "https://shadowob.com";
|
|
874
909
|
return trimmed.endsWith("/api") ? trimmed.slice(0, -4) : trimmed.replace(/\/$/, "");
|
|
875
910
|
}
|
|
911
|
+
function shellQuote2(value) {
|
|
912
|
+
if (!value) return "''";
|
|
913
|
+
if (/^[A-Za-z0-9_./:@=-]+$/.test(value)) return value;
|
|
914
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
915
|
+
}
|
|
916
|
+
function tokenForCommand(options) {
|
|
917
|
+
return options.token.trim() || "<BUDDY_TOKEN>";
|
|
918
|
+
}
|
|
919
|
+
function connectorCommand(command, target, options, extras = []) {
|
|
920
|
+
const parts = ["shadowob-connector", command, "--target", target];
|
|
921
|
+
if (command !== "doctor" && command !== "status") {
|
|
922
|
+
parts.push(
|
|
923
|
+
"--server-url",
|
|
924
|
+
normalizeServerUrl2(options.serverUrl),
|
|
925
|
+
"--token",
|
|
926
|
+
tokenForCommand(options)
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
parts.push(...extras);
|
|
930
|
+
return parts.map(shellQuote2).join(" ");
|
|
931
|
+
}
|
|
932
|
+
function commandExists(command) {
|
|
933
|
+
const result = spawnSync2(command, ["--version"], { stdio: "ignore" });
|
|
934
|
+
return result.status === 0;
|
|
935
|
+
}
|
|
936
|
+
function writeExecutable(path, content, dryRun) {
|
|
937
|
+
writeFile(path, content, dryRun);
|
|
938
|
+
if (dryRun) return;
|
|
939
|
+
chmodSync2(path, 493);
|
|
940
|
+
}
|
|
941
|
+
function ensureNpxShim(options) {
|
|
942
|
+
if (commandExists(options.command)) return;
|
|
943
|
+
const localBin = resolve2(homedir2(), ".local/bin");
|
|
944
|
+
const target = resolve2(localBin, options.command);
|
|
945
|
+
const content = [
|
|
946
|
+
"#!/usr/bin/env sh",
|
|
947
|
+
`exec npx -y ${options.packageSpec} ${options.binaryName === options.command ? "" : options.binaryName} "$@"`,
|
|
948
|
+
""
|
|
949
|
+
].join("\n").replace(' "$@"', ' "$@"');
|
|
950
|
+
console.log(`Applying: Install ${options.command} shim ${target}`);
|
|
951
|
+
writeExecutable(target, content, options.dryRun);
|
|
952
|
+
const pathEntries = (process.env.PATH ?? "").split(":");
|
|
953
|
+
if (!pathEntries.includes(localBin)) {
|
|
954
|
+
console.log(`Note: add ${localBin} to PATH so agents can run ${options.command}`);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function shadowCliProfileName(options) {
|
|
958
|
+
return options.projectName?.trim() || "shadow-buddy";
|
|
959
|
+
}
|
|
960
|
+
function writeShadowCliProfile(options) {
|
|
961
|
+
const configPath = resolve2(homedir2(), ".shadowob/shadowob.config.json");
|
|
962
|
+
const current = (() => {
|
|
963
|
+
try {
|
|
964
|
+
return JSON.parse(readExisting(configPath));
|
|
965
|
+
} catch {
|
|
966
|
+
return {};
|
|
967
|
+
}
|
|
968
|
+
})();
|
|
969
|
+
const profileName = shadowCliProfileName(options);
|
|
970
|
+
const next = {
|
|
971
|
+
...current,
|
|
972
|
+
profiles: {
|
|
973
|
+
...current.profiles ?? {},
|
|
974
|
+
[profileName]: {
|
|
975
|
+
serverUrl: normalizeServerUrl2(options.serverUrl),
|
|
976
|
+
token: options.token
|
|
977
|
+
}
|
|
978
|
+
},
|
|
979
|
+
currentProfile: profileName
|
|
980
|
+
};
|
|
981
|
+
console.log(`Applying: Configure Shadow CLI profile ${profileName}`);
|
|
982
|
+
writeFile(configPath, JSON.stringify(next, null, 2), options.dryRun);
|
|
983
|
+
}
|
|
984
|
+
function shadowobSkillMarkdown() {
|
|
985
|
+
const candidates = [
|
|
986
|
+
resolve2(packageRoot(), "skills/shadowob/SKILL.md"),
|
|
987
|
+
resolve2(process.cwd(), "skills/shadowob-cli/SKILL.md"),
|
|
988
|
+
resolve2(process.cwd(), "packages/openclaw-shadowob/skills/shadowob/SKILL.md")
|
|
989
|
+
];
|
|
990
|
+
let currentDir = packageRoot();
|
|
991
|
+
while (true) {
|
|
992
|
+
candidates.push(resolve2(currentDir, "skills/shadowob-cli/SKILL.md"));
|
|
993
|
+
const parentDir = dirname2(currentDir);
|
|
994
|
+
if (parentDir === currentDir) break;
|
|
995
|
+
currentDir = parentDir;
|
|
996
|
+
}
|
|
997
|
+
const found = candidates.find((candidate) => existsSync2(candidate));
|
|
998
|
+
if (!found) throw new Error("Cannot find bundled Shadow CLI skill");
|
|
999
|
+
return readFileSync(found, "utf8");
|
|
1000
|
+
}
|
|
1001
|
+
function shadowobSkillTargets(options) {
|
|
1002
|
+
const hermesDir = expandHome2(options.hermesHome ?? process.env.HERMES_HOME ?? "~/.hermes");
|
|
1003
|
+
return Array.from(
|
|
1004
|
+
/* @__PURE__ */ new Set([
|
|
1005
|
+
resolve2(homedir2(), ".shadowob/skills/shadowob/SKILL.md"),
|
|
1006
|
+
resolve2(homedir2(), ".agents/skills/shadowob/SKILL.md"),
|
|
1007
|
+
resolve2(homedir2(), ".codex/skills/shadowob/SKILL.md"),
|
|
1008
|
+
resolve2(homedir2(), ".claude/skills/shadowob/SKILL.md"),
|
|
1009
|
+
resolve2(homedir2(), ".gemini/skills/shadowob/SKILL.md"),
|
|
1010
|
+
resolve2(homedir2(), ".opencode/skills/shadowob/SKILL.md"),
|
|
1011
|
+
resolve2(homedir2(), ".openclaw/skills/shadowob/SKILL.md"),
|
|
1012
|
+
resolve2(hermesDir, "skills/shadowob/SKILL.md")
|
|
1013
|
+
])
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
function installShadowCliAndSkills(options) {
|
|
1017
|
+
ensureNpxShim({
|
|
1018
|
+
command: "shadowob",
|
|
1019
|
+
packageSpec: SHADOW_CLI_PACKAGE,
|
|
1020
|
+
binaryName: "shadowob",
|
|
1021
|
+
dryRun: options.dryRun
|
|
1022
|
+
});
|
|
1023
|
+
ensureNpxShim({
|
|
1024
|
+
command: "shadowob-connector",
|
|
1025
|
+
packageSpec: SHADOW_CONNECTOR_PACKAGE,
|
|
1026
|
+
binaryName: "shadowob-connector",
|
|
1027
|
+
dryRun: options.dryRun
|
|
1028
|
+
});
|
|
1029
|
+
const skill = shadowobSkillMarkdown();
|
|
1030
|
+
for (const target of shadowobSkillTargets(options)) {
|
|
1031
|
+
console.log(`Applying: Install Shadow skill ${target}`);
|
|
1032
|
+
writeFile(target, skill, options.dryRun);
|
|
1033
|
+
}
|
|
1034
|
+
writeShadowCliProfile(options);
|
|
1035
|
+
}
|
|
1036
|
+
function check(target, status, label, detail, fix) {
|
|
1037
|
+
return { target, status, label, detail, fix };
|
|
1038
|
+
}
|
|
1039
|
+
function selectedTargets(options) {
|
|
1040
|
+
return options.target ? [options.target] : [...ALL_TARGETS];
|
|
1041
|
+
}
|
|
1042
|
+
function parseJsonFile(path, label) {
|
|
1043
|
+
try {
|
|
1044
|
+
const content = readExisting(path);
|
|
1045
|
+
if (!content.trim()) return { error: `${label} config is empty` };
|
|
1046
|
+
const parsed = JSON.parse(content);
|
|
1047
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1048
|
+
return { error: `${label} config must be an object` };
|
|
1049
|
+
}
|
|
1050
|
+
return { value: parsed };
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
function asObject(value) {
|
|
1056
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1057
|
+
}
|
|
1058
|
+
function diagnoseCommon(options) {
|
|
1059
|
+
const checks = [
|
|
1060
|
+
check(
|
|
1061
|
+
"common",
|
|
1062
|
+
commandExists("shadowob") ? "ok" : "warn",
|
|
1063
|
+
"Shadow CLI command",
|
|
1064
|
+
commandExists("shadowob") ? "shadowob is on PATH" : "shadowob is not on PATH",
|
|
1065
|
+
"Run fix/update to install the ~/.local/bin/shadowob shim."
|
|
1066
|
+
),
|
|
1067
|
+
check(
|
|
1068
|
+
"common",
|
|
1069
|
+
commandExists("shadowob-connector") ? "ok" : "warn",
|
|
1070
|
+
"Connector command",
|
|
1071
|
+
commandExists("shadowob-connector") ? "shadowob-connector is on PATH" : "shadowob-connector is not on PATH",
|
|
1072
|
+
"Run fix/update to install the ~/.local/bin/shadowob-connector shim."
|
|
1073
|
+
)
|
|
1074
|
+
];
|
|
1075
|
+
const profilePath = resolve2(homedir2(), ".shadowob/shadowob.config.json");
|
|
1076
|
+
if (!existsSync2(profilePath)) {
|
|
1077
|
+
checks.push(
|
|
1078
|
+
check(
|
|
1079
|
+
"common",
|
|
1080
|
+
"warn",
|
|
1081
|
+
"Shadow CLI profile",
|
|
1082
|
+
`${profilePath} does not exist`,
|
|
1083
|
+
"Run fix/update with --token to write the Buddy profile."
|
|
1084
|
+
)
|
|
1085
|
+
);
|
|
1086
|
+
} else {
|
|
1087
|
+
const parsed = parseJsonFile(profilePath, "Shadow CLI");
|
|
1088
|
+
const profiles = asObject(parsed.value?.profiles);
|
|
1089
|
+
const profileName = shadowCliProfileName(options);
|
|
1090
|
+
checks.push(
|
|
1091
|
+
check(
|
|
1092
|
+
"common",
|
|
1093
|
+
parsed.error ? "fail" : profiles[profileName] ? "ok" : "warn",
|
|
1094
|
+
"Shadow CLI profile",
|
|
1095
|
+
parsed.error ?? (profiles[profileName] ? `profile ${profileName} exists` : `profile ${profileName} is missing`),
|
|
1096
|
+
"Run fix/update with --token to write the Buddy profile."
|
|
1097
|
+
)
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
const skillTargets = shadowobSkillTargets(options);
|
|
1101
|
+
const installed = skillTargets.filter((target) => existsSync2(target)).length;
|
|
1102
|
+
checks.push(
|
|
1103
|
+
check(
|
|
1104
|
+
"common",
|
|
1105
|
+
installed > 0 ? "ok" : "warn",
|
|
1106
|
+
"Shadow skill files",
|
|
1107
|
+
`${installed}/${skillTargets.length} common skill locations contain shadowob/SKILL.md`,
|
|
1108
|
+
"Run fix/update to install the official Shadow skill files."
|
|
1109
|
+
)
|
|
1110
|
+
);
|
|
1111
|
+
return checks;
|
|
1112
|
+
}
|
|
1113
|
+
function diagnoseOpenClaw(options) {
|
|
1114
|
+
const configPath = expandHome2(
|
|
1115
|
+
options.openclawConfig ?? process.env.OPENCLAW_CONFIG ?? "~/.shadowob/openclaw.json"
|
|
1116
|
+
);
|
|
1117
|
+
const checks = [
|
|
1118
|
+
check(
|
|
1119
|
+
"openclaw",
|
|
1120
|
+
commandExists("openclaw") ? "ok" : "warn",
|
|
1121
|
+
"OpenClaw command",
|
|
1122
|
+
commandExists("openclaw") ? "openclaw is on PATH" : "openclaw is not on PATH",
|
|
1123
|
+
"Install OpenClaw before starting the gateway."
|
|
1124
|
+
)
|
|
1125
|
+
];
|
|
1126
|
+
if (!existsSync2(configPath)) {
|
|
1127
|
+
checks.push(
|
|
1128
|
+
check(
|
|
1129
|
+
"openclaw",
|
|
1130
|
+
"fail",
|
|
1131
|
+
"OpenClaw config",
|
|
1132
|
+
`${configPath} does not exist`,
|
|
1133
|
+
"Run fix/update."
|
|
1134
|
+
)
|
|
1135
|
+
);
|
|
1136
|
+
return checks;
|
|
1137
|
+
}
|
|
1138
|
+
const parsed = parseJsonFile(configPath, "OpenClaw");
|
|
1139
|
+
if (parsed.error) {
|
|
1140
|
+
checks.push(
|
|
1141
|
+
check(
|
|
1142
|
+
"openclaw",
|
|
1143
|
+
"fail",
|
|
1144
|
+
"OpenClaw config",
|
|
1145
|
+
parsed.error,
|
|
1146
|
+
"Fix the JSON or run fix/update with --force."
|
|
1147
|
+
)
|
|
1148
|
+
);
|
|
1149
|
+
return checks;
|
|
1150
|
+
}
|
|
1151
|
+
const root = parsed.value ?? {};
|
|
1152
|
+
const channels = asObject(root.channels);
|
|
1153
|
+
const shadow = asObject(channels.shadowob);
|
|
1154
|
+
const plugins = asObject(root.plugins);
|
|
1155
|
+
const pluginEntries = asObject(plugins.entries);
|
|
1156
|
+
checks.push(
|
|
1157
|
+
check(
|
|
1158
|
+
"openclaw",
|
|
1159
|
+
typeof shadow.token === "string" && shadow.token.length > 0 ? "ok" : "fail",
|
|
1160
|
+
"OpenClaw Shadow token",
|
|
1161
|
+
typeof shadow.token === "string" && shadow.token.length > 0 ? "channels.shadowob.token is set" : "channels.shadowob.token is missing",
|
|
1162
|
+
"Run fix/update with --token."
|
|
1163
|
+
),
|
|
1164
|
+
check(
|
|
1165
|
+
"openclaw",
|
|
1166
|
+
typeof shadow.serverUrl === "string" && shadow.serverUrl.length > 0 ? "ok" : "fail",
|
|
1167
|
+
"OpenClaw Shadow server URL",
|
|
1168
|
+
typeof shadow.serverUrl === "string" && shadow.serverUrl.length > 0 ? `channels.shadowob.serverUrl=${shadow.serverUrl}` : "channels.shadowob.serverUrl is missing",
|
|
1169
|
+
"Run fix/update with --server-url."
|
|
1170
|
+
),
|
|
1171
|
+
check(
|
|
1172
|
+
"openclaw",
|
|
1173
|
+
asObject(pluginEntries["openclaw-shadowob"]).enabled === true ? "ok" : "warn",
|
|
1174
|
+
"OpenClaw Shadow plugin entry",
|
|
1175
|
+
asObject(pluginEntries["openclaw-shadowob"]).enabled === true ? "openclaw-shadowob plugin entry is enabled" : "openclaw-shadowob plugin entry is missing or disabled",
|
|
1176
|
+
"Run fix/update."
|
|
1177
|
+
)
|
|
1178
|
+
);
|
|
1179
|
+
return checks;
|
|
1180
|
+
}
|
|
1181
|
+
function diagnoseHermes(options) {
|
|
1182
|
+
const hermesDir = expandHome2(options.hermesHome ?? process.env.HERMES_HOME ?? "~/.hermes");
|
|
1183
|
+
const pluginTarget = resolve2(hermesDir, "plugins/shadowob");
|
|
1184
|
+
const envPath = resolve2(hermesDir, ".env");
|
|
1185
|
+
const configPath = resolve2(hermesDir, "config.yaml");
|
|
1186
|
+
const checks = [
|
|
1187
|
+
check(
|
|
1188
|
+
"hermes",
|
|
1189
|
+
commandExists("hermes") ? "ok" : "warn",
|
|
1190
|
+
"Hermes command",
|
|
1191
|
+
commandExists("hermes") ? "hermes is on PATH" : "hermes is not on PATH",
|
|
1192
|
+
"Install Hermes before starting the gateway."
|
|
1193
|
+
),
|
|
1194
|
+
check(
|
|
1195
|
+
"hermes",
|
|
1196
|
+
existsSync2(pluginTarget) ? "ok" : "fail",
|
|
1197
|
+
"Hermes Shadow plugin",
|
|
1198
|
+
existsSync2(pluginTarget) ? `${pluginTarget} exists` : `${pluginTarget} is missing`,
|
|
1199
|
+
"Run fix/update."
|
|
1200
|
+
)
|
|
1201
|
+
];
|
|
1202
|
+
const env = readExisting(envPath);
|
|
1203
|
+
checks.push(
|
|
1204
|
+
check(
|
|
1205
|
+
"hermes",
|
|
1206
|
+
env.includes("SHADOW_TOKEN=") && env.includes("SHADOW_BASE_URL=") ? "ok" : "fail",
|
|
1207
|
+
"Hermes environment",
|
|
1208
|
+
existsSync2(envPath) ? "SHADOW_TOKEN and SHADOW_BASE_URL are present" : `${envPath} does not exist`,
|
|
1209
|
+
"Run fix/update with --token and --server-url."
|
|
1210
|
+
)
|
|
1211
|
+
);
|
|
1212
|
+
if (!existsSync2(configPath)) {
|
|
1213
|
+
checks.push(
|
|
1214
|
+
check("hermes", "fail", "Hermes config", `${configPath} does not exist`, "Run fix/update.")
|
|
1215
|
+
);
|
|
1216
|
+
return checks;
|
|
1217
|
+
}
|
|
1218
|
+
try {
|
|
1219
|
+
const parsed = parseYaml2(readExisting(configPath));
|
|
1220
|
+
const root = asObject(parsed);
|
|
1221
|
+
const shadow = asObject(asObject(root.platforms).shadowob);
|
|
1222
|
+
checks.push(
|
|
1223
|
+
check(
|
|
1224
|
+
"hermes",
|
|
1225
|
+
shadow.enabled === true && typeof shadow.token === "string" ? "ok" : "fail",
|
|
1226
|
+
"Hermes Shadow platform",
|
|
1227
|
+
shadow.enabled === true && typeof shadow.token === "string" ? "platforms.shadowob is enabled" : "platforms.shadowob is missing token or enabled=true",
|
|
1228
|
+
"Run fix/update."
|
|
1229
|
+
)
|
|
1230
|
+
);
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
checks.push(
|
|
1233
|
+
check(
|
|
1234
|
+
"hermes",
|
|
1235
|
+
"fail",
|
|
1236
|
+
"Hermes config",
|
|
1237
|
+
error instanceof Error ? error.message : String(error),
|
|
1238
|
+
"Fix the YAML or run fix/update with --force."
|
|
1239
|
+
)
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
return checks;
|
|
1243
|
+
}
|
|
1244
|
+
function diagnoseCcConnect(options) {
|
|
1245
|
+
const configPath = resolve2(homedir2(), ".cc-connect/config.toml");
|
|
1246
|
+
const binary = getCcConnectBinaryStatus();
|
|
1247
|
+
const checks = [
|
|
1248
|
+
check(
|
|
1249
|
+
"cc-connect",
|
|
1250
|
+
binary.usable ? "ok" : "warn",
|
|
1251
|
+
"cc-connect Shadow fork",
|
|
1252
|
+
binary.usable ? `${binary.binaryPath} passes version check` : `${binary.binaryPath} is missing or does not match the pinned Shadow fork`,
|
|
1253
|
+
"Run fix/update with --install."
|
|
1254
|
+
)
|
|
1255
|
+
];
|
|
1256
|
+
if (!existsSync2(configPath)) {
|
|
1257
|
+
checks.push(
|
|
1258
|
+
check(
|
|
1259
|
+
"cc-connect",
|
|
1260
|
+
"fail",
|
|
1261
|
+
"cc-connect config",
|
|
1262
|
+
`${configPath} does not exist`,
|
|
1263
|
+
"Run fix/update."
|
|
1264
|
+
)
|
|
1265
|
+
);
|
|
1266
|
+
return checks;
|
|
1267
|
+
}
|
|
1268
|
+
try {
|
|
1269
|
+
const root = parseToml2(readExisting(configPath));
|
|
1270
|
+
const projects = Array.isArray(root.projects) ? root.projects : [];
|
|
1271
|
+
const projectName = options.projectName?.trim() || "shadow-buddy";
|
|
1272
|
+
const workDir = options.workDir?.trim() || ".";
|
|
1273
|
+
const project = projects.find((item) => asObject(item).name === projectName) ?? projects.find((item) => asObject(asObject(asObject(item).agent).options).work_dir === workDir);
|
|
1274
|
+
const projectPlatforms = asObject(project).platforms;
|
|
1275
|
+
const platforms = Array.isArray(projectPlatforms) ? projectPlatforms : [];
|
|
1276
|
+
const shadow = platforms.find((item) => asObject(item).type === "shadowob");
|
|
1277
|
+
const shadowOptions = asObject(asObject(shadow).options);
|
|
1278
|
+
checks.push(
|
|
1279
|
+
check(
|
|
1280
|
+
"cc-connect",
|
|
1281
|
+
project ? "ok" : "fail",
|
|
1282
|
+
"cc-connect project",
|
|
1283
|
+
project ? `project ${projectName} is configured` : `project ${projectName} is missing`,
|
|
1284
|
+
"Run fix/update with --project-name and --work-dir."
|
|
1285
|
+
),
|
|
1286
|
+
check(
|
|
1287
|
+
"cc-connect",
|
|
1288
|
+
typeof shadowOptions.token === "string" && typeof shadowOptions.server_url === "string" ? "ok" : "fail",
|
|
1289
|
+
"cc-connect Shadow platform",
|
|
1290
|
+
typeof shadowOptions.token === "string" && typeof shadowOptions.server_url === "string" ? "shadowob platform has token and server_url" : "shadowob platform is missing token or server_url",
|
|
1291
|
+
"Run fix/update with --token and --server-url."
|
|
1292
|
+
)
|
|
1293
|
+
);
|
|
1294
|
+
} catch (error) {
|
|
1295
|
+
checks.push(
|
|
1296
|
+
check(
|
|
1297
|
+
"cc-connect",
|
|
1298
|
+
"fail",
|
|
1299
|
+
"cc-connect config",
|
|
1300
|
+
error instanceof Error ? error.message : String(error),
|
|
1301
|
+
"Fix the TOML or run fix/update with --force."
|
|
1302
|
+
)
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
return checks;
|
|
1306
|
+
}
|
|
1307
|
+
function diagnostics(options) {
|
|
1308
|
+
const checks = diagnoseCommon(options);
|
|
1309
|
+
for (const target of selectedTargets(options)) {
|
|
1310
|
+
if (target === "openclaw") checks.push(...diagnoseOpenClaw(options));
|
|
1311
|
+
if (target === "hermes") checks.push(...diagnoseHermes(options));
|
|
1312
|
+
if (target === "cc-connect") checks.push(...diagnoseCcConnect(options));
|
|
1313
|
+
}
|
|
1314
|
+
return checks;
|
|
1315
|
+
}
|
|
1316
|
+
function printDiagnostics(options, mode) {
|
|
1317
|
+
const checks = diagnostics(options);
|
|
1318
|
+
if (options.json) {
|
|
1319
|
+
console.log(
|
|
1320
|
+
JSON.stringify({ ok: !checks.some((item) => item.status === "fail"), checks }, null, 2)
|
|
1321
|
+
);
|
|
1322
|
+
return !checks.some((item) => item.status === "fail");
|
|
1323
|
+
}
|
|
1324
|
+
console.log(`# Connector ${mode}`);
|
|
1325
|
+
for (const item of checks) {
|
|
1326
|
+
const marker = item.status === "ok" ? "OK" : item.status === "warn" ? "WARN" : "FAIL";
|
|
1327
|
+
console.log(
|
|
1328
|
+
`[${marker}] ${item.target}: ${item.label}${item.detail ? ` - ${item.detail}` : ""}`
|
|
1329
|
+
);
|
|
1330
|
+
if (mode === "doctor" && item.status !== "ok" && item.fix) {
|
|
1331
|
+
console.log(` fix: ${item.fix}`);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return !checks.some((item) => item.status === "fail");
|
|
1335
|
+
}
|
|
1336
|
+
function firstExistingPath(paths) {
|
|
1337
|
+
return paths.find((path) => existsSync2(path));
|
|
1338
|
+
}
|
|
1339
|
+
function openClawConfigCandidates(options) {
|
|
1340
|
+
return Array.from(
|
|
1341
|
+
new Set(
|
|
1342
|
+
[
|
|
1343
|
+
options.openclawConfig,
|
|
1344
|
+
process.env.OPENCLAW_CONFIG,
|
|
1345
|
+
process.env.OPENCLAW_CONFIG_PATH,
|
|
1346
|
+
"~/.shadowob/openclaw.json",
|
|
1347
|
+
"~/.openclaw/openclaw.json"
|
|
1348
|
+
].filter((value) => !!value?.trim()).map(expandHome2)
|
|
1349
|
+
)
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
function ccConnectScanExtras(options) {
|
|
1353
|
+
const configPath = resolve2(homedir2(), ".cc-connect/config.toml");
|
|
1354
|
+
const fallback = [
|
|
1355
|
+
"--work-dir",
|
|
1356
|
+
options.workDir?.trim() || ".",
|
|
1357
|
+
"--project-name",
|
|
1358
|
+
options.projectName?.trim() || "shadow-buddy",
|
|
1359
|
+
"--agent-type",
|
|
1360
|
+
options.agentType?.trim() || "codex"
|
|
1361
|
+
];
|
|
1362
|
+
if (!existsSync2(configPath)) return fallback;
|
|
1363
|
+
try {
|
|
1364
|
+
const root = parseToml2(readExisting(configPath));
|
|
1365
|
+
const projects = Array.isArray(root.projects) ? root.projects : [];
|
|
1366
|
+
const configuredProject = projects.find((project2) => {
|
|
1367
|
+
const platformsValue = asObject(project2).platforms;
|
|
1368
|
+
const platforms = Array.isArray(platformsValue) ? platformsValue : [];
|
|
1369
|
+
return platforms.some((platform) => asObject(platform).type === "shadowob");
|
|
1370
|
+
}) ?? projects[0];
|
|
1371
|
+
const project = asObject(configuredProject);
|
|
1372
|
+
const agent = asObject(project.agent);
|
|
1373
|
+
const agentOptions = asObject(agent.options);
|
|
1374
|
+
return [
|
|
1375
|
+
"--work-dir",
|
|
1376
|
+
options.workDir?.trim() || (typeof agentOptions.work_dir === "string" ? agentOptions.work_dir : "."),
|
|
1377
|
+
"--project-name",
|
|
1378
|
+
options.projectName?.trim() || (typeof project.name === "string" ? project.name : "shadow-buddy"),
|
|
1379
|
+
"--agent-type",
|
|
1380
|
+
options.agentType?.trim() || (typeof agent.type === "string" ? agent.type : "codex")
|
|
1381
|
+
];
|
|
1382
|
+
} catch {
|
|
1383
|
+
return fallback;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
function scanOpenClaw(options) {
|
|
1387
|
+
const configPath = firstExistingPath(openClawConfigCandidates(options));
|
|
1388
|
+
const evidence = [];
|
|
1389
|
+
if (commandExists("openclaw")) evidence.push("openclaw command is on PATH");
|
|
1390
|
+
if (configPath) evidence.push(`config found at ${configPath}`);
|
|
1391
|
+
const detected = evidence.length > 0;
|
|
1392
|
+
return {
|
|
1393
|
+
target: "openclaw",
|
|
1394
|
+
detected,
|
|
1395
|
+
evidence,
|
|
1396
|
+
configPath,
|
|
1397
|
+
connectCommand: connectorCommand("connect", "openclaw", options),
|
|
1398
|
+
updateCommand: connectorCommand("update", "openclaw", options),
|
|
1399
|
+
doctorCommand: connectorCommand("doctor", "openclaw", options),
|
|
1400
|
+
statusCommand: connectorCommand("status", "openclaw", options)
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
function scanHermes(options) {
|
|
1404
|
+
const hermesDir = expandHome2(options.hermesHome ?? process.env.HERMES_HOME ?? "~/.hermes");
|
|
1405
|
+
const configPath = resolve2(hermesDir, "config.yaml");
|
|
1406
|
+
const evidence = [];
|
|
1407
|
+
if (commandExists("hermes")) evidence.push("hermes command is on PATH");
|
|
1408
|
+
if (existsSync2(configPath)) evidence.push(`config found at ${configPath}`);
|
|
1409
|
+
if (existsSync2(resolve2(hermesDir, "plugins/shadowob"))) {
|
|
1410
|
+
evidence.push(`shadowob plugin found under ${resolve2(hermesDir, "plugins/shadowob")}`);
|
|
1411
|
+
}
|
|
1412
|
+
return {
|
|
1413
|
+
target: "hermes",
|
|
1414
|
+
detected: evidence.length > 0,
|
|
1415
|
+
evidence,
|
|
1416
|
+
configPath: existsSync2(configPath) ? configPath : void 0,
|
|
1417
|
+
connectCommand: connectorCommand("connect", "hermes", options),
|
|
1418
|
+
updateCommand: connectorCommand("update", "hermes", options),
|
|
1419
|
+
doctorCommand: connectorCommand("doctor", "hermes", options),
|
|
1420
|
+
statusCommand: connectorCommand("status", "hermes", options)
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
function scanCcConnect(options) {
|
|
1424
|
+
const configPath = resolve2(homedir2(), ".cc-connect/config.toml");
|
|
1425
|
+
const binary = getCcConnectBinaryStatus();
|
|
1426
|
+
const evidence = [];
|
|
1427
|
+
if (commandExists("cc-connect")) evidence.push("cc-connect command is on PATH");
|
|
1428
|
+
if (binary.usable) evidence.push(`Shadow fork binary found at ${binary.binaryPath}`);
|
|
1429
|
+
if (existsSync2(configPath)) evidence.push(`config found at ${configPath}`);
|
|
1430
|
+
const extras = ccConnectScanExtras(options);
|
|
1431
|
+
return {
|
|
1432
|
+
target: "cc-connect",
|
|
1433
|
+
detected: evidence.length > 0,
|
|
1434
|
+
evidence,
|
|
1435
|
+
configPath: existsSync2(configPath) ? configPath : void 0,
|
|
1436
|
+
connectCommand: connectorCommand("connect", "cc-connect", options, extras),
|
|
1437
|
+
updateCommand: connectorCommand("update", "cc-connect", options, extras),
|
|
1438
|
+
doctorCommand: connectorCommand("doctor", "cc-connect", options),
|
|
1439
|
+
statusCommand: connectorCommand("status", "cc-connect", options)
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
function scanConnectors(options) {
|
|
1443
|
+
return selectedTargets(options).map((target) => {
|
|
1444
|
+
if (target === "openclaw") return scanOpenClaw(options);
|
|
1445
|
+
if (target === "hermes") return scanHermes(options);
|
|
1446
|
+
return scanCcConnect(options);
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
function printScan(options) {
|
|
1450
|
+
const results = scanConnectors(options);
|
|
1451
|
+
if (options.json) {
|
|
1452
|
+
console.log(
|
|
1453
|
+
JSON.stringify({ serverUrl: normalizeServerUrl2(options.serverUrl), results }, null, 2)
|
|
1454
|
+
);
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
console.log("# Connector scan");
|
|
1458
|
+
console.log(`Shadow server URL: ${normalizeServerUrl2(options.serverUrl)}`);
|
|
1459
|
+
console.log(`Buddy token: ${options.token.trim() ? "provided" : "<BUDDY_TOKEN>"}`);
|
|
1460
|
+
for (const result of results) {
|
|
1461
|
+
console.log("");
|
|
1462
|
+
console.log(`## ${result.target}`);
|
|
1463
|
+
console.log(`Detected: ${result.detected ? "yes" : "no"}`);
|
|
1464
|
+
if (result.evidence.length > 0) {
|
|
1465
|
+
console.log("Evidence:");
|
|
1466
|
+
for (const item of result.evidence) console.log(`- ${item}`);
|
|
1467
|
+
}
|
|
1468
|
+
console.log("Connection instructions:");
|
|
1469
|
+
console.log(`- connect: ${result.connectCommand}`);
|
|
1470
|
+
console.log(`- update: ${result.updateCommand}`);
|
|
1471
|
+
console.log(`- doctor: ${result.doctorCommand}`);
|
|
1472
|
+
console.log(`- status: ${result.statusCommand}`);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
876
1475
|
function hermesPluginSource() {
|
|
877
1476
|
const candidates = [
|
|
878
1477
|
resolve2(packageRoot(), "hermes-shadowob-plugin"),
|
|
@@ -882,33 +1481,39 @@ function hermesPluginSource() {
|
|
|
882
1481
|
if (!found) throw new Error("Cannot find bundled hermes-shadowob-plugin directory");
|
|
883
1482
|
return found;
|
|
884
1483
|
}
|
|
885
|
-
function applyOpenClaw(options) {
|
|
886
|
-
const
|
|
1484
|
+
function applyOpenClaw(options, behavior = { restart: true }) {
|
|
1485
|
+
const target = requireTarget(options);
|
|
1486
|
+
const plan = createConnectorPlan({ ...options, target });
|
|
887
1487
|
const configPath = expandHome2(
|
|
888
1488
|
options.openclawConfig ?? process.env.OPENCLAW_CONFIG ?? "~/.shadowob/openclaw.json"
|
|
889
1489
|
);
|
|
890
|
-
|
|
891
|
-
runShell("openclaw plugins install @shadowob/openclaw-shadowob", options.dryRun);
|
|
1490
|
+
installShadowCliAndSkills(options);
|
|
892
1491
|
console.log(`Applying: Merge OpenClaw config ${configPath}`);
|
|
893
1492
|
const next = mergeOpenClawConfigContent(readExisting(configPath), {
|
|
894
1493
|
token: options.token,
|
|
895
1494
|
serverUrl: normalizeServerUrl2(options.serverUrl)
|
|
896
1495
|
});
|
|
897
1496
|
writeFile(configPath, next, options.dryRun);
|
|
1497
|
+
if (options.install) {
|
|
1498
|
+
console.log("Applying: Install plugin");
|
|
1499
|
+
runShell("openclaw plugins install @shadowob/openclaw-shadowob", options.dryRun);
|
|
1500
|
+
}
|
|
898
1501
|
const restart = plan.commands.find((step) => step.label === "Restart gateway");
|
|
899
|
-
if (restart) {
|
|
1502
|
+
if (restart && behavior.restart) {
|
|
900
1503
|
console.log(`Applying: ${restart.label}`);
|
|
901
1504
|
runShell(restart.command, options.dryRun);
|
|
902
1505
|
}
|
|
903
1506
|
}
|
|
904
1507
|
function applyHermes(options) {
|
|
905
|
-
const
|
|
1508
|
+
const target = requireTarget(options);
|
|
1509
|
+
const plan = createConnectorPlan({ ...options, target });
|
|
906
1510
|
const hermesDir = expandHome2(options.hermesHome ?? process.env.HERMES_HOME ?? "~/.hermes");
|
|
907
1511
|
const pluginTarget = resolve2(hermesDir, "plugins/shadowob");
|
|
908
1512
|
const envPath = resolve2(hermesDir, ".env");
|
|
909
1513
|
const configPath = resolve2(hermesDir, "config.yaml");
|
|
910
1514
|
const envBlock = plan.configBlocks.find((block) => block.label === "~/.hermes/.env");
|
|
911
1515
|
if (!envBlock) throw new Error("Hermes plan is missing config blocks");
|
|
1516
|
+
installShadowCliAndSkills(options);
|
|
912
1517
|
if (options.dryRun) {
|
|
913
1518
|
console.log(`[dry-run] copy ${hermesPluginSource()} -> ${pluginTarget}`);
|
|
914
1519
|
} else {
|
|
@@ -937,9 +1542,11 @@ function applyHermes(options) {
|
|
|
937
1542
|
}
|
|
938
1543
|
}
|
|
939
1544
|
async function applyCcConnect(options) {
|
|
940
|
-
const
|
|
1545
|
+
const target = requireTarget(options);
|
|
1546
|
+
const plan = createConnectorPlan({ ...options, target });
|
|
941
1547
|
const configBlock = plan.configBlocks.find((block) => block.label === "~/.cc-connect/config.toml");
|
|
942
1548
|
if (!configBlock) throw new Error("cc-connect plan is missing config block");
|
|
1549
|
+
installShadowCliAndSkills(options);
|
|
943
1550
|
const configPath = resolve2(homedir2(), ".cc-connect/config.toml");
|
|
944
1551
|
const nextConfig = options.force ? configBlock.content : mergeCcConnectConfigContent(readExisting(configPath), {
|
|
945
1552
|
token: options.token,
|
|
@@ -963,20 +1570,41 @@ async function applyCcConnect(options) {
|
|
|
963
1570
|
}
|
|
964
1571
|
}
|
|
965
1572
|
async function connect(options) {
|
|
966
|
-
|
|
1573
|
+
const target = requireTarget(options);
|
|
1574
|
+
if (target === "openclaw") {
|
|
967
1575
|
applyOpenClaw(options);
|
|
968
1576
|
return;
|
|
969
1577
|
}
|
|
970
|
-
if (
|
|
1578
|
+
if (target === "hermes") {
|
|
971
1579
|
applyHermes(options);
|
|
972
1580
|
return;
|
|
973
1581
|
}
|
|
974
1582
|
await applyCcConnect(options);
|
|
975
1583
|
}
|
|
1584
|
+
async function repair(options, mode) {
|
|
1585
|
+
const target = requireTarget(options);
|
|
1586
|
+
console.log(`Applying: ${mode} ${target} connector`);
|
|
1587
|
+
if (target === "openclaw") {
|
|
1588
|
+
applyOpenClaw(options, { restart: options.start });
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
if (target === "hermes") {
|
|
1592
|
+
applyHermes({ ...options, start: options.start });
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
await applyCcConnect({ ...options, start: options.start });
|
|
1596
|
+
}
|
|
976
1597
|
async function main() {
|
|
977
1598
|
const options = parseArgs(process.argv.slice(2));
|
|
978
1599
|
if (options.command === "connect") {
|
|
979
1600
|
await connect(options);
|
|
1601
|
+
} else if (options.command === "fix" || options.command === "update") {
|
|
1602
|
+
await repair(options, options.command);
|
|
1603
|
+
} else if (options.command === "doctor" || options.command === "status") {
|
|
1604
|
+
const ok = printDiagnostics(options, options.command);
|
|
1605
|
+
if (options.command === "doctor" && !ok) process.exitCode = 1;
|
|
1606
|
+
} else if (options.command === "scan") {
|
|
1607
|
+
printScan(options);
|
|
980
1608
|
} else {
|
|
981
1609
|
printPlan(options);
|
|
982
1610
|
}
|