@shadowob/connector 1.1.3-dev.271 → 1.1.4
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 +33 -7
- package/dist/cli.js +593 -81
- package/dist/index.cjs +52 -18
- package/dist/index.js +50 -18
- package/hermes-shadowob-plugin/README.md +2 -2
- package/hermes-shadowob-plugin/adapter.py +124 -32
- package/hermes-shadowob-plugin/shadow_sdk.py +33 -2
- package/hermes-shadowob-plugin/tests/test_adapter_env.py +102 -0
- package/package.json +6 -1
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,469 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { spawnSync } from "child_process";
|
|
5
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
5
|
+
import { cpSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
6
|
+
import { homedir as homedir2 } from "os";
|
|
7
|
+
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
|
|
10
|
+
// src/cc-connect-installer.ts
|
|
11
|
+
import { execFileSync, spawnSync } from "child_process";
|
|
12
|
+
import { createHash } from "crypto";
|
|
13
|
+
import {
|
|
14
|
+
chmodSync,
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
readdirSync,
|
|
18
|
+
renameSync,
|
|
19
|
+
rmSync,
|
|
20
|
+
writeFileSync
|
|
21
|
+
} from "fs";
|
|
22
|
+
import { get as httpGet } from "http";
|
|
23
|
+
import { get as httpsGet } from "https";
|
|
6
24
|
import { homedir } from "os";
|
|
7
25
|
import { dirname, resolve } from "path";
|
|
8
|
-
|
|
26
|
+
|
|
27
|
+
// src/cc-connect-fork.ts
|
|
28
|
+
var CC_CONNECT_FORK_REPO = "buggyblues/cc-connect";
|
|
29
|
+
var CC_CONNECT_FORK_REF = "63b5d59127b3004bc7002f2d51892b1f2a91ea83";
|
|
30
|
+
var CC_CONNECT_FORK_SHORT_REF = CC_CONNECT_FORK_REF.slice(0, 7);
|
|
31
|
+
var CC_CONNECT_FORK_PACKAGE_VERSION = "1.3.3-beta.5";
|
|
32
|
+
var CC_CONNECT_FORK_DOCS_URL = `https://github.com/${CC_CONNECT_FORK_REPO}/blob/main/docs/shadowob.md`;
|
|
33
|
+
|
|
34
|
+
// src/cc-connect-installer.ts
|
|
35
|
+
var NAME = "cc-connect";
|
|
36
|
+
var RELEASE_ARCHIVE_SHA256 = {
|
|
37
|
+
"cc-connect-v1.3.3-beta.5-darwin-amd64.tar.gz": "71677cd565f6ea79e186ffc1b842e4548faa3baeb2e3dd2ced25ab2e95c416a3",
|
|
38
|
+
"cc-connect-v1.3.3-beta.5-darwin-arm64.tar.gz": "c1fc5a3d4cfe6db97a5d864ea844dbfd86345b89cea3d89871330568e6eb43cf",
|
|
39
|
+
"cc-connect-v1.3.3-beta.5-linux-amd64.tar.gz": "812484d19733044c8c5d67d997a921e351e2ae4e9a200ce5ae258ab338400bc1",
|
|
40
|
+
"cc-connect-v1.3.3-beta.5-linux-arm64.tar.gz": "290110a60e905f5e25f6133f52c1b3988ab6aeff1b1199318398cfef06f81e9a",
|
|
41
|
+
"cc-connect-v1.3.3-beta.5-windows-amd64.zip": "a90f8a669a48412fc5896613173b5e9b3dc5fdebdee731b6f6b9d4625a200952",
|
|
42
|
+
"cc-connect-v1.3.3-beta.5-windows-arm64.zip": "3c5ff24769d95c42b95a717949dc6369169c029e1110ab4bdd76d12e0043d283"
|
|
43
|
+
};
|
|
44
|
+
var PLATFORM_MAP = {
|
|
45
|
+
darwin: "darwin",
|
|
46
|
+
linux: "linux",
|
|
47
|
+
win32: "windows"
|
|
48
|
+
};
|
|
49
|
+
var ARCH_MAP = {
|
|
50
|
+
x64: "amd64",
|
|
51
|
+
arm64: "arm64"
|
|
52
|
+
};
|
|
53
|
+
function log(options, message) {
|
|
54
|
+
options.log?.(message);
|
|
55
|
+
}
|
|
56
|
+
function quoteArg(value) {
|
|
57
|
+
return /^[A-Za-z0-9_./:@=-]+$/.test(value) ? value : JSON.stringify(value);
|
|
58
|
+
}
|
|
59
|
+
function expandHome(value) {
|
|
60
|
+
return value.startsWith("~/") ? resolve(homedir(), value.slice(2)) : resolve(value);
|
|
61
|
+
}
|
|
62
|
+
function installRoot() {
|
|
63
|
+
const override = process.env.SHADOW_CC_CONNECT_HOME?.trim();
|
|
64
|
+
return override ? expandHome(override) : resolve(homedir(), ".shadowob/connector/cc-connect");
|
|
65
|
+
}
|
|
66
|
+
function binaryName() {
|
|
67
|
+
return process.platform === "win32" ? `${NAME}.exe` : NAME;
|
|
68
|
+
}
|
|
69
|
+
function cachedBinaryPath() {
|
|
70
|
+
return resolve(installRoot(), CC_CONNECT_FORK_SHORT_REF, "bin", binaryName());
|
|
71
|
+
}
|
|
72
|
+
function runCommand(command, args, options) {
|
|
73
|
+
const rendered = [command, ...args].map(quoteArg).join(" ");
|
|
74
|
+
if (options.dryRun) {
|
|
75
|
+
console.log(`[dry-run] ${rendered}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const result = spawnSync(command, args, {
|
|
79
|
+
cwd: options.cwd,
|
|
80
|
+
env: options.env,
|
|
81
|
+
stdio: "inherit"
|
|
82
|
+
});
|
|
83
|
+
if (result.status !== 0) {
|
|
84
|
+
throw new Error(`Command failed with exit code ${result.status ?? "unknown"}: ${rendered}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function platformInfo() {
|
|
88
|
+
const platform = PLATFORM_MAP[process.platform];
|
|
89
|
+
const arch = ARCH_MAP[process.arch];
|
|
90
|
+
if (!platform || !arch) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`Unsupported cc-connect platform: ${process.platform}/${process.arch}. Supported: linux/darwin/windows x64/arm64`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return { platform, arch, ext: platform === "windows" ? ".zip" : ".tar.gz" };
|
|
96
|
+
}
|
|
97
|
+
function fetchBuffer(url, redirects = 5) {
|
|
98
|
+
return new Promise((resolvePromise, reject) => {
|
|
99
|
+
if (redirects <= 0) {
|
|
100
|
+
reject(new Error(`Too many redirects for ${url}`));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const client = url.startsWith("https:") ? httpsGet : httpGet;
|
|
104
|
+
const request = client(url, { headers: { "User-Agent": "shadowob-connector" } }, (response) => {
|
|
105
|
+
const location = response.headers.location;
|
|
106
|
+
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && location) {
|
|
107
|
+
response.resume();
|
|
108
|
+
const next = new URL(location, url).toString();
|
|
109
|
+
resolvePromise(fetchBuffer(next, redirects - 1));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (response.statusCode !== 200) {
|
|
113
|
+
response.resume();
|
|
114
|
+
reject(new Error(`HTTP ${response.statusCode ?? "unknown"} for ${url}`));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const chunks = [];
|
|
118
|
+
response.on("data", (chunk) => chunks.push(chunk));
|
|
119
|
+
response.on("end", () => resolvePromise(Buffer.concat(chunks)));
|
|
120
|
+
response.on("error", reject);
|
|
121
|
+
});
|
|
122
|
+
request.on("error", reject);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function findExtractedBinary(dir) {
|
|
126
|
+
const expected = binaryName();
|
|
127
|
+
const entries = readdirSync(dir);
|
|
128
|
+
if (entries.includes(expected)) return resolve(dir, expected);
|
|
129
|
+
return entries.filter((entry) => entry.startsWith(NAME)).map((entry) => resolve(dir, entry)).find(
|
|
130
|
+
(entry) => process.platform === "win32" ? entry.endsWith(".exe") : !entry.endsWith(".gz")
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
function extractReleaseArchive(archivePath, binDir, ext) {
|
|
134
|
+
if (ext === ".tar.gz") {
|
|
135
|
+
runCommand("tar", ["xzf", archivePath, "-C", binDir], { dryRun: false });
|
|
136
|
+
} else {
|
|
137
|
+
const unzip = spawnSync("unzip", ["-o", archivePath, "-d", binDir], { stdio: "inherit" });
|
|
138
|
+
if (unzip.status !== 0) {
|
|
139
|
+
runCommand("powershell", ["-Command", `Expand-Archive -Force '${archivePath}' '${binDir}'`], {
|
|
140
|
+
dryRun: false
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const extracted = findExtractedBinary(binDir);
|
|
145
|
+
const target = resolve(binDir, binaryName());
|
|
146
|
+
if (!extracted) throw new Error("cc-connect release archive did not contain a binary");
|
|
147
|
+
if (extracted !== target) renameSync(extracted, target);
|
|
148
|
+
}
|
|
149
|
+
function verifyReleaseChecksum(filename, data) {
|
|
150
|
+
const expected = RELEASE_ARCHIVE_SHA256[filename];
|
|
151
|
+
if (!expected) throw new Error(`No pinned SHA-256 for ${filename}`);
|
|
152
|
+
const actual = createHash("sha256").update(data).digest("hex");
|
|
153
|
+
if (actual !== expected) {
|
|
154
|
+
throw new Error(`SHA-256 mismatch for ${filename}: expected ${expected}, got ${actual}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function installFromRelease(binaryPath, options) {
|
|
158
|
+
const { platform, arch, ext } = platformInfo();
|
|
159
|
+
const version = `v${CC_CONNECT_FORK_PACKAGE_VERSION}`;
|
|
160
|
+
const filename = `${NAME}-${version}-${platform}-${arch}${ext}`;
|
|
161
|
+
const url = `https://github.com/${CC_CONNECT_FORK_REPO}/releases/download/${version}/${filename}`;
|
|
162
|
+
const binDir = dirname(binaryPath);
|
|
163
|
+
const archivePath = resolve(binDir, `_release${ext}`);
|
|
164
|
+
log(options, `[cc-connect] Trying fork release asset ${url}`);
|
|
165
|
+
try {
|
|
166
|
+
const data = await fetchBuffer(url);
|
|
167
|
+
verifyReleaseChecksum(filename, data);
|
|
168
|
+
mkdirSync(binDir, { recursive: true });
|
|
169
|
+
writeFileSync(archivePath, data);
|
|
170
|
+
extractReleaseArchive(archivePath, binDir, ext);
|
|
171
|
+
rmSync(archivePath, { force: true });
|
|
172
|
+
if (process.platform !== "win32") chmodSync(binaryPath, 493);
|
|
173
|
+
return true;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
rmSync(archivePath, { force: true });
|
|
176
|
+
log(
|
|
177
|
+
options,
|
|
178
|
+
`[cc-connect] Fork release asset unavailable (${error instanceof Error ? error.message : String(error)}); building from source`
|
|
179
|
+
);
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async function ensureSourceArchive(options) {
|
|
184
|
+
const sourceDir = resolve(installRoot(), CC_CONNECT_FORK_SHORT_REF, "source");
|
|
185
|
+
if (existsSync(resolve(sourceDir, "go.mod"))) return sourceDir;
|
|
186
|
+
if (existsSync(sourceDir)) rmSync(sourceDir, { recursive: true, force: true });
|
|
187
|
+
const parent = dirname(sourceDir);
|
|
188
|
+
const extractDir = resolve(parent, `_source-${process.pid}-${Date.now()}`);
|
|
189
|
+
const archivePath = resolve(parent, `${CC_CONNECT_FORK_SHORT_REF}.tar.gz`);
|
|
190
|
+
const sourceUrl = `https://github.com/${CC_CONNECT_FORK_REPO}/archive/${CC_CONNECT_FORK_REF}.tar.gz`;
|
|
191
|
+
log(options, `[cc-connect] Pulling fork source ${sourceUrl}`);
|
|
192
|
+
mkdirSync(parent, { recursive: true });
|
|
193
|
+
const data = await fetchBuffer(sourceUrl);
|
|
194
|
+
writeFileSync(archivePath, data);
|
|
195
|
+
mkdirSync(extractDir, { recursive: true });
|
|
196
|
+
try {
|
|
197
|
+
runCommand("tar", ["xzf", archivePath, "-C", extractDir], { dryRun: false });
|
|
198
|
+
const extracted = readdirSync(extractDir, { withFileTypes: true }).find(
|
|
199
|
+
(entry) => entry.isDirectory()
|
|
200
|
+
);
|
|
201
|
+
if (!extracted) throw new Error("cc-connect source archive did not contain a directory");
|
|
202
|
+
mkdirSync(dirname(sourceDir), { recursive: true });
|
|
203
|
+
renameSync(resolve(extractDir, extracted.name), sourceDir);
|
|
204
|
+
} finally {
|
|
205
|
+
rmSync(archivePath, { force: true });
|
|
206
|
+
rmSync(extractDir, { recursive: true, force: true });
|
|
207
|
+
}
|
|
208
|
+
return sourceDir;
|
|
209
|
+
}
|
|
210
|
+
async function buildFromSource(binaryPath, options) {
|
|
211
|
+
const sourceDir = await ensureSourceArchive(options);
|
|
212
|
+
const buildTime = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
213
|
+
const ldflags = [
|
|
214
|
+
"-s",
|
|
215
|
+
"-w",
|
|
216
|
+
"-X",
|
|
217
|
+
"main.version=dev",
|
|
218
|
+
"-X",
|
|
219
|
+
`main.commit=${CC_CONNECT_FORK_SHORT_REF}`,
|
|
220
|
+
"-X",
|
|
221
|
+
`main.buildTime=${buildTime}`
|
|
222
|
+
].join(" ");
|
|
223
|
+
mkdirSync(dirname(binaryPath), { recursive: true });
|
|
224
|
+
runCommand(
|
|
225
|
+
"go",
|
|
226
|
+
["build", "-tags", "no_web", "-ldflags", ldflags, "-o", binaryPath, "./cmd/cc-connect"],
|
|
227
|
+
{
|
|
228
|
+
cwd: sourceDir,
|
|
229
|
+
dryRun: false,
|
|
230
|
+
env: goBuildEnv()
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
if (process.platform !== "win32") chmodSync(binaryPath, 493);
|
|
234
|
+
}
|
|
235
|
+
function goBuildEnv() {
|
|
236
|
+
return {
|
|
237
|
+
...process.env,
|
|
238
|
+
CGO_ENABLED: "0",
|
|
239
|
+
GOPROXY: process.env.SHADOW_CC_CONNECT_GOPROXY?.trim() || "https://proxy.golang.org,direct",
|
|
240
|
+
GOSUMDB: process.env.SHADOW_CC_CONNECT_GOSUMDB?.trim() || process.env.GOSUMDB || "sum.golang.org"
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function binaryLooksUsable(path) {
|
|
244
|
+
if (!existsSync(path)) return false;
|
|
245
|
+
try {
|
|
246
|
+
const out = execFileSync(path, ["--version"], { encoding: "utf8", timeout: 5e3 });
|
|
247
|
+
return out.includes(CC_CONNECT_FORK_SHORT_REF) || out.includes(CC_CONNECT_FORK_PACKAGE_VERSION);
|
|
248
|
+
} catch {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async function ensureCcConnectFork(options) {
|
|
253
|
+
const override = process.env.SHADOW_CC_CONNECT_BIN?.trim();
|
|
254
|
+
if (override) {
|
|
255
|
+
const binaryPath2 = expandHome(override);
|
|
256
|
+
if (!existsSync(binaryPath2))
|
|
257
|
+
throw new Error(`SHADOW_CC_CONNECT_BIN does not exist: ${binaryPath2}`);
|
|
258
|
+
return { binaryPath: binaryPath2, source: "env" };
|
|
259
|
+
}
|
|
260
|
+
const binaryPath = cachedBinaryPath();
|
|
261
|
+
if (binaryLooksUsable(binaryPath)) {
|
|
262
|
+
return { binaryPath, source: "cache" };
|
|
263
|
+
}
|
|
264
|
+
if (options.dryRun) {
|
|
265
|
+
console.log(
|
|
266
|
+
`[dry-run] install ${CC_CONNECT_FORK_REPO}@${CC_CONNECT_FORK_SHORT_REF} -> ${binaryPath}`
|
|
267
|
+
);
|
|
268
|
+
return { binaryPath, source: "source" };
|
|
269
|
+
}
|
|
270
|
+
mkdirSync(dirname(binaryPath), { recursive: true });
|
|
271
|
+
const fromRelease = await installFromRelease(binaryPath, options);
|
|
272
|
+
if (!fromRelease) await buildFromSource(binaryPath, options);
|
|
273
|
+
if (!binaryLooksUsable(binaryPath)) {
|
|
274
|
+
throw new Error(`Installed cc-connect binary did not pass version verification: ${binaryPath}`);
|
|
275
|
+
}
|
|
276
|
+
return { binaryPath, source: fromRelease ? "release" : "source" };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/config-writers.ts
|
|
280
|
+
import { parse as parseDotenv } from "dotenv";
|
|
281
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
282
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
283
|
+
var SHADOW_ENV_VALUES = {
|
|
284
|
+
SHADOW_ALLOW_ALL_USERS: "true",
|
|
285
|
+
SHADOW_HEARTBEAT_INTERVAL_SECONDS: "30",
|
|
286
|
+
SHADOW_SLASH_COMMANDS_JSON: "[]"
|
|
287
|
+
};
|
|
288
|
+
function isRecord(value) {
|
|
289
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
290
|
+
}
|
|
291
|
+
function asRecord(value) {
|
|
292
|
+
return isRecord(value) ? { ...value } : {};
|
|
293
|
+
}
|
|
294
|
+
function uniqueStrings(values, required) {
|
|
295
|
+
return [
|
|
296
|
+
.../* @__PURE__ */ new Set([...values.filter((value) => typeof value === "string"), required])
|
|
297
|
+
];
|
|
298
|
+
}
|
|
299
|
+
function ensureTrailingNewline(value) {
|
|
300
|
+
return value.endsWith("\n") ? value : `${value}
|
|
301
|
+
`;
|
|
302
|
+
}
|
|
303
|
+
function quoteEnv(value) {
|
|
304
|
+
return /^[A-Za-z0-9_./:@-]+$/.test(value) ? value : JSON.stringify(value);
|
|
305
|
+
}
|
|
306
|
+
function normalizeJsonRoot(existing, label) {
|
|
307
|
+
if (!existing.trim()) return {};
|
|
308
|
+
const parsed = JSON.parse(existing);
|
|
309
|
+
if (!isRecord(parsed)) {
|
|
310
|
+
throw new Error(`${label} config must be a JSON object`);
|
|
311
|
+
}
|
|
312
|
+
return { ...parsed };
|
|
313
|
+
}
|
|
314
|
+
function parseYamlRoot(existing, label) {
|
|
315
|
+
if (!existing.trim()) return {};
|
|
316
|
+
const parsed = parseYaml(existing);
|
|
317
|
+
if (parsed == null) return {};
|
|
318
|
+
if (!isRecord(parsed)) {
|
|
319
|
+
throw new Error(`${label} config must be a YAML object`);
|
|
320
|
+
}
|
|
321
|
+
return { ...parsed };
|
|
322
|
+
}
|
|
323
|
+
function parseTomlRoot(existing, label) {
|
|
324
|
+
if (!existing.trim()) return {};
|
|
325
|
+
const parsed = parseToml(existing);
|
|
326
|
+
if (!isRecord(parsed)) {
|
|
327
|
+
throw new Error(`${label} config must be a TOML table`);
|
|
328
|
+
}
|
|
329
|
+
return parsed;
|
|
330
|
+
}
|
|
331
|
+
function mergeEnvContent(existing, values) {
|
|
332
|
+
parseDotenv(existing);
|
|
333
|
+
const updates = {
|
|
334
|
+
SHADOW_BASE_URL: values.serverUrl,
|
|
335
|
+
SHADOW_TOKEN: values.token,
|
|
336
|
+
...SHADOW_ENV_VALUES
|
|
337
|
+
};
|
|
338
|
+
const seen = /* @__PURE__ */ new Set();
|
|
339
|
+
const lines = existing.length > 0 ? existing.split(/\r?\n/) : [];
|
|
340
|
+
const next = [];
|
|
341
|
+
for (const line of lines) {
|
|
342
|
+
const match = line.match(/^(\s*(?:export\s+)?)((?:[A-Za-z_][A-Za-z0-9_]*))\s*=/);
|
|
343
|
+
const key = match?.[2];
|
|
344
|
+
if (!key || !(key in updates)) {
|
|
345
|
+
next.push(line);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (seen.has(key)) continue;
|
|
349
|
+
seen.add(key);
|
|
350
|
+
next.push(`${match[1] ?? ""}${key}=${quoteEnv(updates[key] ?? "")}`);
|
|
351
|
+
}
|
|
352
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
353
|
+
if (!seen.has(key)) next.push(`${key}=${quoteEnv(value)}`);
|
|
354
|
+
}
|
|
355
|
+
while (next.length > 0 && next[next.length - 1] === "") next.pop();
|
|
356
|
+
return ensureTrailingNewline(next.join("\n"));
|
|
357
|
+
}
|
|
358
|
+
function mergeOpenClawConfigContent(existing, values) {
|
|
359
|
+
const root = normalizeJsonRoot(existing, "OpenClaw");
|
|
360
|
+
const channels = asRecord(root.channels);
|
|
361
|
+
const legacyShadow = asRecord(channels["openclaw-shadowob"]);
|
|
362
|
+
const shadow = asRecord(channels.shadowob);
|
|
363
|
+
channels.shadowob = {
|
|
364
|
+
...legacyShadow,
|
|
365
|
+
...shadow,
|
|
366
|
+
token: values.token,
|
|
367
|
+
serverUrl: values.serverUrl
|
|
368
|
+
};
|
|
369
|
+
delete channels["openclaw-shadowob"];
|
|
370
|
+
root.channels = channels;
|
|
371
|
+
const plugins = asRecord(root.plugins);
|
|
372
|
+
plugins.enabled = plugins.enabled ?? true;
|
|
373
|
+
plugins.allow = uniqueStrings(
|
|
374
|
+
Array.isArray(plugins.allow) ? plugins.allow : [],
|
|
375
|
+
"openclaw-shadowob"
|
|
376
|
+
);
|
|
377
|
+
const entries = asRecord(plugins.entries);
|
|
378
|
+
entries["openclaw-shadowob"] = {
|
|
379
|
+
...asRecord(entries["openclaw-shadowob"]),
|
|
380
|
+
enabled: true
|
|
381
|
+
};
|
|
382
|
+
plugins.entries = entries;
|
|
383
|
+
root.plugins = plugins;
|
|
384
|
+
return ensureTrailingNewline(JSON.stringify(root, null, 2));
|
|
385
|
+
}
|
|
386
|
+
function mergeHermesConfigContent(existing, values) {
|
|
387
|
+
const root = parseYamlRoot(existing, "Hermes");
|
|
388
|
+
const plugins = asRecord(root.plugins);
|
|
389
|
+
plugins.enabled = uniqueStrings(Array.isArray(plugins.enabled) ? plugins.enabled : [], "shadowob");
|
|
390
|
+
root.plugins = plugins;
|
|
391
|
+
const platforms = asRecord(root.platforms);
|
|
392
|
+
const shadowob = asRecord(platforms.shadowob);
|
|
393
|
+
const extra = asRecord(shadowob.extra);
|
|
394
|
+
platforms.shadowob = {
|
|
395
|
+
...shadowob,
|
|
396
|
+
enabled: true,
|
|
397
|
+
token: values.token,
|
|
398
|
+
extra: {
|
|
399
|
+
mention_only: false,
|
|
400
|
+
rest_only: false,
|
|
401
|
+
catchup_minutes: 0,
|
|
402
|
+
download_media: true,
|
|
403
|
+
slash_commands: [],
|
|
404
|
+
...extra,
|
|
405
|
+
base_url: values.serverUrl
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
root.platforms = platforms;
|
|
409
|
+
return ensureTrailingNewline(stringifyYaml(root));
|
|
410
|
+
}
|
|
411
|
+
function asTomlTable(value) {
|
|
412
|
+
return isRecord(value) && !Array.isArray(value) ? { ...value } : {};
|
|
413
|
+
}
|
|
414
|
+
function tomlArray(value) {
|
|
415
|
+
if (!Array.isArray(value)) return [];
|
|
416
|
+
const tables = [];
|
|
417
|
+
for (const item of value) {
|
|
418
|
+
if (isRecord(item)) tables.push({ ...item });
|
|
419
|
+
}
|
|
420
|
+
return tables;
|
|
421
|
+
}
|
|
422
|
+
function ccConnectProjectWorkDir(project) {
|
|
423
|
+
const agent = asTomlTable(project.agent);
|
|
424
|
+
const options = asTomlTable(agent.options);
|
|
425
|
+
return typeof options.work_dir === "string" ? options.work_dir : typeof project.work_dir === "string" ? project.work_dir : void 0;
|
|
426
|
+
}
|
|
427
|
+
function mergeCcConnectConfigContent(existing, values) {
|
|
428
|
+
const root = parseTomlRoot(existing, "cc-connect");
|
|
429
|
+
const projects = tomlArray(root.projects);
|
|
430
|
+
let project = projects.find((item) => item.name === values.projectName) ?? projects.find((item) => ccConnectProjectWorkDir(item) === values.workDir);
|
|
431
|
+
if (!project) {
|
|
432
|
+
project = {};
|
|
433
|
+
projects.push(project);
|
|
434
|
+
}
|
|
435
|
+
project.name = values.projectName;
|
|
436
|
+
delete project.work_dir;
|
|
437
|
+
delete project.agent_type;
|
|
438
|
+
const agent = asTomlTable(project.agent);
|
|
439
|
+
const agentOptions = asTomlTable(agent.options);
|
|
440
|
+
agent.type = values.agentType;
|
|
441
|
+
agent.options = {
|
|
442
|
+
...agentOptions,
|
|
443
|
+
work_dir: values.workDir
|
|
444
|
+
};
|
|
445
|
+
project.agent = agent;
|
|
446
|
+
const platforms = tomlArray(project.platforms);
|
|
447
|
+
let shadowPlatform = platforms.find((item) => item.type === "shadowob");
|
|
448
|
+
if (!shadowPlatform) {
|
|
449
|
+
shadowPlatform = {};
|
|
450
|
+
platforms.push(shadowPlatform);
|
|
451
|
+
}
|
|
452
|
+
const options = asTomlTable(shadowPlatform.options);
|
|
453
|
+
shadowPlatform.type = "shadowob";
|
|
454
|
+
shadowPlatform.options = {
|
|
455
|
+
allow_from: "*",
|
|
456
|
+
listen_dms: true,
|
|
457
|
+
share_session_in_channel: false,
|
|
458
|
+
progress_style: "compact",
|
|
459
|
+
...options,
|
|
460
|
+
token: values.token,
|
|
461
|
+
server_url: values.serverUrl
|
|
462
|
+
};
|
|
463
|
+
project.platforms = platforms;
|
|
464
|
+
root.projects = projects;
|
|
465
|
+
return ensureTrailingNewline(stringifyToml(root));
|
|
466
|
+
}
|
|
9
467
|
|
|
10
468
|
// src/index.ts
|
|
11
469
|
var DEFAULT_SERVER_URL = "https://shadowob.com";
|
|
@@ -94,7 +552,14 @@ function buildOpenClawPlan(input) {
|
|
|
94
552
|
"typing",
|
|
95
553
|
"activityStatus",
|
|
96
554
|
"reactions",
|
|
97
|
-
"editDelete"
|
|
555
|
+
"editDelete",
|
|
556
|
+
"statusChecks",
|
|
557
|
+
"usageCosts",
|
|
558
|
+
"multiAgentBinding",
|
|
559
|
+
"shadowCliLogin",
|
|
560
|
+
"notifications",
|
|
561
|
+
"officialSkills",
|
|
562
|
+
"cronTasks"
|
|
98
563
|
]
|
|
99
564
|
};
|
|
100
565
|
}
|
|
@@ -180,7 +645,12 @@ function buildHermesPlan(input) {
|
|
|
180
645
|
"onlineStatus",
|
|
181
646
|
"typing",
|
|
182
647
|
"activityStatus",
|
|
183
|
-
"cronDelivery"
|
|
648
|
+
"cronDelivery",
|
|
649
|
+
"statusChecks",
|
|
650
|
+
"usageCosts",
|
|
651
|
+
"shadowCliLogin",
|
|
652
|
+
"notifications",
|
|
653
|
+
"officialSkills"
|
|
184
654
|
]
|
|
185
655
|
};
|
|
186
656
|
}
|
|
@@ -195,8 +665,12 @@ function buildCcConnectPlan(input) {
|
|
|
195
665
|
"",
|
|
196
666
|
"[[projects]]",
|
|
197
667
|
`name = "${projectName}"`,
|
|
668
|
+
"",
|
|
669
|
+
"[projects.agent]",
|
|
670
|
+
`type = "${agentType}"`,
|
|
671
|
+
"",
|
|
672
|
+
"[projects.agent.options]",
|
|
198
673
|
`work_dir = "${workDir}"`,
|
|
199
|
-
`agent_type = "${agentType}"`,
|
|
200
674
|
"",
|
|
201
675
|
"[[projects.platforms]]",
|
|
202
676
|
'type = "shadowob"',
|
|
@@ -209,15 +683,6 @@ function buildCcConnectPlan(input) {
|
|
|
209
683
|
"share_session_in_channel = false",
|
|
210
684
|
'progress_style = "compact"'
|
|
211
685
|
].join("\n");
|
|
212
|
-
const commands = [
|
|
213
|
-
{ label: "Install cc-connect", command: "npm install -g cc-connect" },
|
|
214
|
-
{ label: "Create config directory", command: "mkdir -p ~/.cc-connect" },
|
|
215
|
-
{
|
|
216
|
-
label: "Edit config",
|
|
217
|
-
command: "$EDITOR ~/.cc-connect/config.toml"
|
|
218
|
-
},
|
|
219
|
-
{ label: "Start cc-connect", command: "cc-connect" }
|
|
220
|
-
];
|
|
221
686
|
const connectCommand = [
|
|
222
687
|
"npx @shadowob/connector@latest connect",
|
|
223
688
|
"--target cc-connect",
|
|
@@ -227,12 +692,26 @@ function buildCcConnectPlan(input) {
|
|
|
227
692
|
`--project-name ${shellQuote(projectName)}`,
|
|
228
693
|
`--agent-type ${shellQuote(agentType)}`
|
|
229
694
|
].join(" ");
|
|
695
|
+
const installCommand = `${connectCommand} --install`;
|
|
696
|
+
const startCommand = `${connectCommand} --install --start`;
|
|
697
|
+
const commands = [
|
|
698
|
+
{
|
|
699
|
+
label: "Install ShadowOB cc-connect fork",
|
|
700
|
+
command: installCommand
|
|
701
|
+
},
|
|
702
|
+
{ label: "Create config directory", command: "mkdir -p ~/.cc-connect" },
|
|
703
|
+
{
|
|
704
|
+
label: "Edit config",
|
|
705
|
+
command: "$EDITOR ~/.cc-connect/config.toml"
|
|
706
|
+
},
|
|
707
|
+
{ label: "Start ShadowOB cc-connect fork", command: startCommand }
|
|
708
|
+
];
|
|
230
709
|
return {
|
|
231
710
|
target: "cc-connect",
|
|
232
711
|
title: "cc-connect",
|
|
233
|
-
summary:
|
|
234
|
-
connectCommand,
|
|
235
|
-
quickCommand:
|
|
712
|
+
summary: `Use ${CC_CONNECT_FORK_REPO}@${CC_CONNECT_FORK_SHORT_REF} with ShadowOB Socket.IO platform support for this Buddy token.`,
|
|
713
|
+
connectCommand: startCommand,
|
|
714
|
+
quickCommand: startCommand,
|
|
236
715
|
commands,
|
|
237
716
|
configBlocks: [{ label: "~/.cc-connect/config.toml", language: "toml", content: tomlConfig }],
|
|
238
717
|
aiPrompt: [
|
|
@@ -243,9 +722,9 @@ function buildCcConnectPlan(input) {
|
|
|
243
722
|
`Project work_dir: ${workDir}`,
|
|
244
723
|
`Agent type: ${agentType}`,
|
|
245
724
|
"",
|
|
246
|
-
|
|
725
|
+
`Install ${CC_CONNECT_FORK_REPO}@${CC_CONNECT_FORK_SHORT_REF}, add the TOML platform block, and start cc-connect.`
|
|
247
726
|
].join("\n"),
|
|
248
|
-
docsUrl:
|
|
727
|
+
docsUrl: CC_CONNECT_FORK_DOCS_URL,
|
|
249
728
|
capabilities: [
|
|
250
729
|
"channelMessages",
|
|
251
730
|
"dms",
|
|
@@ -255,7 +734,12 @@ function buildCcConnectPlan(input) {
|
|
|
255
734
|
"slashCommands",
|
|
256
735
|
"typing",
|
|
257
736
|
"streamingPreviews",
|
|
258
|
-
"forms"
|
|
737
|
+
"forms",
|
|
738
|
+
"statusChecks",
|
|
739
|
+
"usageCosts",
|
|
740
|
+
"multiAgentBinding",
|
|
741
|
+
"shadowCliLogin",
|
|
742
|
+
"notifications"
|
|
259
743
|
]
|
|
260
744
|
};
|
|
261
745
|
}
|
|
@@ -286,13 +770,14 @@ function usage() {
|
|
|
286
770
|
" shadowob-connector connect --target <openclaw|hermes|cc-connect> --server-url <url> --token <token>",
|
|
287
771
|
"",
|
|
288
772
|
"Options:",
|
|
773
|
+
" --openclaw-config <path> OpenClaw JSON config, default $OPENCLAW_CONFIG or ~/.shadowob/openclaw.json",
|
|
289
774
|
" --hermes-home <path> Hermes config directory, default $HERMES_HOME or ~/.hermes",
|
|
290
775
|
" --work-dir <path> cc-connect project work directory",
|
|
291
776
|
" --project-name <name> cc-connect project name",
|
|
292
777
|
" --agent-type <type> cc-connect agent type, default codex",
|
|
293
778
|
" --json Print the full plan as JSON",
|
|
294
779
|
" --force Overwrite target config files when needed",
|
|
295
|
-
" --install Install cc-connect when target is cc-connect",
|
|
780
|
+
" --install Install the ShadowOB cc-connect fork when target is cc-connect",
|
|
296
781
|
" --no-install Skip Hermes dependency install and plugin enablement",
|
|
297
782
|
" --start Start Hermes gateway or cc-connect after setup",
|
|
298
783
|
" --dry-run Show what would be applied without changing files"
|
|
@@ -316,6 +801,7 @@ function parseArgs(args) {
|
|
|
316
801
|
target,
|
|
317
802
|
serverUrl: readOption(optionArgs, "--server-url") ?? "https://shadowob.com",
|
|
318
803
|
token: readOption(optionArgs, "--token") ?? "",
|
|
804
|
+
openclawConfig: readOption(optionArgs, "--openclaw-config"),
|
|
319
805
|
hermesHome: readOption(optionArgs, "--hermes-home"),
|
|
320
806
|
workDir: readOption(optionArgs, "--work-dir"),
|
|
321
807
|
projectName: readOption(optionArgs, "--project-name"),
|
|
@@ -349,81 +835,99 @@ function runShell(command, dryRun) {
|
|
|
349
835
|
console.log(`[dry-run] ${command}`);
|
|
350
836
|
return;
|
|
351
837
|
}
|
|
352
|
-
const result =
|
|
838
|
+
const result = spawnSync2(command, { shell: true, stdio: "inherit" });
|
|
353
839
|
if (result.status !== 0) {
|
|
354
840
|
throw new Error(`Command failed with exit code ${result.status ?? "unknown"}: ${command}`);
|
|
355
841
|
}
|
|
356
842
|
}
|
|
843
|
+
function runBinary(binaryPath, args, dryRun) {
|
|
844
|
+
const rendered = [binaryPath, ...args].map((arg) => /^[A-Za-z0-9_./:@=-]+$/.test(arg) ? arg : JSON.stringify(arg)).join(" ");
|
|
845
|
+
if (dryRun) {
|
|
846
|
+
console.log(`[dry-run] ${rendered}`);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const result = spawnSync2(binaryPath, args, { stdio: "inherit" });
|
|
850
|
+
if (result.status !== 0) {
|
|
851
|
+
throw new Error(`Command failed with exit code ${result.status ?? "unknown"}: ${rendered}`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
357
854
|
function writeFile(path, content, dryRun) {
|
|
358
855
|
if (dryRun) {
|
|
359
856
|
console.log(`[dry-run] write ${path}`);
|
|
360
857
|
return;
|
|
361
858
|
}
|
|
362
|
-
|
|
363
|
-
|
|
859
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
860
|
+
writeFileSync2(path, content.endsWith("\n") ? content : `${content}
|
|
364
861
|
`);
|
|
365
862
|
}
|
|
366
|
-
function upsertManagedBlock(path, name, content, dryRun) {
|
|
367
|
-
const begin = `# BEGIN ShadowOB ${name}`;
|
|
368
|
-
const end = `# END ShadowOB ${name}`;
|
|
369
|
-
const block = `${begin}
|
|
370
|
-
${content}
|
|
371
|
-
${end}`;
|
|
372
|
-
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
373
|
-
const pattern = new RegExp(
|
|
374
|
-
`${begin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${end.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`
|
|
375
|
-
);
|
|
376
|
-
const next = existing.match(pattern) ? existing.replace(pattern, block) : [existing.trimEnd(), block].filter(Boolean).join("\n\n");
|
|
377
|
-
writeFile(path, next, dryRun);
|
|
378
|
-
}
|
|
379
863
|
function packageRoot() {
|
|
380
|
-
return
|
|
864
|
+
return resolve2(dirname2(fileURLToPath(import.meta.url)), "..");
|
|
381
865
|
}
|
|
382
|
-
function
|
|
383
|
-
return value.startsWith("~/") ?
|
|
866
|
+
function expandHome2(value) {
|
|
867
|
+
return value.startsWith("~/") ? resolve2(homedir2(), value.slice(2)) : resolve2(value);
|
|
868
|
+
}
|
|
869
|
+
function readExisting(path) {
|
|
870
|
+
return existsSync2(path) ? readFileSync(path, "utf8") : "";
|
|
871
|
+
}
|
|
872
|
+
function normalizeServerUrl2(value) {
|
|
873
|
+
const trimmed = value.trim() || "https://shadowob.com";
|
|
874
|
+
return trimmed.endsWith("/api") ? trimmed.slice(0, -4) : trimmed.replace(/\/$/, "");
|
|
384
875
|
}
|
|
385
876
|
function hermesPluginSource() {
|
|
386
877
|
const candidates = [
|
|
387
|
-
|
|
388
|
-
|
|
878
|
+
resolve2(packageRoot(), "hermes-shadowob-plugin"),
|
|
879
|
+
resolve2(process.cwd(), "packages/connector/hermes-shadowob-plugin")
|
|
389
880
|
];
|
|
390
|
-
const found = candidates.find((candidate) =>
|
|
881
|
+
const found = candidates.find((candidate) => existsSync2(candidate));
|
|
391
882
|
if (!found) throw new Error("Cannot find bundled hermes-shadowob-plugin directory");
|
|
392
883
|
return found;
|
|
393
884
|
}
|
|
394
885
|
function applyOpenClaw(options) {
|
|
395
886
|
const plan = createConnectorPlan(options);
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
887
|
+
const configPath = expandHome2(
|
|
888
|
+
options.openclawConfig ?? process.env.OPENCLAW_CONFIG ?? "~/.shadowob/openclaw.json"
|
|
889
|
+
);
|
|
890
|
+
console.log("Applying: Install plugin");
|
|
891
|
+
runShell("openclaw plugins install @shadowob/openclaw-shadowob", options.dryRun);
|
|
892
|
+
console.log(`Applying: Merge OpenClaw config ${configPath}`);
|
|
893
|
+
const next = mergeOpenClawConfigContent(readExisting(configPath), {
|
|
894
|
+
token: options.token,
|
|
895
|
+
serverUrl: normalizeServerUrl2(options.serverUrl)
|
|
896
|
+
});
|
|
897
|
+
writeFile(configPath, next, options.dryRun);
|
|
898
|
+
const restart = plan.commands.find((step) => step.label === "Restart gateway");
|
|
899
|
+
if (restart) {
|
|
900
|
+
console.log(`Applying: ${restart.label}`);
|
|
901
|
+
runShell(restart.command, options.dryRun);
|
|
399
902
|
}
|
|
400
903
|
}
|
|
401
904
|
function applyHermes(options) {
|
|
402
905
|
const plan = createConnectorPlan(options);
|
|
403
|
-
const hermesDir =
|
|
404
|
-
const pluginTarget =
|
|
405
|
-
const envPath =
|
|
406
|
-
const configPath =
|
|
407
|
-
const generatedConfigPath = resolve(hermesDir, "config.shadowob.yaml");
|
|
906
|
+
const hermesDir = expandHome2(options.hermesHome ?? process.env.HERMES_HOME ?? "~/.hermes");
|
|
907
|
+
const pluginTarget = resolve2(hermesDir, "plugins/shadowob");
|
|
908
|
+
const envPath = resolve2(hermesDir, ".env");
|
|
909
|
+
const configPath = resolve2(hermesDir, "config.yaml");
|
|
408
910
|
const envBlock = plan.configBlocks.find((block) => block.label === "~/.hermes/.env");
|
|
409
|
-
|
|
410
|
-
if (!envBlock || !yamlBlock) throw new Error("Hermes plan is missing config blocks");
|
|
911
|
+
if (!envBlock) throw new Error("Hermes plan is missing config blocks");
|
|
411
912
|
if (options.dryRun) {
|
|
412
913
|
console.log(`[dry-run] copy ${hermesPluginSource()} -> ${pluginTarget}`);
|
|
413
914
|
} else {
|
|
414
|
-
|
|
915
|
+
mkdirSync2(resolve2(hermesDir, "plugins"), { recursive: true });
|
|
415
916
|
cpSync(hermesPluginSource(), pluginTarget, { recursive: true, force: true });
|
|
416
917
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
918
|
+
const nextEnv = options.force ? envBlock.content : mergeEnvContent(readExisting(envPath), {
|
|
919
|
+
token: options.token,
|
|
920
|
+
serverUrl: normalizeServerUrl2(options.serverUrl)
|
|
921
|
+
});
|
|
922
|
+
writeFile(envPath, nextEnv, options.dryRun);
|
|
923
|
+
const nextConfig = mergeHermesConfigContent(options.force ? "" : readExisting(configPath), {
|
|
924
|
+
token: options.token,
|
|
925
|
+
serverUrl: normalizeServerUrl2(options.serverUrl)
|
|
926
|
+
});
|
|
927
|
+
writeFile(configPath, nextConfig, options.dryRun);
|
|
424
928
|
if (options.install) {
|
|
425
929
|
runShell(
|
|
426
|
-
`python -m pip install -r "${
|
|
930
|
+
`python -m pip install -r "${resolve2(pluginTarget, "requirements.txt")}"`,
|
|
427
931
|
options.dryRun
|
|
428
932
|
);
|
|
429
933
|
runShell("hermes plugins enable shadowob", options.dryRun);
|
|
@@ -432,26 +936,33 @@ function applyHermes(options) {
|
|
|
432
936
|
runShell("hermes gateway", options.dryRun);
|
|
433
937
|
}
|
|
434
938
|
}
|
|
435
|
-
function applyCcConnect(options) {
|
|
939
|
+
async function applyCcConnect(options) {
|
|
436
940
|
const plan = createConnectorPlan(options);
|
|
437
941
|
const configBlock = plan.configBlocks.find((block) => block.label === "~/.cc-connect/config.toml");
|
|
438
942
|
if (!configBlock) throw new Error("cc-connect plan is missing config block");
|
|
439
|
-
const configPath =
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
|
|
943
|
+
const configPath = resolve2(homedir2(), ".cc-connect/config.toml");
|
|
944
|
+
const nextConfig = options.force ? configBlock.content : mergeCcConnectConfigContent(readExisting(configPath), {
|
|
945
|
+
token: options.token,
|
|
946
|
+
serverUrl: normalizeServerUrl2(options.serverUrl),
|
|
947
|
+
projectName: options.projectName?.trim() || "shadow-buddy",
|
|
948
|
+
workDir: options.workDir?.trim() || ".",
|
|
949
|
+
agentType: options.agentType?.trim() || "codex"
|
|
950
|
+
});
|
|
951
|
+
writeFile(configPath, nextConfig, options.dryRun);
|
|
952
|
+
let binaryPath;
|
|
953
|
+
if (options.install || options.start) {
|
|
954
|
+
const installed = await ensureCcConnectFork({
|
|
955
|
+
dryRun: options.dryRun,
|
|
956
|
+
log: (message) => console.log(message)
|
|
957
|
+
});
|
|
958
|
+
binaryPath = installed.binaryPath;
|
|
959
|
+
console.log(`cc-connect binary: ${binaryPath}`);
|
|
449
960
|
}
|
|
450
961
|
if (options.start) {
|
|
451
|
-
|
|
962
|
+
runBinary(binaryPath ?? "cc-connect", [], options.dryRun);
|
|
452
963
|
}
|
|
453
964
|
}
|
|
454
|
-
function connect(options) {
|
|
965
|
+
async function connect(options) {
|
|
455
966
|
if (options.target === "openclaw") {
|
|
456
967
|
applyOpenClaw(options);
|
|
457
968
|
return;
|
|
@@ -460,18 +971,19 @@ function connect(options) {
|
|
|
460
971
|
applyHermes(options);
|
|
461
972
|
return;
|
|
462
973
|
}
|
|
463
|
-
applyCcConnect(options);
|
|
974
|
+
await applyCcConnect(options);
|
|
464
975
|
}
|
|
465
|
-
|
|
976
|
+
async function main() {
|
|
466
977
|
const options = parseArgs(process.argv.slice(2));
|
|
467
978
|
if (options.command === "connect") {
|
|
468
|
-
connect(options);
|
|
979
|
+
await connect(options);
|
|
469
980
|
} else {
|
|
470
981
|
printPlan(options);
|
|
471
982
|
}
|
|
472
|
-
}
|
|
983
|
+
}
|
|
984
|
+
main().catch((error) => {
|
|
473
985
|
console.error(error instanceof Error ? error.message : String(error));
|
|
474
986
|
console.error("");
|
|
475
987
|
console.error(usage());
|
|
476
988
|
process.exit(1);
|
|
477
|
-
}
|
|
989
|
+
});
|