@shadowob/connector 1.1.3-dev.281 → 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 CHANGED
@@ -98,6 +98,11 @@ export SHADOW_SLASH_COMMANDS_JSON='[]'
98
98
 
99
99
  ## cc-connect
100
100
 
101
+ The connector uses the ShadowOB-capable fork
102
+ `buggyblues/cc-connect@63b5d59`. It does not install the official npm
103
+ `cc-connect` package, because the npm package currently points to the upstream
104
+ `chenhg5/cc-connect` release line.
105
+
101
106
  ```bash
102
107
  npx @shadowob/connector@latest connect \
103
108
  --target cc-connect \
@@ -105,9 +110,18 @@ npx @shadowob/connector@latest connect \
105
110
  --token buddy-token \
106
111
  --work-dir . \
107
112
  --project-name shadow-buddy \
108
- --agent-type codex
113
+ --agent-type codex \
114
+ --install \
115
+ --start
109
116
  ```
110
117
 
118
+ With `--install`, the CLI first tries the fork's GitHub release asset matching
119
+ the local OS/CPU and verifies its pinned SHA-256. If the fork release asset is
120
+ missing or does not match, it pulls the pinned source archive, builds a `no_web`
121
+ Go binary, caches it under
122
+ `~/.shadowob/connector/cc-connect/63b5d59/bin/`, and starts that binary when
123
+ `--start` is present.
124
+
111
125
  Equivalent TOML:
112
126
 
