@shadowob/connector 1.1.16 → 1.1.18
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 +51 -2
- package/dist/cli.js +622 -128
- package/dist/index.cjs +78 -12
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +78 -12
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7662,10 +7662,10 @@ var require_main = __commonJS({
|
|
|
7662
7662
|
});
|
|
7663
7663
|
|
|
7664
7664
|
// src/cli.ts
|
|
7665
|
-
import { spawn, spawnSync as
|
|
7666
|
-
import { chmodSync as
|
|
7667
|
-
import { arch, homedir as
|
|
7668
|
-
import { dirname as
|
|
7665
|
+
import { spawn, spawnSync as spawnSync3 } from "child_process";
|
|
7666
|
+
import { chmodSync as chmodSync3, cpSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync, writeFileSync as writeFileSync3 } from "fs";
|
|
7667
|
+
import { arch, homedir as homedir3, hostname, platform as platform2 } from "os";
|
|
7668
|
+
import { dirname as dirname3, resolve as resolve3 } from "path";
|
|
7669
7669
|
import { fileURLToPath } from "url";
|
|
7670
7670
|
|
|
7671
7671
|
// ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/error.js
|
|
@@ -8521,20 +8521,20 @@ import { dirname, resolve } from "path";
|
|
|
8521
8521
|
|
|
8522
8522
|
// src/cc-connect-fork.ts
|
|
8523
8523
|
var CC_CONNECT_FORK_REPO = "buggyblues/cc-connect";
|
|
8524
|
-
var CC_CONNECT_FORK_REF = "
|
|
8524
|
+
var CC_CONNECT_FORK_REF = "f382563cfebaef36c5d257461dfaf318161fe3ea";
|
|
8525
8525
|
var CC_CONNECT_FORK_SHORT_REF = CC_CONNECT_FORK_REF.slice(0, 7);
|
|
8526
|
-
var CC_CONNECT_FORK_PACKAGE_VERSION = "1.3.3-beta.
|
|
8526
|
+
var CC_CONNECT_FORK_PACKAGE_VERSION = "1.3.3-beta.7";
|
|
8527
8527
|
var CC_CONNECT_FORK_DOCS_URL = `https://github.com/${CC_CONNECT_FORK_REPO}/blob/main/docs/shadowob.md`;
|
|
8528
8528
|
|
|
8529
8529
|
// src/cc-connect-installer.ts
|
|
8530
8530
|
var NAME = "cc-connect";
|
|
8531
8531
|
var RELEASE_ARCHIVE_SHA256 = {
|
|
8532
|
-
"cc-connect-v1.3.3-beta.
|
|
8533
|
-
"cc-connect-v1.3.3-beta.
|
|
8534
|
-
"cc-connect-v1.3.3-beta.
|
|
8535
|
-
"cc-connect-v1.3.3-beta.
|
|
8536
|
-
"cc-connect-v1.3.3-beta.
|
|
8537
|
-
"cc-connect-v1.3.3-beta.
|
|
8532
|
+
"cc-connect-v1.3.3-beta.7-darwin-amd64.tar.gz": "e5215b202924eea6d98c8dda79318e7ab5ff663e4b7a4091f316e625b87d763f",
|
|
8533
|
+
"cc-connect-v1.3.3-beta.7-darwin-arm64.tar.gz": "633927197755ae14f8987aaf078b9b20003aa69abfd3f7d99dd657c36812f288",
|
|
8534
|
+
"cc-connect-v1.3.3-beta.7-linux-amd64.tar.gz": "ad290a15ba6de0686e18f54221ab83e093bc22513dd1fb29f51c2ce2d26d003d",
|
|
8535
|
+
"cc-connect-v1.3.3-beta.7-linux-arm64.tar.gz": "cb4473e7c128be83c5965dc7e6ef8c4bafd3b7dce1cb2e8adb72064818b85acb",
|
|
8536
|
+
"cc-connect-v1.3.3-beta.7-windows-amd64.zip": "174b53ca30f3667caef640b403b442521b9b37bf45f7e1bfad3c19d07b84c581",
|
|
8537
|
+
"cc-connect-v1.3.3-beta.7-windows-arm64.zip": "ce32a00d2ec4f8deeefe134ba23d4bd6587651c32772773c81772739fe335713"
|
|
8538
8538
|
};
|
|
8539
8539
|
var PLATFORM_MAP = {
|
|
8540
8540
|
darwin: "darwin",
|
|
@@ -8580,14 +8580,14 @@ function runCommand(command, args, options) {
|
|
|
8580
8580
|
}
|
|
8581
8581
|
}
|
|
8582
8582
|
function platformInfo() {
|
|
8583
|
-
const
|
|
8583
|
+
const platform3 = PLATFORM_MAP[process.platform];
|
|
8584
8584
|
const arch2 = ARCH_MAP[process.arch];
|
|
8585
|
-
if (!
|
|
8585
|
+
if (!platform3 || !arch2) {
|
|
8586
8586
|
throw new Error(
|
|
8587
8587
|
`Unsupported cc-connect platform: ${process.platform}/${process.arch}. Supported: linux/darwin/windows x64/arm64`
|
|
8588
8588
|
);
|
|
8589
8589
|
}
|
|
8590
|
-
return { platform:
|
|
8590
|
+
return { platform: platform3, arch: arch2, ext: platform3 === "windows" ? ".zip" : ".tar.gz" };
|
|
8591
8591
|
}
|
|
8592
8592
|
function fetchBuffer(url, redirects = 5) {
|
|
8593
8593
|
return new Promise((resolvePromise, reject) => {
|
|
@@ -8650,9 +8650,9 @@ function verifyReleaseChecksum(filename, data) {
|
|
|
8650
8650
|
}
|
|
8651
8651
|
}
|
|
8652
8652
|
async function installFromRelease(binaryPath, options) {
|
|
8653
|
-
const { platform:
|
|
8653
|
+
const { platform: platform3, arch: arch2, ext } = platformInfo();
|
|
8654
8654
|
const version = `v${CC_CONNECT_FORK_PACKAGE_VERSION}`;
|
|
8655
|
-
const filename = `${NAME}-${version}-${
|
|
8655
|
+
const filename = `${NAME}-${version}-${platform3}-${arch2}${ext}`;
|
|
8656
8656
|
const url = `https://github.com/${CC_CONNECT_FORK_REPO}/releases/download/${version}/${filename}`;
|
|
8657
8657
|
const binDir = dirname(binaryPath);
|
|
8658
8658
|
const archivePath = resolve(binDir, `_release${ext}`);
|
|
@@ -8806,6 +8806,19 @@ function ensureTrailingNewline(value) {
|
|
|
8806
8806
|
function quoteEnv(value) {
|
|
8807
8807
|
return /^[A-Za-z0-9_./:@-]+$/.test(value) ? value : JSON.stringify(value);
|
|
8808
8808
|
}
|
|
8809
|
+
function normalizeModelProvider(provider) {
|
|
8810
|
+
const baseUrl = provider?.baseUrl.trim();
|
|
8811
|
+
const apiKey = provider?.apiKey.trim();
|
|
8812
|
+
const model = provider?.model.trim();
|
|
8813
|
+
if (!baseUrl || !apiKey || !model) return null;
|
|
8814
|
+
return {
|
|
8815
|
+
id: provider?.id?.trim() || "shadow-official",
|
|
8816
|
+
label: provider?.label?.trim() || "Shadow official LLM proxy",
|
|
8817
|
+
baseUrl,
|
|
8818
|
+
apiKey,
|
|
8819
|
+
model
|
|
8820
|
+
};
|
|
8821
|
+
}
|
|
8809
8822
|
function normalizeJsonRoot(existing, label) {
|
|
8810
8823
|
if (!existing.trim()) return {};
|
|
8811
8824
|
const parsed = JSON.parse(existing);
|
|
@@ -8833,11 +8846,18 @@ function parseTomlRoot(existing, label) {
|
|
|
8833
8846
|
}
|
|
8834
8847
|
function mergeEnvContent(existing, values) {
|
|
8835
8848
|
(0, import_dotenv.parse)(existing);
|
|
8849
|
+
const modelProvider = normalizeModelProvider(values.modelProvider);
|
|
8836
8850
|
const updates = {
|
|
8837
8851
|
SHADOW_BASE_URL: values.serverUrl,
|
|
8838
8852
|
SHADOW_TOKEN: values.token,
|
|
8839
8853
|
...SHADOW_ENV_VALUES
|
|
8840
8854
|
};
|
|
8855
|
+
if (modelProvider) {
|
|
8856
|
+
updates.OPENAI_COMPATIBLE_BASE_URL = modelProvider.baseUrl;
|
|
8857
|
+
updates.OPENAI_COMPATIBLE_API_KEY = modelProvider.apiKey;
|
|
8858
|
+
updates.OPENAI_COMPATIBLE_MODEL_ID = modelProvider.model;
|
|
8859
|
+
updates.SHADOW_MODEL_PROVIDER_ID = modelProvider.id ?? "shadow-official";
|
|
8860
|
+
}
|
|
8841
8861
|
const seen = /* @__PURE__ */ new Set();
|
|
8842
8862
|
const lines = existing.length > 0 ? existing.split(/\r?\n/) : [];
|
|
8843
8863
|
const next = [];
|
|
@@ -8860,6 +8880,7 @@ function mergeEnvContent(existing, values) {
|
|
|
8860
8880
|
}
|
|
8861
8881
|
function mergeOpenClawConfigContent(existing, values) {
|
|
8862
8882
|
const root = normalizeJsonRoot(existing, "OpenClaw");
|
|
8883
|
+
const modelProvider = normalizeModelProvider(values.modelProvider);
|
|
8863
8884
|
const channels = asRecord(root.channels);
|
|
8864
8885
|
const legacyShadow = asRecord(channels["openclaw-shadowob"]);
|
|
8865
8886
|
const shadow = asRecord(channels.shadowob);
|
|
@@ -8884,10 +8905,27 @@ function mergeOpenClawConfigContent(existing, values) {
|
|
|
8884
8905
|
};
|
|
8885
8906
|
plugins.entries = entries;
|
|
8886
8907
|
root.plugins = plugins;
|
|
8908
|
+
if (modelProvider) {
|
|
8909
|
+
const models = asRecord(root.models);
|
|
8910
|
+
const providers = asRecord(models.providers);
|
|
8911
|
+
const providerId = modelProvider.id ?? "shadow-official";
|
|
8912
|
+
providers[providerId] = {
|
|
8913
|
+
...asRecord(providers[providerId]),
|
|
8914
|
+
api: "openai-completions",
|
|
8915
|
+
apiKey: "${env:OPENAI_COMPATIBLE_API_KEY}",
|
|
8916
|
+
baseUrl: modelProvider.baseUrl,
|
|
8917
|
+
request: { allowPrivateNetwork: true },
|
|
8918
|
+
models: [{ id: modelProvider.model, name: modelProvider.model }]
|
|
8919
|
+
};
|
|
8920
|
+
models.mode = models.mode ?? "merge";
|
|
8921
|
+
models.providers = providers;
|
|
8922
|
+
root.models = models;
|
|
8923
|
+
}
|
|
8887
8924
|
return ensureTrailingNewline(JSON.stringify(root, null, 2));
|
|
8888
8925
|
}
|
|
8889
8926
|
function mergeHermesConfigContent(existing, values) {
|
|
8890
8927
|
const root = parseYamlRoot(existing, "Hermes");
|
|
8928
|
+
const modelProvider = normalizeModelProvider(values.modelProvider);
|
|
8891
8929
|
const plugins = asRecord(root.plugins);
|
|
8892
8930
|
plugins.enabled = uniqueStrings(Array.isArray(plugins.enabled) ? plugins.enabled : [], "shadowob");
|
|
8893
8931
|
root.plugins = plugins;
|
|
@@ -8909,6 +8947,15 @@ function mergeHermesConfigContent(existing, values) {
|
|
|
8909
8947
|
}
|
|
8910
8948
|
};
|
|
8911
8949
|
root.platforms = platforms;
|
|
8950
|
+
if (modelProvider) {
|
|
8951
|
+
const model = asRecord(root.model);
|
|
8952
|
+
root.model = {
|
|
8953
|
+
...model,
|
|
8954
|
+
default: model.model ?? model.default ?? modelProvider.model,
|
|
8955
|
+
provider: model.provider ?? "custom",
|
|
8956
|
+
base_url: model.base_url ?? modelProvider.baseUrl
|
|
8957
|
+
};
|
|
8958
|
+
}
|
|
8912
8959
|
return ensureTrailingNewline((0, import_yaml.stringify)(root));
|
|
8913
8960
|
}
|
|
8914
8961
|
function asTomlTable(value) {
|
|
@@ -8945,6 +8992,27 @@ function mergeCcConnectConfigContent(existing, values) {
|
|
|
8945
8992
|
...agentOptions,
|
|
8946
8993
|
work_dir: values.workDir
|
|
8947
8994
|
};
|
|
8995
|
+
const modelProvider = normalizeModelProvider(values.modelProvider);
|
|
8996
|
+
if (modelProvider) {
|
|
8997
|
+
agent.options = {
|
|
8998
|
+
...asTomlTable(agent.options),
|
|
8999
|
+
provider: modelProvider.id ?? "shadow-official",
|
|
9000
|
+
model: modelProvider.model
|
|
9001
|
+
};
|
|
9002
|
+
const providers = tomlArray(agent.providers);
|
|
9003
|
+
const providerId = modelProvider.id ?? "shadow-official";
|
|
9004
|
+
let provider = providers.find((item) => item.name === providerId);
|
|
9005
|
+
if (!provider) {
|
|
9006
|
+
provider = {};
|
|
9007
|
+
providers.push(provider);
|
|
9008
|
+
}
|
|
9009
|
+
provider.name = providerId;
|
|
9010
|
+
provider.api_key = modelProvider.apiKey;
|
|
9011
|
+
provider.base_url = modelProvider.baseUrl;
|
|
9012
|
+
provider.model = modelProvider.model;
|
|
9013
|
+
provider.models = [{ model: modelProvider.model }];
|
|
9014
|
+
agent.providers = providers;
|
|
9015
|
+
}
|
|
8948
9016
|
project.agent = agent;
|
|
8949
9017
|
const platforms = tomlArray(project.platforms);
|
|
8950
9018
|
let shadowPlatform = platforms.find((item) => item.type === "shadowob");
|
|
@@ -9154,9 +9222,35 @@ var normalizeServerUrl = (value) => {
|
|
|
9154
9222
|
return trimmed.endsWith("/api") ? trimmed.slice(0, -4) : trimmed.replace(/\/$/, "");
|
|
9155
9223
|
};
|
|
9156
9224
|
var tokenOrPlaceholder = (token) => token.trim() || "<BUDDY_TOKEN>";
|
|
9225
|
+
function normalizeModelProvider2(input) {
|
|
9226
|
+
const baseUrl = input.modelProvider?.baseUrl.trim();
|
|
9227
|
+
const apiKey = input.modelProvider?.apiKey.trim();
|
|
9228
|
+
const model = input.modelProvider?.model.trim();
|
|
9229
|
+
if (!baseUrl || !apiKey || !model) return null;
|
|
9230
|
+
return {
|
|
9231
|
+
id: input.modelProvider?.id?.trim() || "shadow-official",
|
|
9232
|
+
label: input.modelProvider?.label?.trim() || "Shadow official LLM proxy",
|
|
9233
|
+
baseUrl,
|
|
9234
|
+
apiKey,
|
|
9235
|
+
model
|
|
9236
|
+
};
|
|
9237
|
+
}
|
|
9238
|
+
function modelProviderEnvLines(provider) {
|
|
9239
|
+
if (!provider) return [];
|
|
9240
|
+
return [
|
|
9241
|
+
`OPENAI_COMPATIBLE_BASE_URL=${shellQuote(provider.baseUrl)}`,
|
|
9242
|
+
`OPENAI_COMPATIBLE_API_KEY=${shellQuote(provider.apiKey)}`,
|
|
9243
|
+
`OPENAI_COMPATIBLE_MODEL_ID=${shellQuote(provider.model)}`,
|
|
9244
|
+
`SHADOW_MODEL_PROVIDER_ID=${shellQuote(provider.id ?? "shadow-official")}`
|
|
9245
|
+
];
|
|
9246
|
+
}
|
|
9247
|
+
function modelProviderCapabilities(provider, capabilities) {
|
|
9248
|
+
return provider ? [...capabilities, "officialModelProvider"] : capabilities;
|
|
9249
|
+
}
|
|
9157
9250
|
function buildOpenClawPlan(input) {
|
|
9158
9251
|
const token = tokenOrPlaceholder(input.token);
|
|
9159
9252
|
const serverUrl = normalizeServerUrl(input.serverUrl);
|
|
9253
|
+
const modelProvider = normalizeModelProvider2(input);
|
|
9160
9254
|
const jsonConfig = JSON.stringify(
|
|
9161
9255
|
{
|
|
9162
9256
|
channels: {
|
|
@@ -9164,7 +9258,21 @@ function buildOpenClawPlan(input) {
|
|
|
9164
9258
|
token,
|
|
9165
9259
|
serverUrl
|
|
9166
9260
|
}
|
|
9167
|
-
}
|
|
9261
|
+
},
|
|
9262
|
+
...modelProvider ? {
|
|
9263
|
+
models: {
|
|
9264
|
+
mode: "merge",
|
|
9265
|
+
providers: {
|
|
9266
|
+
[modelProvider.id ?? "shadow-official"]: {
|
|
9267
|
+
api: "openai-completions",
|
|
9268
|
+
apiKey: "${env:OPENAI_COMPATIBLE_API_KEY}",
|
|
9269
|
+
baseUrl: modelProvider.baseUrl,
|
|
9270
|
+
request: { allowPrivateNetwork: true },
|
|
9271
|
+
models: [{ id: modelProvider.model, name: modelProvider.model }]
|
|
9272
|
+
}
|
|
9273
|
+
}
|
|
9274
|
+
}
|
|
9275
|
+
} : {}
|
|
9168
9276
|
},
|
|
9169
9277
|
null,
|
|
9170
9278
|
2
|
|
@@ -9210,6 +9318,7 @@ function buildOpenClawPlan(input) {
|
|
|
9210
9318
|
"",
|
|
9211
9319
|
`Preferred one-line setup: ${connectCommand}`,
|
|
9212
9320
|
"The connector installs/configures the Shadow CLI, official Shadow skill files, and the Buddy profile before applying the OpenClaw channel config.",
|
|
9321
|
+
modelProvider ? `It also configures ${modelProvider.label ?? "Shadow official LLM proxy"} as an OpenAI-compatible model provider (${modelProvider.model}).` : "",
|
|
9213
9322
|
"",
|
|
9214
9323
|
"Run these steps in order:",
|
|
9215
9324
|
...commands.map((item, index) => `${index + 1}. ${item.command}`),
|
|
@@ -9217,7 +9326,7 @@ function buildOpenClawPlan(input) {
|
|
|
9217
9326
|
"Confirm each step and then verify the gateway is running."
|
|
9218
9327
|
].join("\n"),
|
|
9219
9328
|
docsUrl: "/product/index.html",
|
|
9220
|
-
capabilities: [
|
|
9329
|
+
capabilities: modelProviderCapabilities(modelProvider, [
|
|
9221
9330
|
"channelMessages",
|
|
9222
9331
|
"dms",
|
|
9223
9332
|
"threads",
|
|
@@ -9238,18 +9347,20 @@ function buildOpenClawPlan(input) {
|
|
|
9238
9347
|
"notifications",
|
|
9239
9348
|
"officialSkills",
|
|
9240
9349
|
"cronTasks"
|
|
9241
|
-
]
|
|
9350
|
+
])
|
|
9242
9351
|
};
|
|
9243
9352
|
}
|
|
9244
9353
|
function buildHermesPlan(input) {
|
|
9245
9354
|
const token = tokenOrPlaceholder(input.token);
|
|
9246
9355
|
const serverUrl = normalizeServerUrl(input.serverUrl);
|
|
9356
|
+
const modelProvider = normalizeModelProvider2(input);
|
|
9247
9357
|
const envBlock = [
|
|
9248
9358
|
`SHADOW_BASE_URL=${shellQuote(serverUrl)}`,
|
|
9249
9359
|
`SHADOW_TOKEN=${shellQuote(token)}`,
|
|
9250
9360
|
"SHADOW_ALLOW_ALL_USERS=true",
|
|
9251
9361
|
"SHADOW_HEARTBEAT_INTERVAL_SECONDS=30",
|
|
9252
|
-
`SHADOW_SLASH_COMMANDS_JSON=${shellQuote("[]")}
|
|
9362
|
+
`SHADOW_SLASH_COMMANDS_JSON=${shellQuote("[]")}`,
|
|
9363
|
+
...modelProviderEnvLines(modelProvider)
|
|
9253
9364
|
].join("\n");
|
|
9254
9365
|
const yamlConfig = [
|
|
9255
9366
|
"plugins:",
|
|
@@ -9266,7 +9377,14 @@ function buildHermesPlan(input) {
|
|
|
9266
9377
|
" rest_only: false",
|
|
9267
9378
|
" catchup_minutes: 0",
|
|
9268
9379
|
" download_media: true",
|
|
9269
|
-
" slash_commands: []"
|
|
9380
|
+
" slash_commands: []",
|
|
9381
|
+
...modelProvider ? [
|
|
9382
|
+
"",
|
|
9383
|
+
"model:",
|
|
9384
|
+
` default: "${modelProvider.model}"`,
|
|
9385
|
+
" provider: custom",
|
|
9386
|
+
` base_url: "${modelProvider.baseUrl}"`
|
|
9387
|
+
] : []
|
|
9270
9388
|
].join("\n");
|
|
9271
9389
|
const commands = [
|
|
9272
9390
|
{
|
|
@@ -9310,10 +9428,11 @@ function buildHermesPlan(input) {
|
|
|
9310
9428
|
`Buddy token: ${token}`,
|
|
9311
9429
|
"",
|
|
9312
9430
|
`Preferred one-line setup: ${connectCommand}`,
|
|
9313
|
-
"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."
|
|
9431
|
+
"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.",
|
|
9432
|
+
modelProvider ? `It also configures Hermes custom model endpoint ${modelProvider.baseUrl} with model ${modelProvider.model}.` : ""
|
|
9314
9433
|
].join("\n"),
|
|
9315
9434
|
docsUrl: "https://hermes-agent.nousresearch.com/docs/user-guide/messaging",
|
|
9316
|
-
capabilities: [
|
|
9435
|
+
capabilities: modelProviderCapabilities(modelProvider, [
|
|
9317
9436
|
"channelMessages",
|
|
9318
9437
|
"dms",
|
|
9319
9438
|
"threads",
|
|
@@ -9330,7 +9449,7 @@ function buildHermesPlan(input) {
|
|
|
9330
9449
|
"shadowCliLogin",
|
|
9331
9450
|
"notifications",
|
|
9332
9451
|
"officialSkills"
|
|
9333
|
-
]
|
|
9452
|
+
])
|
|
9334
9453
|
};
|
|
9335
9454
|
}
|
|
9336
9455
|
function buildCcConnectPlan(input) {
|
|
@@ -9339,6 +9458,7 @@ function buildCcConnectPlan(input) {
|
|
|
9339
9458
|
const projectName = input.projectName?.trim() || DEFAULT_PROJECT_NAME;
|
|
9340
9459
|
const workDir = input.workDir?.trim() || DEFAULT_WORK_DIR;
|
|
9341
9460
|
const agentType = input.agentType?.trim() || DEFAULT_CC_AGENT;
|
|
9461
|
+
const modelProvider = normalizeModelProvider2(input);
|
|
9342
9462
|
const tomlConfig = [
|
|
9343
9463
|
'language = "zh"',
|
|
9344
9464
|
"",
|
|
@@ -9350,6 +9470,19 @@ function buildCcConnectPlan(input) {
|
|
|
9350
9470
|
"",
|
|
9351
9471
|
"[projects.agent.options]",
|
|
9352
9472
|
`work_dir = "${workDir}"`,
|
|
9473
|
+
...modelProvider ? [
|
|
9474
|
+
`provider = "${modelProvider.id ?? "shadow-official"}"`,
|
|
9475
|
+
`model = "${modelProvider.model}"`,
|
|
9476
|
+
"",
|
|
9477
|
+
"[[projects.agent.providers]]",
|
|
9478
|
+
`name = "${modelProvider.id ?? "shadow-official"}"`,
|
|
9479
|
+
`api_key = "${modelProvider.apiKey}"`,
|
|
9480
|
+
`base_url = "${modelProvider.baseUrl}"`,
|
|
9481
|
+
`model = "${modelProvider.model}"`,
|
|
9482
|
+
"",
|
|
9483
|
+
"[[projects.agent.providers.models]]",
|
|
9484
|
+
`model = "${modelProvider.model}"`
|
|
9485
|
+
] : [],
|
|
9353
9486
|
"",
|
|
9354
9487
|
"[[projects.platforms]]",
|
|
9355
9488
|
'type = "shadowob"',
|
|
@@ -9402,10 +9535,11 @@ function buildCcConnectPlan(input) {
|
|
|
9402
9535
|
`Agent type: ${agentType}`,
|
|
9403
9536
|
"",
|
|
9404
9537
|
`Preferred one-line setup: ${startCommand}`,
|
|
9405
|
-
`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
|
|
9538
|
+
`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.`,
|
|
9539
|
+
modelProvider ? `Configure ${modelProvider.label ?? "Shadow official LLM proxy"} as provider ${modelProvider.id ?? "shadow-official"} for ${agentType}.` : ""
|
|
9406
9540
|
].join("\n"),
|
|
9407
9541
|
docsUrl: CC_CONNECT_FORK_DOCS_URL,
|
|
9408
|
-
capabilities: [
|
|
9542
|
+
capabilities: modelProviderCapabilities(modelProvider, [
|
|
9409
9543
|
"channelMessages",
|
|
9410
9544
|
"dms",
|
|
9411
9545
|
"attachments",
|
|
@@ -9420,7 +9554,7 @@ function buildCcConnectPlan(input) {
|
|
|
9420
9554
|
"multiAgentBinding",
|
|
9421
9555
|
"shadowCliLogin",
|
|
9422
9556
|
"notifications"
|
|
9423
|
-
]
|
|
9557
|
+
])
|
|
9424
9558
|
};
|
|
9425
9559
|
}
|
|
9426
9560
|
function createConnectorPlan(input) {
|
|
@@ -9430,6 +9564,287 @@ function createConnectorPlan(input) {
|
|
|
9430
9564
|
throw new Error(`Unsupported connector target: ${String(input.target)}`);
|
|
9431
9565
|
}
|
|
9432
9566
|
|
|
9567
|
+
// src/toolchain.ts
|
|
9568
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
9569
|
+
import { createHash as createHash2 } from "crypto";
|
|
9570
|
+
import {
|
|
9571
|
+
chmodSync as chmodSync2,
|
|
9572
|
+
existsSync as existsSync2,
|
|
9573
|
+
mkdirSync as mkdirSync2,
|
|
9574
|
+
readdirSync as readdirSync2,
|
|
9575
|
+
renameSync as renameSync2,
|
|
9576
|
+
rmSync as rmSync2,
|
|
9577
|
+
writeFileSync as writeFileSync2
|
|
9578
|
+
} from "fs";
|
|
9579
|
+
import { get as httpsGet2 } from "https";
|
|
9580
|
+
import { homedir as homedir2, platform } from "os";
|
|
9581
|
+
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
9582
|
+
var CONNECTOR_MANAGED_NODE_VERSION = process.env.SHADOW_CONNECTOR_NODE_VERSION?.trim() || "22.16.0";
|
|
9583
|
+
var PATH_KEY = process.platform === "win32" ? "Path" : "PATH";
|
|
9584
|
+
var NODE_PLATFORM = {
|
|
9585
|
+
darwin: "darwin",
|
|
9586
|
+
linux: "linux",
|
|
9587
|
+
win32: "win"
|
|
9588
|
+
};
|
|
9589
|
+
var NODE_ARCH = {
|
|
9590
|
+
x64: "x64",
|
|
9591
|
+
arm64: "arm64"
|
|
9592
|
+
};
|
|
9593
|
+
var loginShellPath;
|
|
9594
|
+
var cachedNvmBinDirs;
|
|
9595
|
+
var cachedConnectorPath;
|
|
9596
|
+
function expandHome2(value) {
|
|
9597
|
+
return value.startsWith("~/") ? resolve2(homedir2(), value.slice(2)) : resolve2(value);
|
|
9598
|
+
}
|
|
9599
|
+
function connectorHome() {
|
|
9600
|
+
const override = process.env.SHADOW_CONNECTOR_HOME?.trim();
|
|
9601
|
+
return override ? expandHome2(override) : resolve2(homedir2(), ".shadowob/connector");
|
|
9602
|
+
}
|
|
9603
|
+
function managedNodeRoot() {
|
|
9604
|
+
return resolve2(connectorHome(), "node", `v${CONNECTOR_MANAGED_NODE_VERSION}`);
|
|
9605
|
+
}
|
|
9606
|
+
function managedNodeBinDir() {
|
|
9607
|
+
return process.platform === "win32" ? managedNodeRoot() : resolve2(managedNodeRoot(), "bin");
|
|
9608
|
+
}
|
|
9609
|
+
function nodeGlobalRoot() {
|
|
9610
|
+
return resolve2(connectorHome(), "node-global");
|
|
9611
|
+
}
|
|
9612
|
+
function nodeGlobalBinDir() {
|
|
9613
|
+
return process.platform === "win32" ? nodeGlobalRoot() : resolve2(nodeGlobalRoot(), "bin");
|
|
9614
|
+
}
|
|
9615
|
+
function splitPath(value) {
|
|
9616
|
+
return (value ?? "").split(process.platform === "win32" ? ";" : ":").filter(Boolean);
|
|
9617
|
+
}
|
|
9618
|
+
function dedupePaths(paths) {
|
|
9619
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9620
|
+
const result = [];
|
|
9621
|
+
for (const raw of paths) {
|
|
9622
|
+
const item = raw.trim();
|
|
9623
|
+
if (!item || seen.has(item)) continue;
|
|
9624
|
+
seen.add(item);
|
|
9625
|
+
result.push(item);
|
|
9626
|
+
}
|
|
9627
|
+
return result;
|
|
9628
|
+
}
|
|
9629
|
+
function readLoginShellPath() {
|
|
9630
|
+
if (process.env.SHADOW_CONNECTOR_SKIP_LOGIN_SHELL === "1") return [];
|
|
9631
|
+
if (loginShellPath !== void 0) return splitPath(loginShellPath ?? "");
|
|
9632
|
+
const shells = dedupePaths(
|
|
9633
|
+
[process.env.SHELL, "/bin/zsh", "/bin/bash"].filter(
|
|
9634
|
+
(item) => Boolean(item?.trim())
|
|
9635
|
+
)
|
|
9636
|
+
);
|
|
9637
|
+
for (const shell of shells) {
|
|
9638
|
+
const result = spawnSync2(shell, ["-lc", 'printf %s "$PATH"'], {
|
|
9639
|
+
encoding: "utf8",
|
|
9640
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
9641
|
+
timeout: 1800
|
|
9642
|
+
});
|
|
9643
|
+
if (result.status === 0 && result.stdout.trim()) {
|
|
9644
|
+
loginShellPath = result.stdout.trim();
|
|
9645
|
+
return splitPath(loginShellPath);
|
|
9646
|
+
}
|
|
9647
|
+
}
|
|
9648
|
+
loginShellPath = null;
|
|
9649
|
+
return [];
|
|
9650
|
+
}
|
|
9651
|
+
function nvmBinDirs() {
|
|
9652
|
+
if (cachedNvmBinDirs) return cachedNvmBinDirs;
|
|
9653
|
+
const roots = dedupePaths(
|
|
9654
|
+
[process.env.NVM_DIR, resolve2(homedir2(), ".nvm")].filter((item) => Boolean(item?.trim())).map(expandHome2)
|
|
9655
|
+
);
|
|
9656
|
+
const bins = [];
|
|
9657
|
+
if (process.env.NVM_BIN?.trim()) bins.push(process.env.NVM_BIN.trim());
|
|
9658
|
+
for (const root of roots) {
|
|
9659
|
+
const versionsDir = resolve2(root, "versions/node");
|
|
9660
|
+
if (!existsSync2(versionsDir)) continue;
|
|
9661
|
+
try {
|
|
9662
|
+
const versions = readdirSync2(versionsDir).filter((entry) => /^v?\d+\.\d+\.\d+/.test(entry)).sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
|
|
9663
|
+
for (const version of versions) bins.push(resolve2(versionsDir, version, "bin"));
|
|
9664
|
+
} catch {
|
|
9665
|
+
}
|
|
9666
|
+
}
|
|
9667
|
+
cachedNvmBinDirs = bins;
|
|
9668
|
+
return bins;
|
|
9669
|
+
}
|
|
9670
|
+
function commonBinDirs() {
|
|
9671
|
+
return [
|
|
9672
|
+
resolve2(homedir2(), ".local/bin"),
|
|
9673
|
+
nodeGlobalBinDir(),
|
|
9674
|
+
managedNodeBinDir(),
|
|
9675
|
+
resolve2(homedir2(), ".npm-global/bin"),
|
|
9676
|
+
resolve2(homedir2(), ".npm/bin"),
|
|
9677
|
+
resolve2(homedir2(), ".volta/bin"),
|
|
9678
|
+
resolve2(homedir2(), ".bun/bin"),
|
|
9679
|
+
resolve2(homedir2(), ".deno/bin"),
|
|
9680
|
+
resolve2(homedir2(), ".cargo/bin"),
|
|
9681
|
+
"/opt/homebrew/bin",
|
|
9682
|
+
"/opt/homebrew/sbin",
|
|
9683
|
+
"/usr/local/bin",
|
|
9684
|
+
"/usr/local/sbin",
|
|
9685
|
+
"/usr/bin",
|
|
9686
|
+
"/bin",
|
|
9687
|
+
"/usr/sbin",
|
|
9688
|
+
"/sbin"
|
|
9689
|
+
];
|
|
9690
|
+
}
|
|
9691
|
+
function connectorPath(env = process.env) {
|
|
9692
|
+
if (env === process.env && cachedConnectorPath) return cachedConnectorPath;
|
|
9693
|
+
const value = dedupePaths([
|
|
9694
|
+
...commonBinDirs(),
|
|
9695
|
+
...nvmBinDirs(),
|
|
9696
|
+
...readLoginShellPath(),
|
|
9697
|
+
...splitPath(env.PATH ?? env.Path)
|
|
9698
|
+
]).join(process.platform === "win32" ? ";" : ":");
|
|
9699
|
+
if (env === process.env) cachedConnectorPath = value;
|
|
9700
|
+
return value;
|
|
9701
|
+
}
|
|
9702
|
+
function connectorProcessEnv(env = process.env) {
|
|
9703
|
+
const next = {
|
|
9704
|
+
...env,
|
|
9705
|
+
SHADOW_CONNECTOR_HOME: connectorHome(),
|
|
9706
|
+
NPM_CONFIG_PREFIX: nodeGlobalRoot(),
|
|
9707
|
+
npm_config_prefix: nodeGlobalRoot()
|
|
9708
|
+
};
|
|
9709
|
+
next[PATH_KEY] = connectorPath(env);
|
|
9710
|
+
next.PATH = next[PATH_KEY];
|
|
9711
|
+
return next;
|
|
9712
|
+
}
|
|
9713
|
+
function findCommandOnConnectorPath(command, env = process.env) {
|
|
9714
|
+
if (command.includes("/") || process.platform === "win32" && command.includes("\\")) {
|
|
9715
|
+
return existsSync2(command) ? command : null;
|
|
9716
|
+
}
|
|
9717
|
+
const extensions = process.platform === "win32" ? splitPath(env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").map((item) => item.toLowerCase()) : [""];
|
|
9718
|
+
for (const dir of splitPath(connectorPath(env))) {
|
|
9719
|
+
for (const ext of extensions) {
|
|
9720
|
+
const candidate = resolve2(dir, process.platform === "win32" ? `${command}${ext}` : command);
|
|
9721
|
+
if (existsSync2(candidate)) return candidate;
|
|
9722
|
+
}
|
|
9723
|
+
}
|
|
9724
|
+
return null;
|
|
9725
|
+
}
|
|
9726
|
+
function commandExistsOnConnectorPath(command, _args = ["--version"]) {
|
|
9727
|
+
return Boolean(findCommandOnConnectorPath(command));
|
|
9728
|
+
}
|
|
9729
|
+
function nodeAssetInfo() {
|
|
9730
|
+
const nodePlatform = NODE_PLATFORM[process.platform];
|
|
9731
|
+
const nodeArch = NODE_ARCH[process.arch];
|
|
9732
|
+
if (!nodePlatform || !nodeArch) {
|
|
9733
|
+
throw new Error(`Unsupported managed Node platform: ${platform()}/${process.arch}`);
|
|
9734
|
+
}
|
|
9735
|
+
const ext = nodePlatform === "win" ? ".zip" : ".tar.xz";
|
|
9736
|
+
const filename = `node-v${CONNECTOR_MANAGED_NODE_VERSION}-${nodePlatform}-${nodeArch}${ext}`;
|
|
9737
|
+
const baseUrl = `https://nodejs.org/dist/v${CONNECTOR_MANAGED_NODE_VERSION}`;
|
|
9738
|
+
return {
|
|
9739
|
+
filename,
|
|
9740
|
+
url: `${baseUrl}/${filename}`,
|
|
9741
|
+
shasumsUrl: `${baseUrl}/SHASUMS256.txt`,
|
|
9742
|
+
ext
|
|
9743
|
+
};
|
|
9744
|
+
}
|
|
9745
|
+
function fetchBuffer2(url, redirects = 5) {
|
|
9746
|
+
return new Promise((resolvePromise, reject) => {
|
|
9747
|
+
if (redirects <= 0) {
|
|
9748
|
+
reject(new Error(`Too many redirects for ${url}`));
|
|
9749
|
+
return;
|
|
9750
|
+
}
|
|
9751
|
+
const request = httpsGet2(url, { headers: { "User-Agent": "shadowob-connector" } }, (res) => {
|
|
9752
|
+
const location = res.headers.location;
|
|
9753
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && location) {
|
|
9754
|
+
res.resume();
|
|
9755
|
+
resolvePromise(fetchBuffer2(new URL(location, url).toString(), redirects - 1));
|
|
9756
|
+
return;
|
|
9757
|
+
}
|
|
9758
|
+
if (res.statusCode !== 200) {
|
|
9759
|
+
res.resume();
|
|
9760
|
+
reject(new Error(`HTTP ${res.statusCode ?? "unknown"} for ${url}`));
|
|
9761
|
+
return;
|
|
9762
|
+
}
|
|
9763
|
+
const chunks = [];
|
|
9764
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
9765
|
+
res.on("end", () => resolvePromise(Buffer.concat(chunks)));
|
|
9766
|
+
res.on("error", reject);
|
|
9767
|
+
});
|
|
9768
|
+
request.on("error", reject);
|
|
9769
|
+
});
|
|
9770
|
+
}
|
|
9771
|
+
function verifyNodeArchive(filename, archive, shasums) {
|
|
9772
|
+
const line = shasums.split(/\r?\n/).find(
|
|
9773
|
+
(entry) => entry.trim().endsWith(` ${filename}`) || entry.trim().endsWith(` ${filename}`)
|
|
9774
|
+
);
|
|
9775
|
+
const expected = line?.trim().split(/\s+/)[0];
|
|
9776
|
+
if (!expected) throw new Error(`No Node.js SHA-256 found for ${filename}`);
|
|
9777
|
+
const actual = createHash2("sha256").update(archive).digest("hex");
|
|
9778
|
+
if (actual !== expected) {
|
|
9779
|
+
throw new Error(`Node.js SHA-256 mismatch for ${filename}: expected ${expected}, got ${actual}`);
|
|
9780
|
+
}
|
|
9781
|
+
}
|
|
9782
|
+
function runExtract(command, args) {
|
|
9783
|
+
const result = spawnSync2(command, args, { stdio: "ignore" });
|
|
9784
|
+
if (result.status !== 0) {
|
|
9785
|
+
throw new Error(`Failed to extract managed Node.js archive with ${command}`);
|
|
9786
|
+
}
|
|
9787
|
+
}
|
|
9788
|
+
function extractNodeArchive(archivePath, outputDir, ext) {
|
|
9789
|
+
if (ext === ".tar.xz") {
|
|
9790
|
+
runExtract("tar", ["-xJf", archivePath, "-C", outputDir]);
|
|
9791
|
+
return;
|
|
9792
|
+
}
|
|
9793
|
+
const result = spawnSync2(
|
|
9794
|
+
"powershell",
|
|
9795
|
+
["-NoProfile", "-Command", `Expand-Archive -Force '${archivePath}' '${outputDir}'`],
|
|
9796
|
+
{
|
|
9797
|
+
stdio: "ignore"
|
|
9798
|
+
}
|
|
9799
|
+
);
|
|
9800
|
+
if (result.status !== 0) runExtract("unzip", ["-q", archivePath, "-d", outputDir]);
|
|
9801
|
+
}
|
|
9802
|
+
async function ensureManagedNodeRuntime(options) {
|
|
9803
|
+
const root = managedNodeRoot();
|
|
9804
|
+
const binDir = managedNodeBinDir();
|
|
9805
|
+
const nodeBinary = resolve2(binDir, process.platform === "win32" ? "node.exe" : "node");
|
|
9806
|
+
const npmBinary = resolve2(binDir, process.platform === "win32" ? "npm.cmd" : "npm");
|
|
9807
|
+
if (existsSync2(nodeBinary) && existsSync2(npmBinary)) return { binDir, root };
|
|
9808
|
+
if (options.dryRun) {
|
|
9809
|
+
options.log?.(`[dry-run] install Node.js v${CONNECTOR_MANAGED_NODE_VERSION} -> ${root}`);
|
|
9810
|
+
return { binDir, root };
|
|
9811
|
+
}
|
|
9812
|
+
const asset = nodeAssetInfo();
|
|
9813
|
+
const parent = dirname2(root);
|
|
9814
|
+
const tmpDir = resolve2(parent, `_node-${process.pid}-${Date.now()}`);
|
|
9815
|
+
const archivePath = resolve2(parent, asset.filename);
|
|
9816
|
+
options.log?.(`[toolchain] Installing managed Node.js v${CONNECTOR_MANAGED_NODE_VERSION}`);
|
|
9817
|
+
mkdirSync2(parent, { recursive: true });
|
|
9818
|
+
const [archive, shasums] = await Promise.all([
|
|
9819
|
+
fetchBuffer2(asset.url),
|
|
9820
|
+
fetchBuffer2(asset.shasumsUrl).then((buffer) => buffer.toString("utf8"))
|
|
9821
|
+
]);
|
|
9822
|
+
verifyNodeArchive(asset.filename, archive, shasums);
|
|
9823
|
+
rmSync2(tmpDir, { recursive: true, force: true });
|
|
9824
|
+
mkdirSync2(tmpDir, { recursive: true });
|
|
9825
|
+
writeFileSync2(archivePath, archive);
|
|
9826
|
+
try {
|
|
9827
|
+
extractNodeArchive(archivePath, tmpDir, asset.ext);
|
|
9828
|
+
const extracted = readdirSync2(tmpDir, { withFileTypes: true }).find(
|
|
9829
|
+
(entry) => entry.isDirectory()
|
|
9830
|
+
);
|
|
9831
|
+
if (!extracted) throw new Error("Managed Node.js archive did not contain a directory");
|
|
9832
|
+
rmSync2(root, { recursive: true, force: true });
|
|
9833
|
+
renameSync2(resolve2(tmpDir, extracted.name), root);
|
|
9834
|
+
if (process.platform !== "win32") {
|
|
9835
|
+
chmodSync2(nodeBinary, 493);
|
|
9836
|
+
chmodSync2(npmBinary, 493);
|
|
9837
|
+
}
|
|
9838
|
+
} finally {
|
|
9839
|
+
rmSync2(archivePath, { force: true });
|
|
9840
|
+
rmSync2(tmpDir, { recursive: true, force: true });
|
|
9841
|
+
}
|
|
9842
|
+
return { binDir, root };
|
|
9843
|
+
}
|
|
9844
|
+
function shellCommandNeedsNpm(command) {
|
|
9845
|
+
return /(^|[;&|()\s])(?:npm|npx)(?:\.cmd)?(?:\s|$)/.test(command);
|
|
9846
|
+
}
|
|
9847
|
+
|
|
9433
9848
|
// src/cli.ts
|
|
9434
9849
|
var TARGETS = /* @__PURE__ */ new Set(["openclaw", "hermes", "cc-connect"]);
|
|
9435
9850
|
var COMMANDS = /* @__PURE__ */ new Set([
|
|
@@ -9486,6 +9901,10 @@ function usage() {
|
|
|
9486
9901
|
" --work-dir-map-file <path> Daemon-local JSON map for Buddy/runtime work directories",
|
|
9487
9902
|
" --project-name <name> cc-connect project name",
|
|
9488
9903
|
" --agent-type <type> cc-connect agent type, default codex",
|
|
9904
|
+
" --model-provider-base-url <url> OpenAI-compatible model provider base URL",
|
|
9905
|
+
" --model-provider-api-key <key> OpenAI-compatible model provider API key",
|
|
9906
|
+
" --model-provider-model <model> OpenAI-compatible model id",
|
|
9907
|
+
" --model-provider-id <id> Model provider id, default shadow-official",
|
|
9489
9908
|
" --json Print the full plan as JSON",
|
|
9490
9909
|
" --force Overwrite target config files when needed",
|
|
9491
9910
|
" --install Install connector runtime dependencies",
|
|
@@ -9530,6 +9949,11 @@ function parseArgs(args) {
|
|
|
9530
9949
|
workDirMapFile: readOption(optionArgs, "--work-dir-map-file"),
|
|
9531
9950
|
projectName: readOption(optionArgs, "--project-name"),
|
|
9532
9951
|
agentType: readOption(optionArgs, "--agent-type"),
|
|
9952
|
+
modelProviderId: readOption(optionArgs, "--model-provider-id"),
|
|
9953
|
+
modelProviderLabel: readOption(optionArgs, "--model-provider-label"),
|
|
9954
|
+
modelProviderBaseUrl: readOption(optionArgs, "--model-provider-base-url"),
|
|
9955
|
+
modelProviderApiKey: readOption(optionArgs, "--model-provider-api-key"),
|
|
9956
|
+
modelProviderModel: readOption(optionArgs, "--model-provider-model"),
|
|
9533
9957
|
json: hasFlag(optionArgs, "--json"),
|
|
9534
9958
|
force: hasFlag(optionArgs, "--force"),
|
|
9535
9959
|
install,
|
|
@@ -9541,7 +9965,11 @@ function parseArgs(args) {
|
|
|
9541
9965
|
}
|
|
9542
9966
|
function printPlan(options) {
|
|
9543
9967
|
const target = requireTarget(options);
|
|
9544
|
-
const plan = createConnectorPlan({
|
|
9968
|
+
const plan = createConnectorPlan({
|
|
9969
|
+
...options,
|
|
9970
|
+
target,
|
|
9971
|
+
modelProvider: modelProviderFromOptions(options)
|
|
9972
|
+
});
|
|
9545
9973
|
if (options.json) {
|
|
9546
9974
|
console.log(JSON.stringify(plan, null, 2));
|
|
9547
9975
|
return;
|
|
@@ -9557,19 +9985,19 @@ function printPlan(options) {
|
|
|
9557
9985
|
console.log(block.content);
|
|
9558
9986
|
}
|
|
9559
9987
|
}
|
|
9560
|
-
function runShell(command, dryRun) {
|
|
9988
|
+
function runShell(command, dryRun, env = connectorProcessEnv()) {
|
|
9561
9989
|
if (dryRun) {
|
|
9562
9990
|
console.log(`[dry-run] ${command}`);
|
|
9563
9991
|
return;
|
|
9564
9992
|
}
|
|
9565
|
-
const result =
|
|
9993
|
+
const result = spawnSync3(command, { shell: true, stdio: "inherit", env });
|
|
9566
9994
|
if (result.status !== 0) {
|
|
9567
9995
|
throw new Error(`Command failed with exit code ${result.status ?? "unknown"}: ${command}`);
|
|
9568
9996
|
}
|
|
9569
9997
|
}
|
|
9570
|
-
function runShellQuiet(command, dryRun) {
|
|
9998
|
+
function runShellQuiet(command, dryRun, env = connectorProcessEnv()) {
|
|
9571
9999
|
if (dryRun) return;
|
|
9572
|
-
const result =
|
|
10000
|
+
const result = spawnSync3(command, { shell: true, encoding: "utf8", env });
|
|
9573
10001
|
if (result.status !== 0) {
|
|
9574
10002
|
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
9575
10003
|
throw new Error(
|
|
@@ -9577,13 +10005,19 @@ function runShellQuiet(command, dryRun) {
|
|
|
9577
10005
|
);
|
|
9578
10006
|
}
|
|
9579
10007
|
}
|
|
10008
|
+
async function envForShellCommand(command, dryRun) {
|
|
10009
|
+
if (shellCommandNeedsNpm(command) && !commandExists("npm")) {
|
|
10010
|
+
await ensureManagedNodeRuntime({ dryRun, log: (message) => console.log(message) });
|
|
10011
|
+
}
|
|
10012
|
+
return connectorProcessEnv();
|
|
10013
|
+
}
|
|
9580
10014
|
function runBinary(binaryPath, args, dryRun) {
|
|
9581
10015
|
const rendered = [binaryPath, ...args].map((arg) => /^[A-Za-z0-9_./:@=-]+$/.test(arg) ? arg : JSON.stringify(arg)).join(" ");
|
|
9582
10016
|
if (dryRun) {
|
|
9583
10017
|
console.log(`[dry-run] ${rendered}`);
|
|
9584
10018
|
return;
|
|
9585
10019
|
}
|
|
9586
|
-
const result =
|
|
10020
|
+
const result = spawnSync3(binaryPath, args, { stdio: "inherit", env: connectorProcessEnv() });
|
|
9587
10021
|
if (result.status !== 0) {
|
|
9588
10022
|
throw new Error(`Command failed with exit code ${result.status ?? "unknown"}: ${rendered}`);
|
|
9589
10023
|
}
|
|
@@ -9593,21 +10027,21 @@ function writeFile(path, content, dryRun) {
|
|
|
9593
10027
|
console.log(`[dry-run] write ${path}`);
|
|
9594
10028
|
return;
|
|
9595
10029
|
}
|
|
9596
|
-
|
|
9597
|
-
|
|
10030
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
10031
|
+
writeFileSync3(path, content.endsWith("\n") ? content : `${content}
|
|
9598
10032
|
`);
|
|
9599
10033
|
}
|
|
9600
10034
|
function packageRoot() {
|
|
9601
|
-
return
|
|
10035
|
+
return resolve3(dirname3(fileURLToPath(import.meta.url)), "..");
|
|
9602
10036
|
}
|
|
9603
|
-
function
|
|
9604
|
-
return value.startsWith("~/") ?
|
|
10037
|
+
function expandHome3(value) {
|
|
10038
|
+
return value.startsWith("~/") ? resolve3(homedir3(), value.slice(2)) : resolve3(value);
|
|
9605
10039
|
}
|
|
9606
10040
|
function readExisting(path) {
|
|
9607
|
-
return
|
|
10041
|
+
return existsSync3(path) ? readFileSync(path, "utf8") : "";
|
|
9608
10042
|
}
|
|
9609
10043
|
function resolveOpenClawConfigPath(options) {
|
|
9610
|
-
return
|
|
10044
|
+
return expandHome3(
|
|
9611
10045
|
options.openclawConfig ?? process.env.OPENCLAW_CONFIG ?? process.env.OPENCLAW_CONFIG_PATH ?? DEFAULT_OPENCLAW_CONFIG
|
|
9612
10046
|
);
|
|
9613
10047
|
}
|
|
@@ -9623,6 +10057,19 @@ function shellQuote2(value) {
|
|
|
9623
10057
|
function tokenForCommand(options) {
|
|
9624
10058
|
return options.token.trim() || "<BUDDY_TOKEN>";
|
|
9625
10059
|
}
|
|
10060
|
+
function modelProviderFromOptions(options) {
|
|
10061
|
+
const baseUrl = options.modelProviderBaseUrl?.trim();
|
|
10062
|
+
const apiKey = options.modelProviderApiKey?.trim();
|
|
10063
|
+
const model = options.modelProviderModel?.trim();
|
|
10064
|
+
if (!baseUrl || !apiKey || !model) return void 0;
|
|
10065
|
+
return {
|
|
10066
|
+
id: options.modelProviderId?.trim() || "shadow-official",
|
|
10067
|
+
label: options.modelProviderLabel?.trim() || "Shadow official LLM proxy",
|
|
10068
|
+
baseUrl,
|
|
10069
|
+
apiKey,
|
|
10070
|
+
model
|
|
10071
|
+
};
|
|
10072
|
+
}
|
|
9626
10073
|
function connectorCommand(command, target, options, extras = []) {
|
|
9627
10074
|
const parts = ["shadowob-connector", command, "--target", target];
|
|
9628
10075
|
if (command !== "doctor" && command !== "status") {
|
|
@@ -9637,20 +10084,26 @@ function connectorCommand(command, target, options, extras = []) {
|
|
|
9637
10084
|
return parts.map(shellQuote2).join(" ");
|
|
9638
10085
|
}
|
|
9639
10086
|
function commandExists(command) {
|
|
9640
|
-
|
|
9641
|
-
return result.status === 0;
|
|
10087
|
+
return commandExistsOnConnectorPath(command);
|
|
9642
10088
|
}
|
|
9643
10089
|
function writeExecutable(path, content, dryRun) {
|
|
9644
10090
|
writeFile(path, content, dryRun);
|
|
9645
10091
|
if (dryRun) return;
|
|
9646
|
-
|
|
10092
|
+
chmodSync3(path, 493);
|
|
9647
10093
|
}
|
|
9648
10094
|
function ensureNpxShim(options) {
|
|
9649
|
-
|
|
9650
|
-
const
|
|
9651
|
-
|
|
10095
|
+
const localBin = resolve3(homedir3(), ".local/bin");
|
|
10096
|
+
const target = resolve3(localBin, options.command);
|
|
10097
|
+
if (commandExists(options.command) && existsSync3(target)) return;
|
|
10098
|
+
const pathPrefix = [localBin, nodeGlobalBinDir(), managedNodeBinDir()].map(shellQuote2).join(":");
|
|
9652
10099
|
const content = [
|
|
9653
10100
|
"#!/usr/bin/env sh",
|
|
10101
|
+
`PATH=${pathPrefix}:$PATH`,
|
|
10102
|
+
"export PATH",
|
|
10103
|
+
`if command -v ${options.binaryName} >/dev/null 2>&1; then`,
|
|
10104
|
+
` resolved="$(command -v ${options.binaryName})"`,
|
|
10105
|
+
' if [ "$resolved" != "$0" ]; then exec "$resolved" "$@"; fi',
|
|
10106
|
+
"fi",
|
|
9654
10107
|
`exec npx -y ${options.packageSpec} ${options.binaryName === options.command ? "" : options.binaryName} "$@"`,
|
|
9655
10108
|
""
|
|
9656
10109
|
].join("\n").replace(' "$@"', ' "$@"');
|
|
@@ -9661,11 +10114,26 @@ function ensureNpxShim(options) {
|
|
|
9661
10114
|
console.log(`Note: add ${localBin} to PATH so agents can run ${options.command}`);
|
|
9662
10115
|
}
|
|
9663
10116
|
}
|
|
10117
|
+
async function installShadowNpmPackages(options) {
|
|
10118
|
+
if (commandExists("shadowob") && commandExists("shadowob-connector")) return;
|
|
10119
|
+
if (!commandExists("npm")) {
|
|
10120
|
+
await ensureManagedNodeRuntime({
|
|
10121
|
+
dryRun: options.dryRun,
|
|
10122
|
+
log: (message) => console.log(message)
|
|
10123
|
+
});
|
|
10124
|
+
}
|
|
10125
|
+
console.log("Applying: Install Shadow CLI packages");
|
|
10126
|
+
runShellQuiet(
|
|
10127
|
+
`npm install -g ${SHADOW_CLI_PACKAGE} ${SHADOW_CONNECTOR_PACKAGE}`,
|
|
10128
|
+
options.dryRun,
|
|
10129
|
+
connectorProcessEnv()
|
|
10130
|
+
);
|
|
10131
|
+
}
|
|
9664
10132
|
function shadowCliProfileName(options) {
|
|
9665
10133
|
return options.projectName?.trim() || "shadow-buddy";
|
|
9666
10134
|
}
|
|
9667
10135
|
function writeShadowCliProfile(options) {
|
|
9668
|
-
const configPath =
|
|
10136
|
+
const configPath = resolve3(homedir3(), ".shadowob/shadowob.config.json");
|
|
9669
10137
|
const current = (() => {
|
|
9670
10138
|
try {
|
|
9671
10139
|
return JSON.parse(readExisting(configPath));
|
|
@@ -9690,37 +10158,38 @@ function writeShadowCliProfile(options) {
|
|
|
9690
10158
|
}
|
|
9691
10159
|
function shadowobSkillMarkdown() {
|
|
9692
10160
|
const candidates = [
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
10161
|
+
resolve3(packageRoot(), "skills/shadowob/SKILL.md"),
|
|
10162
|
+
resolve3(process.cwd(), "skills/shadowob-cli/SKILL.md"),
|
|
10163
|
+
resolve3(process.cwd(), "packages/openclaw-shadowob/skills/shadowob/SKILL.md")
|
|
9696
10164
|
];
|
|
9697
10165
|
let currentDir = packageRoot();
|
|
9698
10166
|
while (true) {
|
|
9699
|
-
candidates.push(
|
|
9700
|
-
const parentDir =
|
|
10167
|
+
candidates.push(resolve3(currentDir, "skills/shadowob-cli/SKILL.md"));
|
|
10168
|
+
const parentDir = dirname3(currentDir);
|
|
9701
10169
|
if (parentDir === currentDir) break;
|
|
9702
10170
|
currentDir = parentDir;
|
|
9703
10171
|
}
|
|
9704
|
-
const found = candidates.find((candidate) =>
|
|
10172
|
+
const found = candidates.find((candidate) => existsSync3(candidate));
|
|
9705
10173
|
if (!found) throw new Error("Cannot find bundled Shadow CLI skill");
|
|
9706
10174
|
return readFileSync(found, "utf8");
|
|
9707
10175
|
}
|
|
9708
10176
|
function shadowobSkillTargets(options) {
|
|
9709
|
-
const hermesDir =
|
|
10177
|
+
const hermesDir = expandHome3(options.hermesHome ?? process.env.HERMES_HOME ?? "~/.hermes");
|
|
9710
10178
|
return Array.from(
|
|
9711
10179
|
/* @__PURE__ */ new Set([
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
|
|
10180
|
+
resolve3(homedir3(), ".shadowob/skills/shadowob/SKILL.md"),
|
|
10181
|
+
resolve3(homedir3(), ".agents/skills/shadowob/SKILL.md"),
|
|
10182
|
+
resolve3(homedir3(), ".codex/skills/shadowob/SKILL.md"),
|
|
10183
|
+
resolve3(homedir3(), ".claude/skills/shadowob/SKILL.md"),
|
|
10184
|
+
resolve3(homedir3(), ".gemini/skills/shadowob/SKILL.md"),
|
|
10185
|
+
resolve3(homedir3(), ".opencode/skills/shadowob/SKILL.md"),
|
|
10186
|
+
resolve3(homedir3(), ".openclaw/skills/shadowob/SKILL.md"),
|
|
10187
|
+
resolve3(hermesDir, "skills/shadowob/SKILL.md")
|
|
9720
10188
|
])
|
|
9721
10189
|
);
|
|
9722
10190
|
}
|
|
9723
|
-
function installShadowCliAndSkills(options) {
|
|
10191
|
+
async function installShadowCliAndSkills(options) {
|
|
10192
|
+
await installShadowNpmPackages(options);
|
|
9724
10193
|
ensureNpxShim({
|
|
9725
10194
|
command: "shadowob",
|
|
9726
10195
|
packageSpec: SHADOW_CLI_PACKAGE,
|
|
@@ -9779,8 +10248,8 @@ function diagnoseCommon(options) {
|
|
|
9779
10248
|
"Run fix/update to install the ~/.local/bin/shadowob-connector shim."
|
|
9780
10249
|
)
|
|
9781
10250
|
];
|
|
9782
|
-
const profilePath =
|
|
9783
|
-
if (!
|
|
10251
|
+
const profilePath = resolve3(homedir3(), ".shadowob/shadowob.config.json");
|
|
10252
|
+
if (!existsSync3(profilePath)) {
|
|
9784
10253
|
checks.push(
|
|
9785
10254
|
check(
|
|
9786
10255
|
"common",
|
|
@@ -9805,7 +10274,7 @@ function diagnoseCommon(options) {
|
|
|
9805
10274
|
);
|
|
9806
10275
|
}
|
|
9807
10276
|
const skillTargets = shadowobSkillTargets(options);
|
|
9808
|
-
const installed = skillTargets.filter((target) =>
|
|
10277
|
+
const installed = skillTargets.filter((target) => existsSync3(target)).length;
|
|
9809
10278
|
checks.push(
|
|
9810
10279
|
check(
|
|
9811
10280
|
"common",
|
|
@@ -9828,7 +10297,7 @@ function diagnoseOpenClaw(options) {
|
|
|
9828
10297
|
"Install OpenClaw before starting the gateway."
|
|
9829
10298
|
)
|
|
9830
10299
|
];
|
|
9831
|
-
if (!
|
|
10300
|
+
if (!existsSync3(configPath)) {
|
|
9832
10301
|
checks.push(
|
|
9833
10302
|
check(
|
|
9834
10303
|
"openclaw",
|
|
@@ -9884,10 +10353,10 @@ function diagnoseOpenClaw(options) {
|
|
|
9884
10353
|
return checks;
|
|
9885
10354
|
}
|
|
9886
10355
|
function diagnoseHermes(options) {
|
|
9887
|
-
const hermesDir =
|
|
9888
|
-
const pluginTarget =
|
|
9889
|
-
const envPath =
|
|
9890
|
-
const configPath =
|
|
10356
|
+
const hermesDir = expandHome3(options.hermesHome ?? process.env.HERMES_HOME ?? "~/.hermes");
|
|
10357
|
+
const pluginTarget = resolve3(hermesDir, "plugins/shadowob");
|
|
10358
|
+
const envPath = resolve3(hermesDir, ".env");
|
|
10359
|
+
const configPath = resolve3(hermesDir, "config.yaml");
|
|
9891
10360
|
const checks = [
|
|
9892
10361
|
check(
|
|
9893
10362
|
"hermes",
|
|
@@ -9898,9 +10367,9 @@ function diagnoseHermes(options) {
|
|
|
9898
10367
|
),
|
|
9899
10368
|
check(
|
|
9900
10369
|
"hermes",
|
|
9901
|
-
|
|
10370
|
+
existsSync3(pluginTarget) ? "ok" : "fail",
|
|
9902
10371
|
"Hermes Shadow plugin",
|
|
9903
|
-
|
|
10372
|
+
existsSync3(pluginTarget) ? `${pluginTarget} exists` : `${pluginTarget} is missing`,
|
|
9904
10373
|
"Run fix/update."
|
|
9905
10374
|
)
|
|
9906
10375
|
];
|
|
@@ -9910,11 +10379,11 @@ function diagnoseHermes(options) {
|
|
|
9910
10379
|
"hermes",
|
|
9911
10380
|
env.includes("SHADOW_TOKEN=") && env.includes("SHADOW_BASE_URL=") ? "ok" : "fail",
|
|
9912
10381
|
"Hermes environment",
|
|
9913
|
-
|
|
10382
|
+
existsSync3(envPath) ? "SHADOW_TOKEN and SHADOW_BASE_URL are present" : `${envPath} does not exist`,
|
|
9914
10383
|
"Run fix/update with --token and --server-url."
|
|
9915
10384
|
)
|
|
9916
10385
|
);
|
|
9917
|
-
if (!
|
|
10386
|
+
if (!existsSync3(configPath)) {
|
|
9918
10387
|
checks.push(
|
|
9919
10388
|
check("hermes", "fail", "Hermes config", `${configPath} does not exist`, "Run fix/update.")
|
|
9920
10389
|
);
|
|
@@ -9947,7 +10416,7 @@ function diagnoseHermes(options) {
|
|
|
9947
10416
|
return checks;
|
|
9948
10417
|
}
|
|
9949
10418
|
function diagnoseCcConnect(options) {
|
|
9950
|
-
const configPath =
|
|
10419
|
+
const configPath = resolve3(homedir3(), ".cc-connect/config.toml");
|
|
9951
10420
|
const binary = getCcConnectBinaryStatus();
|
|
9952
10421
|
const checks = [
|
|
9953
10422
|
check(
|
|
@@ -9958,7 +10427,7 @@ function diagnoseCcConnect(options) {
|
|
|
9958
10427
|
"Run fix/update with --install."
|
|
9959
10428
|
)
|
|
9960
10429
|
];
|
|
9961
|
-
if (!
|
|
10430
|
+
if (!existsSync3(configPath)) {
|
|
9962
10431
|
checks.push(
|
|
9963
10432
|
check(
|
|
9964
10433
|
"cc-connect",
|
|
@@ -10039,7 +10508,7 @@ function printDiagnostics(options, mode) {
|
|
|
10039
10508
|
return !checks.some((item) => item.status === "fail");
|
|
10040
10509
|
}
|
|
10041
10510
|
function firstExistingPath(paths) {
|
|
10042
|
-
return paths.find((path) =>
|
|
10511
|
+
return paths.find((path) => existsSync3(path));
|
|
10043
10512
|
}
|
|
10044
10513
|
function openClawConfigCandidates(options) {
|
|
10045
10514
|
return Array.from(
|
|
@@ -10050,12 +10519,12 @@ function openClawConfigCandidates(options) {
|
|
|
10050
10519
|
process.env.OPENCLAW_CONFIG_PATH,
|
|
10051
10520
|
DEFAULT_OPENCLAW_CONFIG,
|
|
10052
10521
|
LEGACY_OPENCLAW_CONFIG
|
|
10053
|
-
].filter((value) => !!value?.trim()).map(
|
|
10522
|
+
].filter((value) => !!value?.trim()).map(expandHome3)
|
|
10054
10523
|
)
|
|
10055
10524
|
);
|
|
10056
10525
|
}
|
|
10057
10526
|
function ccConnectScanExtras(options) {
|
|
10058
|
-
const configPath =
|
|
10527
|
+
const configPath = resolve3(homedir3(), ".cc-connect/config.toml");
|
|
10059
10528
|
const fallback = [
|
|
10060
10529
|
"--work-dir",
|
|
10061
10530
|
options.workDir?.trim() || ".",
|
|
@@ -10064,14 +10533,14 @@ function ccConnectScanExtras(options) {
|
|
|
10064
10533
|
"--agent-type",
|
|
10065
10534
|
options.agentType?.trim() || "codex"
|
|
10066
10535
|
];
|
|
10067
|
-
if (!
|
|
10536
|
+
if (!existsSync3(configPath)) return fallback;
|
|
10068
10537
|
try {
|
|
10069
10538
|
const root = parse(readExisting(configPath));
|
|
10070
10539
|
const projects = Array.isArray(root.projects) ? root.projects : [];
|
|
10071
10540
|
const configuredProject = projects.find((project2) => {
|
|
10072
10541
|
const platformsValue = asObject(project2).platforms;
|
|
10073
10542
|
const platforms = Array.isArray(platformsValue) ? platformsValue : [];
|
|
10074
|
-
return platforms.some((
|
|
10543
|
+
return platforms.some((platform3) => asObject(platform3).type === "shadowob");
|
|
10075
10544
|
}) ?? projects[0];
|
|
10076
10545
|
const project = asObject(configuredProject);
|
|
10077
10546
|
const agent = asObject(project.agent);
|
|
@@ -10106,19 +10575,19 @@ function scanOpenClaw(options) {
|
|
|
10106
10575
|
};
|
|
10107
10576
|
}
|
|
10108
10577
|
function scanHermes(options) {
|
|
10109
|
-
const hermesDir =
|
|
10110
|
-
const configPath =
|
|
10578
|
+
const hermesDir = expandHome3(options.hermesHome ?? process.env.HERMES_HOME ?? "~/.hermes");
|
|
10579
|
+
const configPath = resolve3(hermesDir, "config.yaml");
|
|
10111
10580
|
const evidence = [];
|
|
10112
10581
|
if (commandExists("hermes")) evidence.push("hermes command is on PATH");
|
|
10113
|
-
if (
|
|
10114
|
-
if (
|
|
10115
|
-
evidence.push(`shadowob plugin found under ${
|
|
10582
|
+
if (existsSync3(configPath)) evidence.push(`config found at ${configPath}`);
|
|
10583
|
+
if (existsSync3(resolve3(hermesDir, "plugins/shadowob"))) {
|
|
10584
|
+
evidence.push(`shadowob plugin found under ${resolve3(hermesDir, "plugins/shadowob")}`);
|
|
10116
10585
|
}
|
|
10117
10586
|
return {
|
|
10118
10587
|
target: "hermes",
|
|
10119
10588
|
detected: evidence.length > 0,
|
|
10120
10589
|
evidence,
|
|
10121
|
-
configPath:
|
|
10590
|
+
configPath: existsSync3(configPath) ? configPath : void 0,
|
|
10122
10591
|
connectCommand: connectorCommand("connect", "hermes", options),
|
|
10123
10592
|
updateCommand: connectorCommand("update", "hermes", options),
|
|
10124
10593
|
doctorCommand: connectorCommand("doctor", "hermes", options),
|
|
@@ -10126,18 +10595,18 @@ function scanHermes(options) {
|
|
|
10126
10595
|
};
|
|
10127
10596
|
}
|
|
10128
10597
|
function scanCcConnect(options) {
|
|
10129
|
-
const configPath =
|
|
10598
|
+
const configPath = resolve3(homedir3(), ".cc-connect/config.toml");
|
|
10130
10599
|
const binary = getCcConnectBinaryStatus();
|
|
10131
10600
|
const evidence = [];
|
|
10132
10601
|
if (commandExists("cc-connect")) evidence.push("cc-connect command is on PATH");
|
|
10133
10602
|
if (binary.usable) evidence.push(`Shadow fork binary found at ${binary.binaryPath}`);
|
|
10134
|
-
if (
|
|
10603
|
+
if (existsSync3(configPath)) evidence.push(`config found at ${configPath}`);
|
|
10135
10604
|
const extras = ccConnectScanExtras(options);
|
|
10136
10605
|
return {
|
|
10137
10606
|
target: "cc-connect",
|
|
10138
10607
|
detected: evidence.length > 0,
|
|
10139
10608
|
evidence,
|
|
10140
|
-
configPath:
|
|
10609
|
+
configPath: existsSync3(configPath) ? configPath : void 0,
|
|
10141
10610
|
connectCommand: connectorCommand("connect", "cc-connect", options, extras),
|
|
10142
10611
|
updateCommand: connectorCommand("update", "cc-connect", options, extras),
|
|
10143
10612
|
doctorCommand: connectorCommand("doctor", "cc-connect", options),
|
|
@@ -10190,7 +10659,7 @@ function readDaemonWorkDirMap(options) {
|
|
|
10190
10659
|
return { buddies: {}, runtimes: {}, defaultWorkDir: "" };
|
|
10191
10660
|
}
|
|
10192
10661
|
try {
|
|
10193
|
-
const filePath =
|
|
10662
|
+
const filePath = expandHome3(options.workDirMapFile);
|
|
10194
10663
|
const root = JSON.parse(readFileSync(filePath, "utf8"));
|
|
10195
10664
|
return {
|
|
10196
10665
|
buddies: stringRecord(root.buddies),
|
|
@@ -10218,14 +10687,14 @@ function resolveDaemonWorkDir(job, options) {
|
|
|
10218
10687
|
}
|
|
10219
10688
|
function packageVersion() {
|
|
10220
10689
|
try {
|
|
10221
|
-
const json = JSON.parse(readFileSync(
|
|
10690
|
+
const json = JSON.parse(readFileSync(resolve3(packageRoot(), "package.json"), "utf8"));
|
|
10222
10691
|
return json.version ?? "dev";
|
|
10223
10692
|
} catch {
|
|
10224
10693
|
return "dev";
|
|
10225
10694
|
}
|
|
10226
10695
|
}
|
|
10227
10696
|
function commandVersionWithArgs(command, args = ["--version"]) {
|
|
10228
|
-
const result =
|
|
10697
|
+
const result = spawnSync3(command, args, { encoding: "utf8", env: connectorProcessEnv() });
|
|
10229
10698
|
if (result.status !== 0) return { ok: false };
|
|
10230
10699
|
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
10231
10700
|
return { ok: true, version: output.split(/\r?\n/)[0]?.slice(0, 120) || null };
|
|
@@ -10310,7 +10779,7 @@ function printRuntimeScan(options) {
|
|
|
10310
10779
|
}
|
|
10311
10780
|
}
|
|
10312
10781
|
}
|
|
10313
|
-
function installRuntime(options) {
|
|
10782
|
+
async function installRuntime(options) {
|
|
10314
10783
|
const runtime = connectorRuntimeById(options.runtimeId);
|
|
10315
10784
|
if (!runtime) {
|
|
10316
10785
|
throw new Error(
|
|
@@ -10323,10 +10792,12 @@ function installRuntime(options) {
|
|
|
10323
10792
|
`No install command is available for ${runtime.label}. See ${runtime.install.helpUrl}`
|
|
10324
10793
|
);
|
|
10325
10794
|
}
|
|
10795
|
+
const command = commands[0];
|
|
10796
|
+
const env = await envForShellCommand(command, options.dryRun);
|
|
10326
10797
|
if (options.json) {
|
|
10327
|
-
runShellQuiet(
|
|
10798
|
+
runShellQuiet(command, options.dryRun, env);
|
|
10328
10799
|
} else {
|
|
10329
|
-
runShell(
|
|
10800
|
+
runShell(command, options.dryRun, env);
|
|
10330
10801
|
}
|
|
10331
10802
|
const detected = detectCatalogRuntime(runtime);
|
|
10332
10803
|
if (options.json) {
|
|
@@ -10379,7 +10850,7 @@ async function heartbeat(options) {
|
|
|
10379
10850
|
method: "POST",
|
|
10380
10851
|
body: JSON.stringify({
|
|
10381
10852
|
hostname: hostname(),
|
|
10382
|
-
os:
|
|
10853
|
+
os: platform2(),
|
|
10383
10854
|
arch: arch(),
|
|
10384
10855
|
daemonVersion: packageVersion(),
|
|
10385
10856
|
runtimes
|
|
@@ -10407,6 +10878,7 @@ function startDetached(binaryPath, args, dryRun) {
|
|
|
10407
10878
|
}
|
|
10408
10879
|
const child = spawn(binaryPath, args, {
|
|
10409
10880
|
detached: true,
|
|
10881
|
+
env: connectorProcessEnv(),
|
|
10410
10882
|
stdio: "ignore"
|
|
10411
10883
|
});
|
|
10412
10884
|
child.unref();
|
|
@@ -10420,7 +10892,7 @@ async function applyDaemonJob(job, baseOptions) {
|
|
|
10420
10892
|
const projectName = payload.projectName?.trim() || payload.buddy?.username || "shadow-buddy";
|
|
10421
10893
|
const workDir = resolveDaemonWorkDir(job, baseOptions);
|
|
10422
10894
|
if (runtimeId === "openclaw") {
|
|
10423
|
-
applyOpenClaw(
|
|
10895
|
+
await applyOpenClaw(
|
|
10424
10896
|
{
|
|
10425
10897
|
...baseOptions,
|
|
10426
10898
|
target: "openclaw",
|
|
@@ -10428,6 +10900,11 @@ async function applyDaemonJob(job, baseOptions) {
|
|
|
10428
10900
|
token: payload.token,
|
|
10429
10901
|
projectName,
|
|
10430
10902
|
workDir,
|
|
10903
|
+
modelProviderId: payload.modelProvider?.id,
|
|
10904
|
+
modelProviderLabel: payload.modelProvider?.label,
|
|
10905
|
+
modelProviderBaseUrl: payload.modelProvider?.baseUrl,
|
|
10906
|
+
modelProviderApiKey: payload.modelProvider?.apiKey,
|
|
10907
|
+
modelProviderModel: payload.modelProvider?.model,
|
|
10431
10908
|
install: true
|
|
10432
10909
|
},
|
|
10433
10910
|
{ restart: true }
|
|
@@ -10435,13 +10912,18 @@ async function applyDaemonJob(job, baseOptions) {
|
|
|
10435
10912
|
return { runtimeId, target: "openclaw" };
|
|
10436
10913
|
}
|
|
10437
10914
|
if (runtimeId === "hermes") {
|
|
10438
|
-
applyHermes({
|
|
10915
|
+
await applyHermes({
|
|
10439
10916
|
...baseOptions,
|
|
10440
10917
|
target: "hermes",
|
|
10441
10918
|
serverUrl: payload.serverUrl,
|
|
10442
10919
|
token: payload.token,
|
|
10443
10920
|
projectName,
|
|
10444
10921
|
workDir,
|
|
10922
|
+
modelProviderId: payload.modelProvider?.id,
|
|
10923
|
+
modelProviderLabel: payload.modelProvider?.label,
|
|
10924
|
+
modelProviderBaseUrl: payload.modelProvider?.baseUrl,
|
|
10925
|
+
modelProviderApiKey: payload.modelProvider?.apiKey,
|
|
10926
|
+
modelProviderModel: payload.modelProvider?.model,
|
|
10445
10927
|
install: true,
|
|
10446
10928
|
start: false
|
|
10447
10929
|
});
|
|
@@ -10456,6 +10938,11 @@ async function applyDaemonJob(job, baseOptions) {
|
|
|
10456
10938
|
projectName,
|
|
10457
10939
|
workDir,
|
|
10458
10940
|
agentType: ccAgentTypeForRuntime(runtimeId),
|
|
10941
|
+
modelProviderId: payload.modelProvider?.id,
|
|
10942
|
+
modelProviderLabel: payload.modelProvider?.label,
|
|
10943
|
+
modelProviderBaseUrl: payload.modelProvider?.baseUrl,
|
|
10944
|
+
modelProviderApiKey: payload.modelProvider?.apiKey,
|
|
10945
|
+
modelProviderModel: payload.modelProvider?.model,
|
|
10459
10946
|
install: true,
|
|
10460
10947
|
start: false
|
|
10461
10948
|
});
|
|
@@ -10511,22 +10998,24 @@ async function runDaemon(options) {
|
|
|
10511
10998
|
}
|
|
10512
10999
|
function hermesPluginSource() {
|
|
10513
11000
|
const candidates = [
|
|
10514
|
-
|
|
10515
|
-
|
|
11001
|
+
resolve3(packageRoot(), "hermes-shadowob-plugin"),
|
|
11002
|
+
resolve3(process.cwd(), "packages/connector/hermes-shadowob-plugin")
|
|
10516
11003
|
];
|
|
10517
|
-
const found = candidates.find((candidate) =>
|
|
11004
|
+
const found = candidates.find((candidate) => existsSync3(candidate));
|
|
10518
11005
|
if (!found) throw new Error("Cannot find bundled hermes-shadowob-plugin directory");
|
|
10519
11006
|
return found;
|
|
10520
11007
|
}
|
|
10521
|
-
function applyOpenClaw(options, behavior = { restart: true }) {
|
|
11008
|
+
async function applyOpenClaw(options, behavior = { restart: true }) {
|
|
10522
11009
|
const target = requireTarget(options);
|
|
10523
|
-
const
|
|
11010
|
+
const modelProvider = modelProviderFromOptions(options);
|
|
11011
|
+
const plan = createConnectorPlan({ ...options, target, modelProvider });
|
|
10524
11012
|
const configPath = resolveOpenClawConfigPath(options);
|
|
10525
|
-
installShadowCliAndSkills(options);
|
|
11013
|
+
await installShadowCliAndSkills(options);
|
|
10526
11014
|
console.log(`Applying: Merge OpenClaw config ${configPath}`);
|
|
10527
11015
|
const next = mergeOpenClawConfigContent(readExisting(configPath), {
|
|
10528
11016
|
token: options.token,
|
|
10529
|
-
serverUrl: normalizeServerUrl2(options.serverUrl)
|
|
11017
|
+
serverUrl: normalizeServerUrl2(options.serverUrl),
|
|
11018
|
+
modelProvider
|
|
10530
11019
|
});
|
|
10531
11020
|
writeFile(configPath, next, options.dryRun);
|
|
10532
11021
|
if (options.install) {
|
|
@@ -10539,35 +11028,38 @@ function applyOpenClaw(options, behavior = { restart: true }) {
|
|
|
10539
11028
|
runShell(restart.command, options.dryRun);
|
|
10540
11029
|
}
|
|
10541
11030
|
}
|
|
10542
|
-
function applyHermes(options) {
|
|
11031
|
+
async function applyHermes(options) {
|
|
10543
11032
|
const target = requireTarget(options);
|
|
10544
|
-
const
|
|
10545
|
-
const
|
|
10546
|
-
const
|
|
10547
|
-
const
|
|
10548
|
-
const
|
|
11033
|
+
const modelProvider = modelProviderFromOptions(options);
|
|
11034
|
+
const plan = createConnectorPlan({ ...options, target, modelProvider });
|
|
11035
|
+
const hermesDir = expandHome3(options.hermesHome ?? process.env.HERMES_HOME ?? "~/.hermes");
|
|
11036
|
+
const pluginTarget = resolve3(hermesDir, "plugins/shadowob");
|
|
11037
|
+
const envPath = resolve3(hermesDir, ".env");
|
|
11038
|
+
const configPath = resolve3(hermesDir, "config.yaml");
|
|
10549
11039
|
const envBlock = plan.configBlocks.find((block) => block.label === "~/.hermes/.env");
|
|
10550
11040
|
if (!envBlock) throw new Error("Hermes plan is missing config blocks");
|
|
10551
|
-
installShadowCliAndSkills(options);
|
|
11041
|
+
await installShadowCliAndSkills(options);
|
|
10552
11042
|
if (options.dryRun) {
|
|
10553
11043
|
console.log(`[dry-run] copy ${hermesPluginSource()} -> ${pluginTarget}`);
|
|
10554
11044
|
} else {
|
|
10555
|
-
|
|
11045
|
+
mkdirSync3(resolve3(hermesDir, "plugins"), { recursive: true });
|
|
10556
11046
|
cpSync(hermesPluginSource(), pluginTarget, { recursive: true, force: true });
|
|
10557
11047
|
}
|
|
10558
11048
|
const nextEnv = options.force ? envBlock.content : mergeEnvContent(readExisting(envPath), {
|
|
10559
11049
|
token: options.token,
|
|
10560
|
-
serverUrl: normalizeServerUrl2(options.serverUrl)
|
|
11050
|
+
serverUrl: normalizeServerUrl2(options.serverUrl),
|
|
11051
|
+
modelProvider
|
|
10561
11052
|
});
|
|
10562
11053
|
writeFile(envPath, nextEnv, options.dryRun);
|
|
10563
11054
|
const nextConfig = mergeHermesConfigContent(options.force ? "" : readExisting(configPath), {
|
|
10564
11055
|
token: options.token,
|
|
10565
|
-
serverUrl: normalizeServerUrl2(options.serverUrl)
|
|
11056
|
+
serverUrl: normalizeServerUrl2(options.serverUrl),
|
|
11057
|
+
modelProvider
|
|
10566
11058
|
});
|
|
10567
11059
|
writeFile(configPath, nextConfig, options.dryRun);
|
|
10568
11060
|
if (options.install) {
|
|
10569
11061
|
runShell(
|
|
10570
|
-
`python -m pip install -r "${
|
|
11062
|
+
`python -m pip install -r "${resolve3(pluginTarget, "requirements.txt")}"`,
|
|
10571
11063
|
options.dryRun
|
|
10572
11064
|
);
|
|
10573
11065
|
runShell("hermes plugins enable shadowob", options.dryRun);
|
|
@@ -10578,17 +11070,19 @@ function applyHermes(options) {
|
|
|
10578
11070
|
}
|
|
10579
11071
|
async function applyCcConnect(options) {
|
|
10580
11072
|
const target = requireTarget(options);
|
|
10581
|
-
const
|
|
11073
|
+
const modelProvider = modelProviderFromOptions(options);
|
|
11074
|
+
const plan = createConnectorPlan({ ...options, target, modelProvider });
|
|
10582
11075
|
const configBlock = plan.configBlocks.find((block) => block.label === "~/.cc-connect/config.toml");
|
|
10583
11076
|
if (!configBlock) throw new Error("cc-connect plan is missing config block");
|
|
10584
|
-
installShadowCliAndSkills(options);
|
|
10585
|
-
const configPath =
|
|
11077
|
+
await installShadowCliAndSkills(options);
|
|
11078
|
+
const configPath = resolve3(homedir3(), ".cc-connect/config.toml");
|
|
10586
11079
|
const nextConfig = options.force ? configBlock.content : mergeCcConnectConfigContent(readExisting(configPath), {
|
|
10587
11080
|
token: options.token,
|
|
10588
11081
|
serverUrl: normalizeServerUrl2(options.serverUrl),
|
|
10589
11082
|
projectName: options.projectName?.trim() || "shadow-buddy",
|
|
10590
11083
|
workDir: options.workDir?.trim() || ".",
|
|
10591
|
-
agentType: options.agentType?.trim() || "codex"
|
|
11084
|
+
agentType: options.agentType?.trim() || "codex",
|
|
11085
|
+
modelProvider
|
|
10592
11086
|
});
|
|
10593
11087
|
writeFile(configPath, nextConfig, options.dryRun);
|
|
10594
11088
|
let binaryPath;
|
|
@@ -10607,11 +11101,11 @@ async function applyCcConnect(options) {
|
|
|
10607
11101
|
async function connect(options) {
|
|
10608
11102
|
const target = requireTarget(options);
|
|
10609
11103
|
if (target === "openclaw") {
|
|
10610
|
-
applyOpenClaw(options);
|
|
11104
|
+
await applyOpenClaw(options);
|
|
10611
11105
|
return;
|
|
10612
11106
|
}
|
|
10613
11107
|
if (target === "hermes") {
|
|
10614
|
-
applyHermes(options);
|
|
11108
|
+
await applyHermes(options);
|
|
10615
11109
|
return;
|
|
10616
11110
|
}
|
|
10617
11111
|
await applyCcConnect(options);
|
|
@@ -10620,11 +11114,11 @@ async function repair(options, mode) {
|
|
|
10620
11114
|
const target = requireTarget(options);
|
|
10621
11115
|
console.log(`Applying: ${mode} ${target} connector`);
|
|
10622
11116
|
if (target === "openclaw") {
|
|
10623
|
-
applyOpenClaw(options, { restart: options.start });
|
|
11117
|
+
await applyOpenClaw(options, { restart: options.start });
|
|
10624
11118
|
return;
|
|
10625
11119
|
}
|
|
10626
11120
|
if (target === "hermes") {
|
|
10627
|
-
applyHermes({ ...options, start: options.start });
|
|
11121
|
+
await applyHermes({ ...options, start: options.start });
|
|
10628
11122
|
return;
|
|
10629
11123
|
}
|
|
10630
11124
|
await applyCcConnect({ ...options, start: options.start });
|
|
@@ -10645,7 +11139,7 @@ async function main() {
|
|
|
10645
11139
|
} else if (options.command === "runtime-scan") {
|
|
10646
11140
|
printRuntimeScan(options);
|
|
10647
11141
|
} else if (options.command === "runtime-install") {
|
|
10648
|
-
installRuntime(options);
|
|
11142
|
+
await installRuntime(options);
|
|
10649
11143
|
} else {
|
|
10650
11144
|
printPlan(options);
|
|
10651
11145
|
}
|