@shadowob/connector 1.1.3-dev.251

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 ADDED
@@ -0,0 +1,157 @@
1
+ # Shadow Connector
2
+
3
+ Connection helpers for attaching a Shadow Buddy token to OpenClaw, Hermes Agent, or cc-connect.
4
+
5
+ The package exports pure plan builders for app UIs and a `shadowob-connector` CLI for terminal setup.
6
+
7
+ ## CLI
8
+
9
+ Print a plan:
10
+
11
+ ```bash
12
+ npx @shadowob/connector@latest plan \
13
+ --target openclaw \
14
+ --server-url https://shadowob.com \
15
+ --token buddy-token
16
+ ```
17
+
18
+ Apply a connector setup:
19
+
20
+ ```bash
21
+ npx @shadowob/connector@latest connect \
22
+ --target hermes \
23
+ --server-url https://shadowob.com \
24
+ --token buddy-token
25
+ ```
26
+
27
+ Use `--dry-run` to preview writes and commands. Use `--json` with `plan` when embedding the plan in another tool.
28
+
29
+ ## OpenClaw
30
+
31
+ ```bash
32
+ npx @shadowob/connector@latest connect \
33
+ --target openclaw \
34
+ --server-url https://shadowob.com \
35
+ --token buddy-token
36
+ ```
37
+
38
+ Equivalent manual steps:
39
+
40
+ ```bash
41
+ openclaw plugins install @shadowob/openclaw-shadowob
42
+ openclaw config set channels.shadowob.token 'buddy-token'
43
+ openclaw config set channels.shadowob.serverUrl 'https://shadowob.com'
44
+ openclaw gateway restart
45
+ ```
46
+
47
+ OpenClaw resolves the Buddy identity from the token and pulls channel policy dynamically from Shadow.
48
+
49
+ ## Hermes Agent
50
+
51
+ ```bash
52
+ npx @shadowob/connector@latest connect \
53
+ --target hermes \
54
+ --server-url https://shadowob.com \
55
+ --token buddy-token
56
+ ```
57
+
58
+ The Hermes plugin is bundled in `hermes-shadowob-plugin/`. The connector copies it to `~/.hermes/plugins/shadowob`, writes the Shadow token/base URL, and enables the plugin.
59
+
60
+ Hermes does not need `agentId` or `channelId` in the setup command. The plugin calls `/api/auth/me` to resolve the Buddy agent id, then `/api/agents/:id/config` to receive channel access policy dynamically, matching the OpenClaw plugin behavior.
61
+
62
+ Manual config shape:
63
+
64
+ ```yaml
65
+ plugins:
66
+ enabled:
67
+ - shadowob
68
+
69
+ platforms:
70
+ shadowob:
71
+ enabled: true
72
+ token: "buddy-token"
73
+ extra:
74
+ base_url: "https://shadowob.com"
75
+ mention_only: false
76
+ rest_only: false
77
+ catchup_minutes: 0
78
+ download_media: true
79
+ slash_commands: []
80
+ ```
81
+
82
+ Optional environment variables:
83
+
84
+ ```bash
85
+ export SHADOW_BASE_URL="https://shadowob.com"
86
+ export SHADOW_TOKEN="buddy-token"
87
+ export SHADOW_HEARTBEAT_INTERVAL_SECONDS=30
88
+ export SHADOW_SLASH_COMMANDS_JSON='[]'
89
+ ```
90
+
91
+ ## cc-connect
92
+
93
+ ```bash
94
+ npx @shadowob/connector@latest connect \
95
+ --target cc-connect \
96
+ --server-url https://shadowob.com \
97
+ --token buddy-token \
98
+ --work-dir . \
99
+ --project-name shadow-buddy \
100
+ --agent-type codex
101
+ ```
102
+
103
+ Equivalent TOML:
104
+
105
+ ```toml
106
+ language = "zh"
107
+
108
+ [[projects]]
109
+ name = "shadow-buddy"
110
+ work_dir = "."
111
+ agent_type = "codex"
112
+
113
+ [[projects.platforms]]
114
+ type = "shadowob"
115
+
116
+ [projects.platforms.options]
117
+ token = "buddy-token"
118
+ server_url = "https://shadowob.com"
119
+ allow_from = "*"
120
+ listen_dms = true
121
+ share_session_in_channel = false
122
+ progress_style = "compact"
123
+ ```
124
+
125
+ ## TypeScript API
126
+
127
+ ```ts
128
+ import { createConnectorPlan, createConnectorPlans } from '@shadowob/connector'
129
+
130
+ const hermes = createConnectorPlan({
131
+ target: 'hermes',
132
+ serverUrl: 'https://shadowob.com',
133
+ token: 'buddy-token',
134
+ })
135
+
136
+ const allPlans = createConnectorPlans({
137
+ serverUrl: 'https://shadowob.com',
138
+ token: 'buddy-token',
139
+ })
140
+ ```
141
+
142
+ ## Tests
143
+
144
+ ```bash
145
+ pnpm --filter @shadowob/connector test
146
+ pnpm -C packages/connector typecheck
147
+ pnpm -C packages/connector build
148
+ uv run --project .tmp/hermes-agent --with pytest python -m pytest packages/connector/hermes-shadowob-plugin/tests
149
+ ```
150
+
151
+ The tests cover plan generation for OpenClaw, Hermes, and cc-connect, and assert that Hermes setup no longer emits static `agentId` or `channelId` arguments.
152
+
153
+ ## Capability Coverage
154
+
155
+ - OpenClaw: channel messages, DMs, threads, mentions, attachments/images, interactive components, slash commands, online status, typing/activity, reactions, edits/deletes.
156
+ - Hermes Agent: channel messages, DMs, threads, attachments/images, interactive components, slash commands, online status, typing/activity, cron delivery.
157
+ - cc-connect: channel messages, DMs, attachments/images, interactive components, slash commands, typing, streaming previews, forms.
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,477 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { spawnSync } from "child_process";
5
+ import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
6
+ import { homedir } from "os";
7
+ import { dirname, resolve } from "path";
8
+ import { fileURLToPath } from "url";
9
+
10
+ // src/index.ts
11
+ var DEFAULT_SERVER_URL = "https://shadowob.com";
12
+ var DEFAULT_WORK_DIR = ".";
13
+ var DEFAULT_PROJECT_NAME = "shadow-buddy";
14
+ var DEFAULT_CC_AGENT = "codex";
15
+ var shellQuote = (value) => {
16
+ if (!value) return "''";
17
+ return `'${value.replace(/'/g, `'\\''`)}'`;
18
+ };
19
+ var normalizeServerUrl = (value) => {
20
+ const trimmed = value.trim() || DEFAULT_SERVER_URL;
21
+ return trimmed.endsWith("/api") ? trimmed.slice(0, -4) : trimmed.replace(/\/$/, "");
22
+ };
23
+ var tokenOrPlaceholder = (token) => token.trim() || "<BUDDY_TOKEN>";
24
+ function buildOpenClawPlan(input) {
25
+ const token = tokenOrPlaceholder(input.token);
26
+ const serverUrl = normalizeServerUrl(input.serverUrl);
27
+ const jsonConfig = JSON.stringify(
28
+ {
29
+ channels: {
30
+ shadowob: {
31
+ token,
32
+ serverUrl
33
+ }
34
+ }
35
+ },
36
+ null,
37
+ 2
38
+ );
39
+ const commands = [
40
+ {
41
+ label: "Install plugin",
42
+ command: "openclaw plugins install @shadowob/openclaw-shadowob"
43
+ },
44
+ {
45
+ label: "Set Buddy token",
46
+ command: `openclaw config set channels.shadowob.token ${shellQuote(token)}`
47
+ },
48
+ {
49
+ label: "Set Shadow server URL",
50
+ command: `openclaw config set channels.shadowob.serverUrl ${shellQuote(serverUrl)}`
51
+ },
52
+ {
53
+ label: "Restart gateway",
54
+ command: "openclaw gateway restart"
55
+ }
56
+ ];
57
+ const quickCommand = commands.map((item) => item.command).join(" && ");
58
+ const connectCommand = [
59
+ "npx @shadowob/connector@latest connect",
60
+ "--target openclaw",
61
+ `--server-url ${shellQuote(serverUrl)}`,
62
+ `--token ${shellQuote(token)}`
63
+ ].join(" ");
64
+ return {
65
+ target: "openclaw",
66
+ title: "OpenClaw",
67
+ summary: "Install the Shadow channel plugin and bind this Buddy token to OpenClaw.",
68
+ connectCommand,
69
+ quickCommand,
70
+ commands,
71
+ configBlocks: [{ label: "openclaw.json", language: "json", content: jsonConfig }],
72
+ aiPrompt: [
73
+ "Configure this Shadow Buddy in OpenClaw.",
74
+ "",
75
+ `Shadow server URL: ${serverUrl}`,
76
+ `Buddy token: ${token}`,
77
+ "",
78
+ "Run these steps in order:",
79
+ ...commands.map((item, index) => `${index + 1}. ${item.command}`),
80
+ "",
81
+ "Confirm each step and then verify the gateway is running."
82
+ ].join("\n"),
83
+ docsUrl: "/product/index.html",
84
+ capabilities: [
85
+ "channelMessages",
86
+ "dms",
87
+ "threads",
88
+ "mentions",
89
+ "attachments",
90
+ "images",
91
+ "interactive",
92
+ "slashCommands",
93
+ "onlineStatus",
94
+ "typing",
95
+ "activityStatus",
96
+ "reactions",
97
+ "editDelete"
98
+ ]
99
+ };
100
+ }
101
+ function buildHermesPlan(input) {
102
+ const token = tokenOrPlaceholder(input.token);
103
+ const serverUrl = normalizeServerUrl(input.serverUrl);
104
+ const envBlock = [
105
+ `SHADOW_BASE_URL=${shellQuote(serverUrl)}`,
106
+ `SHADOW_TOKEN=${shellQuote(token)}`,
107
+ "SHADOW_ALLOW_ALL_USERS=true",
108
+ "SHADOW_HEARTBEAT_INTERVAL_SECONDS=30",
109
+ `SHADOW_SLASH_COMMANDS_JSON=${shellQuote("[]")}`
110
+ ].join("\n");
111
+ const yamlConfig = [
112
+ "plugins:",
113
+ " enabled:",
114
+ " - shadowob",
115
+ "",
116
+ "platforms:",
117
+ " shadowob:",
118
+ " enabled: true",
119
+ ` token: "${token}"`,
120
+ " extra:",
121
+ ` base_url: "${serverUrl}"`,
122
+ " mention_only: false",
123
+ " rest_only: false",
124
+ " catchup_minutes: 0",
125
+ " download_media: true",
126
+ " slash_commands: []"
127
+ ].join("\n");
128
+ const commands = [
129
+ {
130
+ label: "Copy plugin directory",
131
+ command: "mkdir -p ~/.hermes/plugins && cp -R ./packages/connector/hermes-shadowob-plugin ~/.hermes/plugins/shadowob"
132
+ },
133
+ {
134
+ label: "Install plugin dependencies",
135
+ command: "python -m pip install -r ~/.hermes/plugins/shadowob/requirements.txt"
136
+ },
137
+ {
138
+ label: "Enable plugin",
139
+ command: "hermes plugins enable shadowob"
140
+ },
141
+ {
142
+ label: "Start gateway",
143
+ command: "hermes gateway"
144
+ }
145
+ ];
146
+ const connectCommand = [
147
+ "npx @shadowob/connector@latest connect",
148
+ "--target hermes",
149
+ `--server-url ${shellQuote(serverUrl)}`,
150
+ `--token ${shellQuote(token)}`
151
+ ].join(" ");
152
+ return {
153
+ target: "hermes",
154
+ title: "Hermes Agent",
155
+ summary: "Install the ShadowOB Hermes platform plugin and run Hermes gateway for this Buddy.",
156
+ connectCommand,
157
+ quickCommand: commands.map((item) => item.command).join(" && "),
158
+ commands,
159
+ configBlocks: [
160
+ { label: "~/.hermes/.env", language: "bash", content: envBlock },
161
+ { label: "~/.hermes/config.yaml", language: "yaml", content: yamlConfig }
162
+ ],
163
+ aiPrompt: [
164
+ "Configure this Shadow Buddy in Hermes Agent.",
165
+ "",
166
+ `Shadow server URL: ${serverUrl}`,
167
+ `Buddy token: ${token}`,
168
+ "",
169
+ "Install the bundled ShadowOB platform plugin, write the environment values above, enable the plugin, then run hermes gateway. The plugin resolves the Buddy agent id and channel policy from Shadow at runtime."
170
+ ].join("\n"),
171
+ docsUrl: "https://hermes-agent.nousresearch.com/docs/user-guide/messaging",
172
+ capabilities: [
173
+ "channelMessages",
174
+ "dms",
175
+ "threads",
176
+ "attachments",
177
+ "images",
178
+ "interactive",
179
+ "slashCommands",
180
+ "onlineStatus",
181
+ "typing",
182
+ "activityStatus",
183
+ "cronDelivery"
184
+ ]
185
+ };
186
+ }
187
+ function buildCcConnectPlan(input) {
188
+ const token = tokenOrPlaceholder(input.token);
189
+ const serverUrl = normalizeServerUrl(input.serverUrl);
190
+ const projectName = input.projectName?.trim() || DEFAULT_PROJECT_NAME;
191
+ const workDir = input.workDir?.trim() || DEFAULT_WORK_DIR;
192
+ const agentType = input.agentType?.trim() || DEFAULT_CC_AGENT;
193
+ const tomlConfig = [
194
+ 'language = "zh"',
195
+ "",
196
+ "[[projects]]",
197
+ `name = "${projectName}"`,
198
+ `work_dir = "${workDir}"`,
199
+ `agent_type = "${agentType}"`,
200
+ "",
201
+ "[[projects.platforms]]",
202
+ 'type = "shadowob"',
203
+ "",
204
+ "[projects.platforms.options]",
205
+ `token = "${token}"`,
206
+ `server_url = "${serverUrl}"`,
207
+ 'allow_from = "*"',
208
+ "listen_dms = true",
209
+ "share_session_in_channel = false",
210
+ 'progress_style = "compact"'
211
+ ].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
+ const connectCommand = [
222
+ "npx @shadowob/connector@latest connect",
223
+ "--target cc-connect",
224
+ `--server-url ${shellQuote(serverUrl)}`,
225
+ `--token ${shellQuote(token)}`,
226
+ `--work-dir ${shellQuote(workDir)}`,
227
+ `--project-name ${shellQuote(projectName)}`,
228
+ `--agent-type ${shellQuote(agentType)}`
229
+ ].join(" ");
230
+ return {
231
+ target: "cc-connect",
232
+ title: "cc-connect",
233
+ summary: "Use cc-connect ShadowOB Socket.IO platform support with this Buddy token.",
234
+ connectCommand,
235
+ quickCommand: commands.map((item) => item.command).join(" && "),
236
+ commands,
237
+ configBlocks: [{ label: "~/.cc-connect/config.toml", language: "toml", content: tomlConfig }],
238
+ aiPrompt: [
239
+ "Configure this Shadow Buddy in cc-connect.",
240
+ "",
241
+ `Shadow server URL: ${serverUrl}`,
242
+ `Buddy token: ${token}`,
243
+ `Project work_dir: ${workDir}`,
244
+ `Agent type: ${agentType}`,
245
+ "",
246
+ "Install cc-connect, add the TOML platform block, and start cc-connect."
247
+ ].join("\n"),
248
+ docsUrl: "https://github.com/buggyblues/cc-connect/blob/main/docs/shadowob.md",
249
+ capabilities: [
250
+ "channelMessages",
251
+ "dms",
252
+ "attachments",
253
+ "images",
254
+ "interactive",
255
+ "slashCommands",
256
+ "typing",
257
+ "streamingPreviews",
258
+ "forms"
259
+ ]
260
+ };
261
+ }
262
+ function createConnectorPlan(input) {
263
+ if (input.target === "openclaw") return buildOpenClawPlan(input);
264
+ if (input.target === "hermes") return buildHermesPlan(input);
265
+ if (input.target === "cc-connect") return buildCcConnectPlan(input);
266
+ throw new Error(`Unsupported connector target: ${String(input.target)}`);
267
+ }
268
+
269
+ // src/cli.ts
270
+ var TARGETS = /* @__PURE__ */ new Set(["openclaw", "hermes", "cc-connect"]);
271
+ function readOption(args, name) {
272
+ const prefix = `${name}=`;
273
+ const inline = args.find((arg) => arg.startsWith(prefix));
274
+ if (inline) return inline.slice(prefix.length);
275
+ const index = args.indexOf(name);
276
+ if (index >= 0) return args[index + 1];
277
+ return void 0;
278
+ }
279
+ function hasFlag(args, name) {
280
+ return args.includes(name);
281
+ }
282
+ function usage() {
283
+ return [
284
+ "Usage:",
285
+ " shadowob-connector plan --target <openclaw|hermes|cc-connect> --server-url <url> --token <token>",
286
+ " shadowob-connector connect --target <openclaw|hermes|cc-connect> --server-url <url> --token <token>",
287
+ "",
288
+ "Options:",
289
+ " --hermes-home <path> Hermes config directory, default $HERMES_HOME or ~/.hermes",
290
+ " --work-dir <path> cc-connect project work directory",
291
+ " --project-name <name> cc-connect project name",
292
+ " --agent-type <type> cc-connect agent type, default codex",
293
+ " --json Print the full plan as JSON",
294
+ " --force Overwrite target config files when needed",
295
+ " --install Install cc-connect when target is cc-connect",
296
+ " --no-install Skip Hermes dependency install and plugin enablement",
297
+ " --start Start Hermes gateway or cc-connect after setup",
298
+ " --dry-run Show what would be applied without changing files"
299
+ ].join("\n");
300
+ }
301
+ function parseArgs(args) {
302
+ if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
303
+ console.log(usage());
304
+ process.exit(0);
305
+ }
306
+ const commandArg = args[0];
307
+ const command = commandArg === "connect" || commandArg === "plan" ? commandArg : "plan";
308
+ const optionArgs = command === "plan" ? args.filter((arg) => arg !== "plan") : args.slice(1);
309
+ const target = readOption(optionArgs, "--target");
310
+ if (!target || !TARGETS.has(target)) {
311
+ throw new Error("Missing or invalid --target");
312
+ }
313
+ const install = target === "cc-connect" ? hasFlag(optionArgs, "--install") : !hasFlag(optionArgs, "--no-install");
314
+ return {
315
+ command,
316
+ target,
317
+ serverUrl: readOption(optionArgs, "--server-url") ?? "https://shadowob.com",
318
+ token: readOption(optionArgs, "--token") ?? "",
319
+ hermesHome: readOption(optionArgs, "--hermes-home"),
320
+ workDir: readOption(optionArgs, "--work-dir"),
321
+ projectName: readOption(optionArgs, "--project-name"),
322
+ agentType: readOption(optionArgs, "--agent-type"),
323
+ json: hasFlag(optionArgs, "--json"),
324
+ force: hasFlag(optionArgs, "--force"),
325
+ install,
326
+ start: hasFlag(optionArgs, "--start"),
327
+ dryRun: hasFlag(optionArgs, "--dry-run")
328
+ };
329
+ }
330
+ function printPlan(options) {
331
+ const plan = createConnectorPlan(options);
332
+ if (options.json) {
333
+ console.log(JSON.stringify(plan, null, 2));
334
+ return;
335
+ }
336
+ console.log(`# ${plan.title}`);
337
+ console.log(plan.summary);
338
+ console.log("");
339
+ console.log("## Quick command");
340
+ console.log(plan.quickCommand);
341
+ for (const block of plan.configBlocks) {
342
+ console.log("");
343
+ console.log(`## ${block.label}`);
344
+ console.log(block.content);
345
+ }
346
+ }
347
+ function runShell(command, dryRun) {
348
+ if (dryRun) {
349
+ console.log(`[dry-run] ${command}`);
350
+ return;
351
+ }
352
+ const result = spawnSync(command, { shell: true, stdio: "inherit" });
353
+ if (result.status !== 0) {
354
+ throw new Error(`Command failed with exit code ${result.status ?? "unknown"}: ${command}`);
355
+ }
356
+ }
357
+ function writeFile(path, content, dryRun) {
358
+ if (dryRun) {
359
+ console.log(`[dry-run] write ${path}`);
360
+ return;
361
+ }
362
+ mkdirSync(dirname(path), { recursive: true });
363
+ writeFileSync(path, content.endsWith("\n") ? content : `${content}
364
+ `);
365
+ }
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
+ function packageRoot() {
380
+ return resolve(dirname(fileURLToPath(import.meta.url)), "..");
381
+ }
382
+ function expandHome(value) {
383
+ return value.startsWith("~/") ? resolve(homedir(), value.slice(2)) : resolve(value);
384
+ }
385
+ function hermesPluginSource() {
386
+ const candidates = [
387
+ resolve(packageRoot(), "hermes-shadowob-plugin"),
388
+ resolve(process.cwd(), "packages/connector/hermes-shadowob-plugin")
389
+ ];
390
+ const found = candidates.find((candidate) => existsSync(candidate));
391
+ if (!found) throw new Error("Cannot find bundled hermes-shadowob-plugin directory");
392
+ return found;
393
+ }
394
+ function applyOpenClaw(options) {
395
+ const plan = createConnectorPlan(options);
396
+ for (const step of plan.commands) {
397
+ console.log(`Applying: ${step.label}`);
398
+ runShell(step.command, options.dryRun);
399
+ }
400
+ }
401
+ function applyHermes(options) {
402
+ const plan = createConnectorPlan(options);
403
+ const hermesDir = expandHome(options.hermesHome ?? process.env.HERMES_HOME ?? "~/.hermes");
404
+ const pluginTarget = resolve(hermesDir, "plugins/shadowob");
405
+ const envPath = resolve(hermesDir, ".env");
406
+ const configPath = resolve(hermesDir, "config.yaml");
407
+ const generatedConfigPath = resolve(hermesDir, "config.shadowob.yaml");
408
+ const envBlock = plan.configBlocks.find((block) => block.label === "~/.hermes/.env");
409
+ const yamlBlock = plan.configBlocks.find((block) => block.label === "~/.hermes/config.yaml");
410
+ if (!envBlock || !yamlBlock) throw new Error("Hermes plan is missing config blocks");
411
+ if (options.dryRun) {
412
+ console.log(`[dry-run] copy ${hermesPluginSource()} -> ${pluginTarget}`);
413
+ } else {
414
+ mkdirSync(resolve(hermesDir, "plugins"), { recursive: true });
415
+ cpSync(hermesPluginSource(), pluginTarget, { recursive: true, force: true });
416
+ }
417
+ upsertManagedBlock(envPath, "Hermes ShadowOB", envBlock.content, options.dryRun);
418
+ if (!existsSync(configPath) || options.force) {
419
+ writeFile(configPath, yamlBlock.content, options.dryRun);
420
+ } else {
421
+ writeFile(generatedConfigPath, yamlBlock.content, options.dryRun);
422
+ console.log(`Existing Hermes config kept. Generated ShadowOB config: ${generatedConfigPath}`);
423
+ }
424
+ if (options.install) {
425
+ runShell(
426
+ `python -m pip install -r "${resolve(pluginTarget, "requirements.txt")}"`,
427
+ options.dryRun
428
+ );
429
+ runShell("hermes plugins enable shadowob", options.dryRun);
430
+ }
431
+ if (options.start) {
432
+ runShell("hermes gateway", options.dryRun);
433
+ }
434
+ }
435
+ function applyCcConnect(options) {
436
+ const plan = createConnectorPlan(options);
437
+ const configBlock = plan.configBlocks.find((block) => block.label === "~/.cc-connect/config.toml");
438
+ if (!configBlock) throw new Error("cc-connect plan is missing config block");
439
+ const configPath = resolve(homedir(), ".cc-connect/config.toml");
440
+ const generatedPath = resolve(homedir(), ".cc-connect/config.shadowob.toml");
441
+ if (!existsSync(configPath) || options.force) {
442
+ writeFile(configPath, configBlock.content, options.dryRun);
443
+ } else {
444
+ writeFile(generatedPath, configBlock.content, options.dryRun);
445
+ console.log(`Existing cc-connect config kept. Generated ShadowOB config: ${generatedPath}`);
446
+ }
447
+ if (options.install) {
448
+ runShell("npm install -g cc-connect", options.dryRun);
449
+ }
450
+ if (options.start) {
451
+ runShell("cc-connect", options.dryRun);
452
+ }
453
+ }
454
+ function connect(options) {
455
+ if (options.target === "openclaw") {
456
+ applyOpenClaw(options);
457
+ return;
458
+ }
459
+ if (options.target === "hermes") {
460
+ applyHermes(options);
461
+ return;
462
+ }
463
+ applyCcConnect(options);
464
+ }
465
+ try {
466
+ const options = parseArgs(process.argv.slice(2));
467
+ if (options.command === "connect") {
468
+ connect(options);
469
+ } else {
470
+ printPlan(options);
471
+ }
472
+ } catch (error) {
473
+ console.error(error instanceof Error ? error.message : String(error));
474
+ console.error("");
475
+ console.error(usage());
476
+ process.exit(1);
477
+ }