113
127
  ```toml
@@ -115,8 +129,12 @@ language = "zh"
115
129
 
116
130
  [[projects]]
117
131
  name = "shadow-buddy"
132
+
133
+ [projects.agent]
134
+ type = "codex"
135
+
136
+ [projects.agent.options]
118
137
  work_dir = "."
119
- agent_type = "codex"
120
138
 
121
139
  [[projects.platforms]]
122
140
  type = "shadowob"
@@ -160,6 +178,6 @@ The tests cover plan generation, config merging for OpenClaw/Hermes/cc-connect,
160
178
 
161
179
  ## Capability Coverage
162
180
 
163
- - OpenClaw: channel messages, DMs, threads, mentions, attachments/images, interactive components, slash commands, online status, typing/activity, reactions, edits/deletes.
164
- - Hermes Agent: channel messages, DMs, threads, attachments/images, interactive components, slash commands, online status, typing/activity, cron delivery.
165
- - cc-connect: channel messages, DMs, attachments/images, interactive components, slash commands, typing, streaming previews, forms.
181
+ - OpenClaw: channel messages, DMs, threads, mentions, attachments/images, interactive components, slash commands, online status, typing/activity, reactions, edits/deletes, status checks, usage/cost telemetry, multi-Agent Buddy binding, Shadow CLI login/notifications, official skills, cron tasks.
182
+ - Hermes Agent: channel messages, DMs, threads, attachments/images, interactive components, slash commands, online status, typing/activity, cron delivery, status checks, usage/cost telemetry, Shadow CLI login/notifications, official skills.
183
+ - cc-connect: channel messages, DMs, attachments/images, interactive components, slash commands, typing, streaming previews, forms, status checks, usage/cost telemetry, multi-Agent Buddy binding, Shadow CLI login/notifications.
package/dist/cli.js CHANGED
@@ -1,11 +1,280 @@
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
- import { fileURLToPath } from "url";
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
+ }
9
278
 
10
279
  // src/config-writers.ts
11
280
  import { parse as parseDotenv } from "dotenv";
@@ -150,17 +419,30 @@ function tomlArray(value) {
150
419
  }
151
420
  return tables;
152
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
+ }
153
427
  function mergeCcConnectConfigContent(existing, values) {
154
428
  const root = parseTomlRoot(existing, "cc-connect");
155
429
  const projects = tomlArray(root.projects);
156
- let project = projects.find((item) => item.name === values.projectName) ?? projects.find((item) => item.work_dir === values.workDir);
430
+ let project = projects.find((item) => item.name === values.projectName) ?? projects.find((item) => ccConnectProjectWorkDir(item) === values.workDir);
157
431
  if (!project) {
158
432
  project = {};
159
433
  projects.push(project);
160
434
  }
161
435
  project.name = values.projectName;
162
- project.work_dir = values.workDir;
163
- project.agent_type = values.agentType;
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;
164
446
  const platforms = tomlArray(project.platforms);
165
447
  let shadowPlatform = platforms.find((item) => item.type === "shadowob");
166
448
  if (!shadowPlatform) {
@@ -270,7 +552,14 @@ function buildOpenClawPlan(input) {
270
552
  "typing",
271
553
  "activityStatus",
272
554
  "reactions",
273
- "editDelete"
555
+ "editDelete",
556
+ "statusChecks",
557
+ "usageCosts",
558
+ "multiAgentBinding",
559
+ "shadowCliLogin",
560
+ "notifications",
561
+ "officialSkills",
562
+ "cronTasks"
274
563
  ]
275
564
  };
276
565
  }
@@ -356,7 +645,12 @@ function buildHermesPlan(input) {
356
645
  "onlineStatus",
357
646
  "typing",
358
647
  "activityStatus",
359
- "cronDelivery"
648
+ "cronDelivery",
649
+ "statusChecks",
650
+ "usageCosts",
651
+ "shadowCliLogin",
652
+ "notifications",
653
+ "officialSkills"
360
654
  ]
361
655
  };
362
656
  }
@@ -371,8 +665,12 @@ function buildCcConnectPlan(input) {
371
665
  "",
372
666
  "[[projects]]",
373
667
  `name = "${projectName}"`,
668
+ "",
669
+ "[projects.agent]",
670
+ `type = "${agentType}"`,
671
+ "",
672
+ "[projects.agent.options]",
374
673
  `work_dir = "${workDir}"`,
375
- `agent_type = "${agentType}"`,
376
674
  "",
377
675
  "[[projects.platforms]]",
378
676
  'type = "shadowob"',
@@ -385,15 +683,6 @@ function buildCcConnectPlan(input) {
385
683
  "share_session_in_channel = false",
386
684
  'progress_style = "compact"'
387
685
  ].join("\n");
388
- const commands = [
389
- { label: "Install cc-connect", command: "npm install -g cc-connect" },
390
- { label: "Create config directory", command: "mkdir -p ~/.cc-connect" },
391
- {
392
- label: "Edit config",
393
- command: "$EDITOR ~/.cc-connect/config.toml"
394
- },
395
- { label: "Start cc-connect", command: "cc-connect" }
396
- ];
397
686
  const connectCommand = [
398
687
  "npx @shadowob/connector@latest connect",
399
688
  "--target cc-connect",
@@ -403,12 +692,26 @@ function buildCcConnectPlan(input) {
403
692
  `--project-name ${shellQuote(projectName)}`,
404
693
  `--agent-type ${shellQuote(agentType)}`
405
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
+ ];
406
709
  return {
407
710
  target: "cc-connect",
408
711
  title: "cc-connect",
409
- summary: "Use cc-connect ShadowOB Socket.IO platform support with this Buddy token.",
410
- connectCommand,
411
- quickCommand: commands.map((item) => item.command).join(" && "),
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,
412
715
  commands,
413
716
  configBlocks: [{ label: "~/.cc-connect/config.toml", language: "toml", content: tomlConfig }],
414
717
  aiPrompt: [
@@ -419,9 +722,9 @@ function buildCcConnectPlan(input) {
419
722
  `Project work_dir: ${workDir}`,
420
723
  `Agent type: ${agentType}`,
421
724
  "",
422
- "Install cc-connect, add the TOML platform block, and start cc-connect."
725
+ `Install ${CC_CONNECT_FORK_REPO}@${CC_CONNECT_FORK_SHORT_REF}, add the TOML platform block, and start cc-connect.`
423
726
  ].join("\n"),
424
- docsUrl: "https://github.com/buggyblues/cc-connect/blob/main/docs/shadowob.md",
727
+ docsUrl: CC_CONNECT_FORK_DOCS_URL,
425
728
  capabilities: [
426
729
  "channelMessages",
427
730
  "dms",
@@ -431,7 +734,12 @@ function buildCcConnectPlan(input) {
431
734
  "slashCommands",
432
735
  "typing",
433
736
  "streamingPreviews",
434
- "forms"
737
+ "forms",
738
+ "statusChecks",
739
+ "usageCosts",
740
+ "multiAgentBinding",
741
+ "shadowCliLogin",
742
+ "notifications"
435
743
  ]
436
744
  };
437
745
  }
@@ -469,7 +777,7 @@ function usage() {
469
777
  " --agent-type <type> cc-connect agent type, default codex",
470
778
  " --json Print the full plan as JSON",
471
779
  " --force Overwrite target config files when needed",
472
- " --install Install cc-connect when target is cc-connect",
780
+ " --install Install the ShadowOB cc-connect fork when target is cc-connect",
473
781
  " --no-install Skip Hermes dependency install and plugin enablement",
474
782
  " --start Start Hermes gateway or cc-connect after setup",
475
783
  " --dry-run Show what would be applied without changing files"
@@ -527,28 +835,39 @@ function runShell(command, dryRun) {
527
835
  console.log(`[dry-run] ${command}`);
528
836
  return;
529
837
  }
530
- const result = spawnSync(command, { shell: true, stdio: "inherit" });
838
+ const result = spawnSync2(command, { shell: true, stdio: "inherit" });
531
839
  if (result.status !== 0) {
532
840
  throw new Error(`Command failed with exit code ${result.status ?? "unknown"}: ${command}`);
533
841
  }
534
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
+ }
535
854
  function writeFile(path, content, dryRun) {
536
855
  if (dryRun) {
537
856
  console.log(`[dry-run] write ${path}`);
538
857
  return;
539
858
  }
540
- mkdirSync(dirname(path), { recursive: true });
541
- writeFileSync(path, content.endsWith("\n") ? content : `${content}
859
+ mkdirSync2(dirname2(path), { recursive: true });
860
+ writeFileSync2(path, content.endsWith("\n") ? content : `${content}
542
861
  `);
543
862
  }
544
863
  function packageRoot() {
545
- return resolve(dirname(fileURLToPath(import.meta.url)), "..");
864
+ return resolve2(dirname2(fileURLToPath(import.meta.url)), "..");
546
865
  }
547
- function expandHome(value) {
548
- return value.startsWith("~/") ? resolve(homedir(), value.slice(2)) : resolve(value);
866
+ function expandHome2(value) {
867
+ return value.startsWith("~/") ? resolve2(homedir2(), value.slice(2)) : resolve2(value);
549
868
  }
550
869
  function readExisting(path) {
551
- return existsSync(path) ? readFileSync(path, "utf8") : "";
870
+ return existsSync2(path) ? readFileSync(path, "utf8") : "";
552
871
  }
553
872
  function normalizeServerUrl2(value) {
554
873
  const trimmed = value.trim() || "https://shadowob.com";
@@ -556,16 +875,16 @@ function normalizeServerUrl2(value) {
556
875
  }
557
876
  function hermesPluginSource() {
558
877
  const candidates = [
559
- resolve(packageRoot(), "hermes-shadowob-plugin"),
560
- resolve(process.cwd(), "packages/connector/hermes-shadowob-plugin")
878
+ resolve2(packageRoot(), "hermes-shadowob-plugin"),
879
+ resolve2(process.cwd(), "packages/connector/hermes-shadowob-plugin")
561
880
  ];
562
- const found = candidates.find((candidate) => existsSync(candidate));
881
+ const found = candidates.find((candidate) => existsSync2(candidate));
563
882
  if (!found) throw new Error("Cannot find bundled hermes-shadowob-plugin directory");
564
883
  return found;
565
884
  }
566
885
  function applyOpenClaw(options) {
567
886
  const plan = createConnectorPlan(options);
568
- const configPath = expandHome(
887
+ const configPath = expandHome2(
569
888
  options.openclawConfig ?? process.env.OPENCLAW_CONFIG ?? "~/.shadowob/openclaw.json"
570
889
  );
571
890
  console.log("Applying: Install plugin");
@@ -584,16 +903,16 @@ function applyOpenClaw(options) {
584
903
  }
585
904
  function applyHermes(options) {
586
905
  const plan = createConnectorPlan(options);
587
- const hermesDir = expandHome(options.hermesHome ?? process.env.HERMES_HOME ?? "~/.hermes");
588
- const pluginTarget = resolve(hermesDir, "plugins/shadowob");
589
- const envPath = resolve(hermesDir, ".env");
590
- const configPath = resolve(hermesDir, "config.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");
591
910
  const envBlock = plan.configBlocks.find((block) => block.label === "~/.hermes/.env");
592
911
  if (!envBlock) throw new Error("Hermes plan is missing config blocks");
593
912
  if (options.dryRun) {
594
913
  console.log(`[dry-run] copy ${hermesPluginSource()} -> ${pluginTarget}`);
595
914
  } else {
596
- mkdirSync(resolve(hermesDir, "plugins"), { recursive: true });
915
+ mkdirSync2(resolve2(hermesDir, "plugins"), { recursive: true });
597
916
  cpSync(hermesPluginSource(), pluginTarget, { recursive: true, force: true });
598
917
  }
599
918
  const nextEnv = options.force ? envBlock.content : mergeEnvContent(readExisting(envPath), {
@@ -608,7 +927,7 @@ function applyHermes(options) {
608
927
  writeFile(configPath, nextConfig, options.dryRun);
609
928
  if (options.install) {
610
929
  runShell(
611
- `python -m pip install -r "${resolve(pluginTarget, "requirements.txt")}"`,
930
+ `python -m pip install -r "${resolve2(pluginTarget, "requirements.txt")}"`,
612
931
  options.dryRun
613
932
  );
614
933
  runShell("hermes plugins enable shadowob", options.dryRun);
@@ -617,11 +936,11 @@ function applyHermes(options) {
617
936
  runShell("hermes gateway", options.dryRun);
618
937
  }
619
938
  }
620
- function applyCcConnect(options) {
939
+ async function applyCcConnect(options) {
621
940
  const plan = createConnectorPlan(options);
622
941
  const configBlock = plan.configBlocks.find((block) => block.label === "~/.cc-connect/config.toml");
623
942
  if (!configBlock) throw new Error("cc-connect plan is missing config block");
624
- const configPath = resolve(homedir(), ".cc-connect/config.toml");
943
+ const configPath = resolve2(homedir2(), ".cc-connect/config.toml");
625
944
  const nextConfig = options.force ? configBlock.content : mergeCcConnectConfigContent(readExisting(configPath), {
626
945
  token: options.token,
627
946
  serverUrl: normalizeServerUrl2(options.serverUrl),
@@ -630,14 +949,20 @@ function applyCcConnect(options) {
630
949
  agentType: options.agentType?.trim() || "codex"
631
950
  });
632
951
  writeFile(configPath, nextConfig, options.dryRun);
633
- if (options.install) {
634
- runShell("npm install -g cc-connect", 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}`);
635
960
  }
636
961
  if (options.start) {
637
- runShell("cc-connect", options.dryRun);
962
+ runBinary(binaryPath ?? "cc-connect", [], options.dryRun);
638
963
  }
639
964
  }
640
- function connect(options) {
965
+ async function connect(options) {
641
966
  if (options.target === "openclaw") {
642
967
  applyOpenClaw(options);
643
968
  return;
@@ -646,18 +971,19 @@ function connect(options) {
646
971
  applyHermes(options);
647
972
  return;
648
973
  }
649
- applyCcConnect(options);
974
+ await applyCcConnect(options);
650
975
  }
651
- try {
976
+ async function main() {
652
977
  const options = parseArgs(process.argv.slice(2));
653
978
  if (options.command === "connect") {
654
- connect(options);
979
+ await connect(options);
655
980
  } else {
656
981
  printPlan(options);
657
982
  }
658
- } catch (error) {
983
+ }
984
+ main().catch((error) => {
659
985
  console.error(error instanceof Error ? error.message : String(error));
660
986
  console.error("");
661
987
  console.error(usage());
662
988
  process.exit(1);
663
- }
989
+ });
package/dist/index.cjs CHANGED
@@ -24,6 +24,14 @@ __export(index_exports, {
24
24
  createConnectorPlans: () => createConnectorPlans
25
25
  });
26
26
  module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/cc-connect-fork.ts
29
+ var CC_CONNECT_FORK_REPO = "buggyblues/cc-connect";
30
+ var CC_CONNECT_FORK_REF = "63b5d59127b3004bc7002f2d51892b1f2a91ea83";
31
+ var CC_CONNECT_FORK_SHORT_REF = CC_CONNECT_FORK_REF.slice(0, 7);
32
+ var CC_CONNECT_FORK_DOCS_URL = `https://github.com/${CC_CONNECT_FORK_REPO}/blob/main/docs/shadowob.md`;
33
+
34
+ // src/index.ts
27
35
  var DEFAULT_SERVER_URL = "https://shadowob.com";
28
36
  var DEFAULT_WORK_DIR = ".";
29
37
  var DEFAULT_PROJECT_NAME = "shadow-buddy";
@@ -110,7 +118,14 @@ function buildOpenClawPlan(input) {
110
118
  "typing",
111
119
  "activityStatus",
112
120
  "reactions",
113
- "editDelete"
121
+ "editDelete",
122
+ "statusChecks",
123
+ "usageCosts",
124
+ "multiAgentBinding",
125
+ "shadowCliLogin",
126
+ "notifications",
127
+ "officialSkills",
128
+ "cronTasks"
114
129
  ]
115
130
  };
116
131
  }
@@ -196,7 +211,12 @@ function buildHermesPlan(input) {
196
211
  "onlineStatus",
197
212
  "typing",
198
213
  "activityStatus",
199
- "cronDelivery"
214
+ "cronDelivery",
215
+ "statusChecks",
216
+ "usageCosts",
217
+ "shadowCliLogin",
218
+ "notifications",
219
+ "officialSkills"
200
220
  ]
201
221
  };
202
222
  }
@@ -211,8 +231,12 @@ function buildCcConnectPlan(input) {
211
231
  "",
212
232
  "[[projects]]",
213
233
  `name = "${projectName}"`,
234
+ "",
235
+ "[projects.agent]",
236
+ `type = "${agentType}"`,
237
+ "",
238
+ "[projects.agent.options]",
214
239
  `work_dir = "${workDir}"`,
215
- `agent_type = "${agentType}"`,
216
240
  "",
217
241
  "[[projects.platforms]]",
218
242
  'type = "shadowob"',
@@ -225,15 +249,6 @@ function buildCcConnectPlan(input) {
225
249
  "share_session_in_channel = false",
226
250
  'progress_style = "compact"'
227
251
  ].join("\n");
228
- const commands = [
229
- { label: "Install cc-connect", command: "npm install -g cc-connect" },
230
- { label: "Create config directory", command: "mkdir -p ~/.cc-connect" },
231
- {
232
- label: "Edit config",
233
- command: "$EDITOR ~/.cc-connect/config.toml"
234
- },
235
- { label: "Start cc-connect", command: "cc-connect" }
236
- ];
237
252
  const connectCommand = [
238
253
  "npx @shadowob/connector@latest connect",
239
254
  "--target cc-connect",
@@ -243,12 +258,26 @@ function buildCcConnectPlan(input) {
243
258
  `--project-name ${shellQuote(projectName)}`,
244
259
  `--agent-type ${shellQuote(agentType)}`
245
260
  ].join(" ");
261
+ const installCommand = `${connectCommand} --install`;
262
+ const startCommand = `${connectCommand} --install --start`;
263
+ const commands = [
264
+ {
265
+ label: "Install ShadowOB cc-connect fork",
266
+ command: installCommand
267
+ },
268
+ { label: "Create config directory", command: "mkdir -p ~/.cc-connect" },
269
+ {
270
+ label: "Edit config",
271
+ command: "$EDITOR ~/.cc-connect/config.toml"
272
+ },
273
+ { label: "Start ShadowOB cc-connect fork", command: startCommand }
274
+ ];
246
275
  return {
247
276
  target: "cc-connect",
248
277
  title: "cc-connect",
249
- summary: "Use cc-connect ShadowOB Socket.IO platform support with this Buddy token.",
250
- connectCommand,
251
- quickCommand: commands.map((item) => item.command).join(" && "),
278
+ summary: `Use ${CC_CONNECT_FORK_REPO}@${CC_CONNECT_FORK_SHORT_REF} with ShadowOB Socket.IO platform support for this Buddy token.`,
279
+ connectCommand: startCommand,
280
+ quickCommand: startCommand,
252
281
  commands,
253
282
  configBlocks: [{ label: "~/.cc-connect/config.toml", language: "toml", content: tomlConfig }],
254
283
  aiPrompt: [
@@ -259,9 +288,9 @@ function buildCcConnectPlan(input) {
259
288
  `Project work_dir: ${workDir}`,
260
289
  `Agent type: ${agentType}`,
261
290
  "",
262
- "Install cc-connect, add the TOML platform block, and start cc-connect."
291
+ `Install ${CC_CONNECT_FORK_REPO}@${CC_CONNECT_FORK_SHORT_REF}, add the TOML platform block, and start cc-connect.`
263
292
  ].join("\n"),
264
- docsUrl: "https://github.com/buggyblues/cc-connect/blob/main/docs/shadowob.md",
293
+ docsUrl: CC_CONNECT_FORK_DOCS_URL,
265
294
  capabilities: [
266
295
  "channelMessages",
267
296
  "dms",
@@ -271,7 +300,12 @@ function buildCcConnectPlan(input) {
271
300
  "slashCommands",
272
301
  "typing",
273
302
  "streamingPreviews",
274
- "forms"
303
+ "forms",
304
+ "statusChecks",
305
+ "usageCosts",
306
+ "multiAgentBinding",
307
+ "shadowCliLogin",
308
+ "notifications"
275
309
  ]
276
310
  };
277
311
  }
package/dist/index.js CHANGED
@@ -1,3 +1,9 @@
1
+ // src/cc-connect-fork.ts
2
+ var CC_CONNECT_FORK_REPO = "buggyblues/cc-connect";
3
+ var CC_CONNECT_FORK_REF = "63b5d59127b3004bc7002f2d51892b1f2a91ea83";
4
+ var CC_CONNECT_FORK_SHORT_REF = CC_CONNECT_FORK_REF.slice(0, 7);
5
+ var CC_CONNECT_FORK_DOCS_URL = `https://github.com/${CC_CONNECT_FORK_REPO}/blob/main/docs/shadowob.md`;
6
+
1
7
  // src/index.ts
2
8
  var DEFAULT_SERVER_URL = "https://shadowob.com";
3
9
  var DEFAULT_WORK_DIR = ".";
@@ -85,7 +91,14 @@ function buildOpenClawPlan(input) {
85
91
  "typing",
86
92
  "activityStatus",
87
93
  "reactions",
88
- "editDelete"
94
+ "editDelete",
95
+ "statusChecks",
96
+ "usageCosts",
97
+ "multiAgentBinding",
98
+ "shadowCliLogin",
99
+ "notifications",
100
+ "officialSkills",
101
+ "cronTasks"
89
102
  ]
90
103
  };
91
104
  }
@@ -171,7 +184,12 @@ function buildHermesPlan(input) {
171
184
  "onlineStatus",
172
185
  "typing",
173
186
  "activityStatus",
174
- "cronDelivery"
187
+ "cronDelivery",
188
+ "statusChecks",
189
+ "usageCosts",
190
+ "shadowCliLogin",
191
+ "notifications",
192
+ "officialSkills"
175
193
  ]
176
194
  };
177
195
  }
@@ -186,8 +204,12 @@ function buildCcConnectPlan(input) {
186
204
  "",
187
205
  "[[projects]]",
188
206
  `name = "${projectName}"`,
207
+ "",
208
+ "[projects.agent]",
209
+ `type = "${agentType}"`,
210
+ "",
211
+ "[projects.agent.options]",
189
212
  `work_dir = "${workDir}"`,
190
- `agent_type = "${agentType}"`,
191
213
  "",
192
214
  "[[projects.platforms]]",
193
215
  'type = "shadowob"',
@@ -200,15 +222,6 @@ function buildCcConnectPlan(input) {
200
222
  "share_session_in_channel = false",
201
223
  'progress_style = "compact"'
202
224
  ].join("\n");
203
- const commands = [
204
- { label: "Install cc-connect", command: "npm install -g cc-connect" },
205
- { label: "Create config directory", command: "mkdir -p ~/.cc-connect" },
206
- {
207
- label: "Edit config",
208
- command: "$EDITOR ~/.cc-connect/config.toml"
209
- },
210
- { label: "Start cc-connect", command: "cc-connect" }
211
- ];
212
225
  const connectCommand = [
213
226
  "npx @shadowob/connector@latest connect",
214
227
  "--target cc-connect",
@@ -218,12 +231,26 @@ function buildCcConnectPlan(input) {
218
231
  `--project-name ${shellQuote(projectName)}`,
219
232
  `--agent-type ${shellQuote(agentType)}`
220
233
  ].join(" ");
234
+ const installCommand = `${connectCommand} --install`;
235
+ const startCommand = `${connectCommand} --install --start`;
236
+ const commands = [
237
+ {
238
+ label: "Install ShadowOB cc-connect fork",
239
+ command: installCommand
240
+ },
241
+ { label: "Create config directory", command: "mkdir -p ~/.cc-connect" },
242
+ {
243
+ label: "Edit config",
244
+ command: "$EDITOR ~/.cc-connect/config.toml"
245
+ },
246
+ { label: "Start ShadowOB cc-connect fork", command: startCommand }
247
+ ];
221
248
  return {
222
249
  target: "cc-connect",
223
250
  title: "cc-connect",
224
- summary: "Use cc-connect ShadowOB Socket.IO platform support with this Buddy token.",
225
- connectCommand,
226
- quickCommand: commands.map((item) => item.command).join(" && "),
251
+ summary: `Use ${CC_CONNECT_FORK_REPO}@${CC_CONNECT_FORK_SHORT_REF} with ShadowOB Socket.IO platform support for this Buddy token.`,
252
+ connectCommand: startCommand,
253
+ quickCommand: startCommand,
227
254
  commands,
228
255
  configBlocks: [{ label: "~/.cc-connect/config.toml", language: "toml", content: tomlConfig }],
229
256
  aiPrompt: [
@@ -234,9 +261,9 @@ function buildCcConnectPlan(input) {
234
261
  `Project work_dir: ${workDir}`,
235
262
  `Agent type: ${agentType}`,
236
263
  "",
237
- "Install cc-connect, add the TOML platform block, and start cc-connect."
264
+ `Install ${CC_CONNECT_FORK_REPO}@${CC_CONNECT_FORK_SHORT_REF}, add the TOML platform block, and start cc-connect.`
238
265
  ].join("\n"),
239
- docsUrl: "https://github.com/buggyblues/cc-connect/blob/main/docs/shadowob.md",
266
+ docsUrl: CC_CONNECT_FORK_DOCS_URL,
240
267
  capabilities: [
241
268
  "channelMessages",
242
269
  "dms",
@@ -246,7 +273,12 @@ function buildCcConnectPlan(input) {
246
273
  "slashCommands",
247
274
  "typing",
248
275
  "streamingPreviews",
249
- "forms"
276
+ "forms",
277
+ "statusChecks",
278
+ "usageCosts",
279
+ "multiAgentBinding",
280
+ "shadowCliLogin",
281
+ "notifications"
250
282
  ]
251
283
  };
252
284
  }
@@ -241,6 +241,21 @@ class ShadowAsyncClient:
241
241
  async def get_channel(self, channel_id: str) -> JsonDict:
242
242
  return await self.request("GET", f"/api/channels/{quote(str(channel_id), safe='')}")
243
243
 
244
+ async def get_channel_bootstrap(
245
+ self,
246
+ channel_id: str,
247
+ *,
248
+ messages_limit: int | None = None,
249
+ ) -> JsonDict:
250
+ params: JsonDict = {}
251
+ if messages_limit is not None:
252
+ params["messagesLimit"] = int(messages_limit)
253
+ return await self.request(
254
+ "GET",
255
+ f"/api/channels/{quote(str(channel_id), safe='')}/bootstrap",
256
+ params=params or None,
257
+ )
258
+
244
259
  async def get_messages(
245
260
  self,
246
261
  channel_id: str,
@@ -263,11 +278,20 @@ class ShadowAsyncClient:
263
278
  async def get_message(self, message_id: str) -> JsonDict:
264
279
  return await self.request("GET", f"/api/messages/{quote(str(message_id), safe='')}")
265
280
 
266
- async def resolve_attachment_media_url(self, attachment_id: str, *, disposition: str = "inline") -> JsonDict:
281
+ async def resolve_attachment_media_url(
282
+ self,
283
+ attachment_id: str,
284
+ *,
285
+ disposition: str = "inline",
286
+ variant: str | None = None,
287
+ ) -> JsonDict:
288
+ params: JsonDict = {"disposition": disposition}
289
+ if variant:
290
+ params["variant"] = variant
267
291
  return await self.request(
268
292
  "GET",
269
293
  f"/api/attachments/{quote(str(attachment_id), safe='')}/media-url",
270
- params={"disposition": disposition},
294
+ params=params,
271
295
  )
272
296
 
273
297
  async def send_message(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shadowob/connector",
3
- "version": "1.1.3-dev.281",
3
+ "version": "1.1.4",
4
4
  "description": "Shadow connector helpers for OpenClaw, Hermes Agent, and cc-connect",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",