@shadowob/connector
Advanced tools
| // src/buddy-collaboration-guidance.ts | ||
| var BUDDY_COLLABORATION_SYSTEM_PROMPT = [ | ||
| "Shadow Buddy collaboration rules:", | ||
| "- Treat a Buddy collaboration as a bounded IM conversation, not an open-ended work session.", | ||
| "- Speak only when you add a distinct useful point. If another Buddy already covered it, stay brief or stay silent.", | ||
| "- If you only agree, use a structured Shadow reaction action when available; otherwise stay silent instead of posting acknowledgement text.", | ||
| "- Later collaboration turns may be routed into a Shadow thread by the platform. Do not announce thread routing yourself.", | ||
| "- Match the density of the triggering message. Short chat gets a short reply; long analysis requires an explicit user pull.", | ||
| "- For Shadow Inbox task status changes, use the mounted shadowob CLI. The CLI consumes server-delivered task policy after status updates.", | ||
| "- Do not run tools, create memories, create skills, write files, promote tasks, or run demos unless a human explicitly asks for current action.", | ||
| "- Keep runtime logs, tool progress, memory updates, skill views, and self-improvement reviews private. Do not post them as chat messages.", | ||
| "- If the user says to stop, stay quiet, not implement, or just discuss, stop the action chain immediately." | ||
| ].join("\n"); | ||
| // src/cc-connect-fork.ts | ||
| var CC_CONNECT_FORK_REPO = "buggyblues/cc-connect"; | ||
| var CC_CONNECT_FORK_REF = "8289423fa21e744e5fe625cba57b2c6c3c5c17ea"; | ||
| var CC_CONNECT_FORK_SHORT_REF = CC_CONNECT_FORK_REF.slice(0, 7); | ||
| var CC_CONNECT_FORK_DOCS_URL = `https://github.com/${CC_CONNECT_FORK_REPO}/blob/main/docs/shadowob.md`; | ||
| // src/model-provider.ts | ||
| function normalizeConnectorModelProvider(provider) { | ||
| const baseUrl = provider?.baseUrl?.trim(); | ||
| const apiKey = provider?.apiKey?.trim(); | ||
| const openAIBaseUrl = provider?.openAIBaseUrl?.trim() || baseUrl; | ||
| const openAIApiKey = provider?.openAIApiKey?.trim() || apiKey; | ||
| const anthropicBaseUrl = provider?.anthropicBaseUrl?.trim(); | ||
| const anthropicApiKey = provider?.anthropicApiKey?.trim(); | ||
| const model = provider?.model?.trim(); | ||
| if ((!openAIBaseUrl || !openAIApiKey) && (!anthropicBaseUrl || !anthropicApiKey)) return null; | ||
| if (!model) return null; | ||
| return { | ||
| id: provider?.id?.trim() || "shadow-official", | ||
| label: provider?.label?.trim() || "Shadow official LLM proxy", | ||
| baseUrl: openAIBaseUrl || anthropicBaseUrl, | ||
| apiKey: openAIApiKey || anthropicApiKey, | ||
| ...openAIBaseUrl ? { openAIBaseUrl } : {}, | ||
| ...openAIApiKey ? { openAIApiKey } : {}, | ||
| ...anthropicBaseUrl ? { anthropicBaseUrl } : {}, | ||
| ...anthropicApiKey ? { anthropicApiKey } : {}, | ||
| model | ||
| }; | ||
| } | ||
| function connectorModelProviderEndpoint(provider, style) { | ||
| if (!provider) return null; | ||
| const baseUrl = style === "anthropic" ? provider.anthropicBaseUrl?.trim() || provider.baseUrl?.trim() : provider.openAIBaseUrl?.trim() || provider.baseUrl?.trim(); | ||
| const apiKey = style === "anthropic" ? provider.anthropicApiKey?.trim() || provider.apiKey?.trim() : provider.openAIApiKey?.trim() || provider.apiKey?.trim(); | ||
| return baseUrl && apiKey ? { baseUrl, apiKey } : null; | ||
| } | ||
| function ccConnectModelRef(agentType, providerId, model) { | ||
| if (agentType !== "opencode") return model; | ||
| return model.startsWith(`${providerId}/`) ? model : `${providerId}/${model}`; | ||
| } | ||
| // src/runtime-catalog.ts | ||
| var HERMES_INSTALL_SCRIPT = "curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- --skip-setup --non-interactive --skip-browser"; | ||
| var HERMES_LINUX_INSTALL_SCRIPT = [ | ||
| `sh -c 'set -e; if ! command -v xz >/dev/null 2>&1; then if [ "$(id -u)" -eq 0 ]; then SUDO=""; elif command -v sudo >/dev/null 2>&1; then SUDO="sudo"; else echo "Hermes Agent installer requires xz; install xz-utils/xz and retry." >&2; exit 1; fi; if command -v apt-get >/dev/null 2>&1; then $SUDO apt-get update && $SUDO apt-get install -y xz-utils; elif command -v apk >/dev/null 2>&1; then $SUDO apk add --no-cache xz; elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y xz; elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y xz; elif command -v pacman >/dev/null 2>&1; then $SUDO pacman -Sy --noconfirm xz; else echo "Hermes Agent installer requires xz; install xz-utils/xz and retry." >&2; exit 1; fi; fi; curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- --skip-setup --non-interactive --skip-browser'` | ||
| ]; | ||
| var CONNECTOR_RUNTIME_CATALOG = [ | ||
| { | ||
| id: "openclaw", | ||
| label: "OpenClaw", | ||
| kind: "openclaw", | ||
| command: "openclaw", | ||
| iconId: "openclaw", | ||
| install: { | ||
| // Docs: https://docs.openclaw.ai/install/index | ||
| commands: { | ||
| darwin: ["curl -fsSL https://openclaw.ai/install.sh | bash -s -- --no-onboard"], | ||
| linux: ["curl -fsSL https://openclaw.ai/install.sh | bash -s -- --no-onboard"], | ||
| win32: [ | ||
| 'powershell -NoProfile -ExecutionPolicy Bypass -Command "& ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -NoOnboard"' | ||
| ], | ||
| default: ["curl -fsSL https://openclaw.ai/install.sh | bash -s -- --no-onboard"] | ||
| }, | ||
| helpUrl: "https://docs.openclaw.ai/install/index" | ||
| } | ||
| }, | ||
| { | ||
| id: "hermes", | ||
| label: "Hermes Agent", | ||
| kind: "cli", | ||
| command: "hermes", | ||
| iconId: "hermes", | ||
| install: { | ||
| // Docs: https://hermes-agent.nousresearch.com/docs/getting-started/installation/ | ||
| // Native Windows details: https://hermes-agent.nousresearch.com/docs/user-guide/windows-native | ||
| commands: { | ||
| darwin: [HERMES_INSTALL_SCRIPT], | ||
| linux: HERMES_LINUX_INSTALL_SCRIPT, | ||
| win32: [ | ||
| 'powershell -NoProfile -ExecutionPolicy Bypass -Command "iex (irm https://hermes-agent.nousresearch.com/install.ps1)"' | ||
| ], | ||
| default: ["pipx install hermes-agent"] | ||
| }, | ||
| helpUrl: "https://hermes-agent.nousresearch.com/docs/getting-started/installation" | ||
| } | ||
| }, | ||
| { | ||
| id: "claude-code", | ||
| label: "Claude Code", | ||
| kind: "cli", | ||
| command: "claude", | ||
| iconId: "claude-code", | ||
| install: { | ||
| // Docs: https://code.claude.com/docs/en/setup | ||
| commands: { | ||
| win32: [ | ||
| 'powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://claude.ai/install.ps1 | iex"' | ||
| ], | ||
| darwin: ["curl -fsSL https://claude.ai/install.sh | bash"], | ||
| linux: ["curl -fsSL https://claude.ai/install.sh | bash"], | ||
| default: ["npm install -g @anthropic-ai/claude-code"] | ||
| }, | ||
| helpUrl: "https://code.claude.com/docs/en/installation" | ||
| } | ||
| }, | ||
| { | ||
| id: "codex", | ||
| label: "Codex CLI", | ||
| kind: "cli", | ||
| command: "codex", | ||
| iconId: "codex", | ||
| install: { | ||
| // Docs: https://developers.openai.com/codex/cli | ||
| commands: { | ||
| default: ["npm install -g @openai/codex"] | ||
| }, | ||
| helpUrl: "https://help.openai.com/en/articles/11096431-openai-codex-cli-getting-started" | ||
| } | ||
| }, | ||
| { | ||
| id: "opencode", | ||
| label: "OpenCode", | ||
| kind: "cli", | ||
| command: "opencode", | ||
| iconId: "opencode", | ||
| install: { | ||
| // Docs: https://opencode.ai/docs/ | ||
| commands: { | ||
| darwin: ["curl -fsSL https://opencode.ai/install | bash"], | ||
| linux: ["curl -fsSL https://opencode.ai/install | bash"], | ||
| win32: ["npm install -g opencode-ai"], | ||
| default: ["npm install -g opencode-ai"] | ||
| }, | ||
| helpUrl: "https://opencli.co/cli/opencode" | ||
| } | ||
| }, | ||
| { | ||
| id: "cursor", | ||
| label: "Cursor CLI", | ||
| kind: "cli", | ||
| command: "cursor-agent", | ||
| commands: ["cursor-agent", "cursor"], | ||
| iconId: "cursor", | ||
| install: { | ||
| // Docs: https://docs.cursor.com/en/cli/installation | ||
| // Cursor documents Windows through WSL; the connector creates a Windows .cmd wrapper after install. | ||
| commands: { | ||
| default: ["curl https://cursor.com/install -fsS | bash"], | ||
| win32: [ | ||
| `powershell -NoProfile -ExecutionPolicy Bypass -Command "wsl.exe bash -lc 'curl https://cursor.com/install -fsS | bash'"` | ||
| ] | ||
| }, | ||
| helpUrl: "https://docs.cursor.com/en/cli/installation" | ||
| } | ||
| }, | ||
| { | ||
| id: "kimi", | ||
| label: "Kimi Code", | ||
| kind: "cli", | ||
| command: "kimi", | ||
| iconId: "kimi", | ||
| install: { | ||
| // Docs: https://www.kimi.com/code/docs/en/kimi-code-cli/getting-started.html | ||
| commands: { | ||
| darwin: ["curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash"], | ||
| linux: ["curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash"], | ||
| win32: [ | ||
| 'powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://code.kimi.com/kimi-code/install.ps1 | iex"' | ||
| ] | ||
| }, | ||
| helpUrl: "https://www.kimi.com/code/docs/en/" | ||
| } | ||
| }, | ||
| { | ||
| id: "copilot", | ||
| label: "GitHub Copilot CLI", | ||
| kind: "cli", | ||
| command: "copilot", | ||
| iconId: "copilot", | ||
| install: { | ||
| // Docs: https://docs.github.com/copilot/how-tos/copilot-cli/install-copilot-cli | ||
| commands: { | ||
| darwin: ["brew install copilot-cli", "curl -fsSL https://gh.io/copilot-install | bash"], | ||
| linux: ["brew install copilot-cli", "curl -fsSL https://gh.io/copilot-install | bash"], | ||
| win32: ["winget install GitHub.Copilot", "npm install -g @github/copilot"] | ||
| }, | ||
| helpUrl: "https://docs.github.com/en/copilot/how-tos/copilot-cli/install-copilot-cli" | ||
| } | ||
| }, | ||
| { | ||
| id: "antigravity", | ||
| label: "Antigravity CLI", | ||
| kind: "cli", | ||
| command: "agy", | ||
| commands: ["agy", "antigravity"], | ||
| iconId: "antigravity", | ||
| install: { | ||
| // Docs: https://antigravity.google/download | ||
| commands: { | ||
| darwin: ["curl -fsSL https://antigravity.google/cli/install.sh | bash"], | ||
| linux: ["curl -fsSL https://antigravity.google/cli/install.sh | bash"], | ||
| win32: [ | ||
| 'powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://antigravity.google/cli/install.ps1 | iex"' | ||
| ] | ||
| }, | ||
| helpUrl: "https://www.antigravity.google/product/antigravity-cli" | ||
| } | ||
| } | ||
| ]; | ||
| function connectorRuntimeCatalog() { | ||
| return CONNECTOR_RUNTIME_CATALOG.map((entry) => ({ ...entry })); | ||
| } | ||
| function connectorRuntimeById(runtimeId) { | ||
| if (!runtimeId) return null; | ||
| return CONNECTOR_RUNTIME_CATALOG.find((entry) => entry.id === runtimeId) ?? null; | ||
| } | ||
| function currentRuntimePlatform() { | ||
| return typeof process !== "undefined" && process.platform ? process.platform : "default"; | ||
| } | ||
| function connectorRuntimeInstallCommands(runtimeId, targetPlatform = currentRuntimePlatform()) { | ||
| const runtime = connectorRuntimeById(runtimeId); | ||
| if (!runtime) return []; | ||
| const commands = runtime.install.commands; | ||
| return commands?.[targetPlatform] ?? commands?.default ?? []; | ||
| } | ||
| function connectorRuntimeInstallCommand(runtimeId, targetPlatform = currentRuntimePlatform()) { | ||
| return connectorRuntimeInstallCommands(runtimeId, targetPlatform)[0] ?? null; | ||
| } | ||
| // src/index.ts | ||
| var DEFAULT_SERVER_URL = "https://shadowob.com"; | ||
| var DEFAULT_WORK_DIR = "."; | ||
| var DEFAULT_PROJECT_NAME = "shadow-buddy"; | ||
| var DEFAULT_CC_AGENT = "codex"; | ||
| var shellQuote = (value) => { | ||
| if (!value) return "''"; | ||
| return `'${value.replace(/'/g, `'\\''`)}'`; | ||
| }; | ||
| var normalizeServerUrl = (value) => { | ||
| const trimmed = value.trim() || DEFAULT_SERVER_URL; | ||
| return trimmed.endsWith("/api") ? trimmed.slice(0, -4) : trimmed.replace(/\/$/, ""); | ||
| }; | ||
| var tokenOrPlaceholder = (token) => token.trim() || "<BUDDY_TOKEN>"; | ||
| function tomlMultilineString(value) { | ||
| return `"""${value.replace(/"""/g, '\\"\\"\\"')}"""`; | ||
| } | ||
| function modelProviderEnvLines(provider) { | ||
| if (!provider) return []; | ||
| const openAI = connectorModelProviderEndpoint(provider, "openai"); | ||
| const anthropic = connectorModelProviderEndpoint(provider, "anthropic"); | ||
| return [ | ||
| ...openAI ? [ | ||
| `OPENAI_COMPATIBLE_BASE_URL=${shellQuote(openAI.baseUrl)}`, | ||
| `OPENAI_COMPATIBLE_API_KEY=${shellQuote(openAI.apiKey)}`, | ||
| `OPENAI_COMPATIBLE_MODEL_ID=${shellQuote(provider.model)}` | ||
| ] : [], | ||
| ...anthropic ? [ | ||
| `ANTHROPIC_COMPATIBLE_BASE_URL=${shellQuote(anthropic.baseUrl)}`, | ||
| `ANTHROPIC_COMPATIBLE_API_KEY=${shellQuote(anthropic.apiKey)}`, | ||
| `ANTHROPIC_COMPATIBLE_MODEL_ID=${shellQuote(provider.model)}` | ||
| ] : [], | ||
| `SHADOWOB_MODEL_PROVIDER_ID=${shellQuote(provider.id ?? "shadow-official")}` | ||
| ]; | ||
| } | ||
| function modelProviderCapabilities(provider, capabilities) { | ||
| return provider ? [...capabilities, "officialModelProvider"] : capabilities; | ||
| } | ||
| function buildOpenClawPlan(input) { | ||
| const token = tokenOrPlaceholder(input.token); | ||
| const serverUrl = normalizeServerUrl(input.serverUrl); | ||
| const modelProvider = normalizeConnectorModelProvider(input.modelProvider); | ||
| const openAIProvider = connectorModelProviderEndpoint(modelProvider, "openai"); | ||
| const jsonConfig = JSON.stringify( | ||
| { | ||
| channels: { | ||
| shadowob: { | ||
| token, | ||
| serverUrl | ||
| } | ||
| }, | ||
| ...modelProvider && openAIProvider ? { | ||
| models: { | ||
| mode: "merge", | ||
| pricing: { enabled: false }, | ||
| providers: { | ||
| [modelProvider.id ?? "shadow-official"]: { | ||
| api: "openai-completions", | ||
| apiKey: "${env:OPENAI_COMPATIBLE_API_KEY}", | ||
| baseUrl: openAIProvider.baseUrl, | ||
| request: { allowPrivateNetwork: true }, | ||
| models: [{ id: modelProvider.model, name: modelProvider.model }] | ||
| } | ||
| } | ||
| } | ||
| } : {} | ||
| }, | ||
| null, | ||
| 2 | ||
| ); | ||
| const commands = [ | ||
| { | ||
| label: "Install plugin", | ||
| command: "openclaw plugins install @shadowob/openclaw-shadowob" | ||
| }, | ||
| { | ||
| label: "Set Buddy token", | ||
| command: `openclaw config set channels.shadowob.token ${shellQuote(token)}` | ||
| }, | ||
| { | ||
| label: "Set Shadow server URL", | ||
| command: `openclaw config set channels.shadowob.serverUrl ${shellQuote(serverUrl)}` | ||
| }, | ||
| { | ||
| label: "Restart gateway", | ||
| command: "openclaw gateway restart" | ||
| } | ||
| ]; | ||
| const quickCommand = commands.map((item) => item.command).join(" && "); | ||
| const connectCommand = [ | ||
| "npx @shadowob/connector@latest connect", | ||
| "--target openclaw", | ||
| `--server-url ${shellQuote(serverUrl)}`, | ||
| `--token ${shellQuote(token)}` | ||
| ].join(" "); | ||
| return { | ||
| target: "openclaw", | ||
| title: "OpenClaw", | ||
| summary: "Install the Shadow channel plugin, Shadow CLI bin/skills, and a Buddy CLI profile for OpenClaw.", | ||
| connectCommand, | ||
| quickCommand, | ||
| commands, | ||
| configBlocks: [{ label: "~/.openclaw/openclaw.json", language: "json", content: jsonConfig }], | ||
| aiPrompt: [ | ||
| "Configure this Shadow Buddy in OpenClaw.", | ||
| "", | ||
| `Shadow server URL: ${serverUrl}`, | ||
| `Buddy token: ${token}`, | ||
| "", | ||
| `Preferred one-line setup: ${connectCommand}`, | ||
| "The connector installs/configures the Shadow CLI, official Shadow skill files, and the Buddy profile before applying the OpenClaw channel config.", | ||
| modelProvider ? `It also configures ${modelProvider.label ?? "Shadow official LLM proxy"} as an OpenAI-compatible model provider (${modelProvider.model}).` : "", | ||
| "", | ||
| "Run these steps in order:", | ||
| ...commands.map((item, index) => `${index + 1}. ${item.command}`), | ||
| "", | ||
| "Confirm each step and then verify the gateway is running." | ||
| ].join("\n"), | ||
| docsUrl: "/product/index.html", | ||
| capabilities: modelProviderCapabilities(modelProvider, [ | ||
| "channelMessages", | ||
| "dms", | ||
| "threads", | ||
| "mentions", | ||
| "attachments", | ||
| "images", | ||
| "interactive", | ||
| "slashCommands", | ||
| "onlineStatus", | ||
| "typing", | ||
| "activityStatus", | ||
| "reactions", | ||
| "editDelete", | ||
| "statusChecks", | ||
| "usageCosts", | ||
| "multiAgentBinding", | ||
| "shadowCliLogin", | ||
| "notifications", | ||
| "officialSkills", | ||
| "cronTasks" | ||
| ]) | ||
| }; | ||
| } | ||
| function buildHermesPlan(input) { | ||
| const token = tokenOrPlaceholder(input.token); | ||
| const serverUrl = normalizeServerUrl(input.serverUrl); | ||
| const modelProvider = normalizeConnectorModelProvider(input.modelProvider); | ||
| const openAIProvider = connectorModelProviderEndpoint(modelProvider, "openai"); | ||
| const envBlock = [ | ||
| `SHADOWOB_SERVER_URL=${shellQuote(serverUrl)}`, | ||
| `SHADOWOB_TOKEN=${shellQuote(token)}`, | ||
| "SHADOWOB_ALLOW_ALL_USERS=true", | ||
| "SHADOWOB_HEARTBEAT_INTERVAL_SECONDS=30", | ||
| ...modelProviderEnvLines(modelProvider) | ||
| ].join("\n"); | ||
| const yamlConfig = [ | ||
| "plugins:", | ||
| " enabled:", | ||
| " - shadowob", | ||
| "", | ||
| "platforms:", | ||
| " shadowob:", | ||
| " enabled: true", | ||
| ` token: "${token}"`, | ||
| " extra:", | ||
| ` base_url: "${serverUrl}"`, | ||
| " mention_only: false", | ||
| " rest_only: false", | ||
| " catchup_minutes: 0", | ||
| " download_media: true", | ||
| " slash_commands: []", | ||
| ...modelProvider && openAIProvider ? [ | ||
| "", | ||
| "model:", | ||
| ` default: "${modelProvider.model}"`, | ||
| ` provider: "${modelProvider.id ?? "shadow-official"}"`, | ||
| "", | ||
| "custom_providers:", | ||
| ` - name: "${modelProvider.id ?? "shadow-official"}"`, | ||
| ` base_url: "${openAIProvider.baseUrl}"`, | ||
| " key_env: OPENAI_COMPATIBLE_API_KEY", | ||
| ` model: "${modelProvider.model}"` | ||
| ] : [] | ||
| ].join("\n"); | ||
| const commands = [ | ||
| { | ||
| label: "Copy plugin directory", | ||
| command: "mkdir -p ~/.hermes/plugins && cp -R ./packages/connector/hermes-shadowob-plugin ~/.hermes/plugins/shadowob" | ||
| }, | ||
| { | ||
| label: "Install plugin dependencies", | ||
| command: "python -m pip install -r ~/.hermes/plugins/shadowob/requirements.txt" | ||
| }, | ||
| { | ||
| label: "Enable plugin", | ||
| command: "hermes plugins enable shadowob" | ||
| }, | ||
| { | ||
| label: "Start gateway", | ||
| command: "hermes gateway" | ||
| } | ||
| ]; | ||
| const connectCommand = [ | ||
| "npx @shadowob/connector@latest connect", | ||
| "--target hermes", | ||
| `--server-url ${shellQuote(serverUrl)}`, | ||
| `--token ${shellQuote(token)}` | ||
| ].join(" "); | ||
| return { | ||
| target: "hermes", | ||
| title: "Hermes Agent", | ||
| summary: "Install the ShadowOB Hermes platform plugin, Shadow CLI bin/skills, and a Buddy CLI profile.", | ||
| connectCommand, | ||
| quickCommand: commands.map((item) => item.command).join(" && "), | ||
| commands, | ||
| configBlocks: [ | ||
| { label: "~/.hermes/.env", language: "bash", content: envBlock }, | ||
| { label: "~/.hermes/config.yaml", language: "yaml", content: yamlConfig } | ||
| ], | ||
| aiPrompt: [ | ||
| "Configure this Shadow Buddy in Hermes Agent.", | ||
| "", | ||
| `Shadow server URL: ${serverUrl}`, | ||
| `Buddy token: ${token}`, | ||
| "", | ||
| `Preferred one-line setup: ${connectCommand}`, | ||
| "The connector installs/configures the Shadow CLI, official Shadow skill files, and the Buddy profile before writing Hermes config. The plugin resolves the Buddy agent id and channel policy from Shadow at runtime.", | ||
| modelProvider && openAIProvider ? `It also configures Hermes custom model endpoint ${openAIProvider.baseUrl} with model ${modelProvider.model}.` : "" | ||
| ].join("\n"), | ||
| docsUrl: "https://hermes-agent.nousresearch.com/docs/user-guide/messaging", | ||
| capabilities: modelProviderCapabilities(modelProvider, [ | ||
| "channelMessages", | ||
| "dms", | ||
| "threads", | ||
| "attachments", | ||
| "images", | ||
| "interactive", | ||
| "slashCommands", | ||
| "onlineStatus", | ||
| "typing", | ||
| "activityStatus", | ||
| "cronDelivery", | ||
| "statusChecks", | ||
| "usageCosts", | ||
| "shadowCliLogin", | ||
| "notifications", | ||
| "officialSkills" | ||
| ]) | ||
| }; | ||
| } | ||
| function buildCcConnectPlan(input) { | ||
| const token = tokenOrPlaceholder(input.token); | ||
| const serverUrl = normalizeServerUrl(input.serverUrl); | ||
| const projectName = input.projectName?.trim() || DEFAULT_PROJECT_NAME; | ||
| const workDir = input.workDir?.trim() || DEFAULT_WORK_DIR; | ||
| const agentType = input.agentType?.trim() || DEFAULT_CC_AGENT; | ||
| const modelProvider = normalizeConnectorModelProvider(input.modelProvider); | ||
| const providerEndpoint = connectorModelProviderEndpoint( | ||
| modelProvider, | ||
| agentType === "claudecode" ? "anthropic" : "openai" | ||
| ); | ||
| const providerId = modelProvider?.id ?? "shadow-official"; | ||
| const providerModel = modelProvider ? ccConnectModelRef(agentType, providerId, modelProvider.model) : null; | ||
| const tomlConfig = [ | ||
| 'language = "zh"', | ||
| "", | ||
| "[[projects]]", | ||
| `name = "${projectName}"`, | ||
| "", | ||
| "[projects.agent]", | ||
| `type = "${agentType}"`, | ||
| "", | ||
| "[projects.agent.options]", | ||
| `work_dir = "${workDir}"`, | ||
| `system_prompt = ${tomlMultilineString(BUDDY_COLLABORATION_SYSTEM_PROMPT)}`, | ||
| ...modelProvider && providerEndpoint ? [ | ||
| `provider = "${providerId}"`, | ||
| `model = "${providerModel}"`, | ||
| "", | ||
| "[[projects.agent.providers]]", | ||
| `name = "${providerId}"`, | ||
| `api_key = "${providerEndpoint.apiKey}"`, | ||
| `base_url = "${providerEndpoint.baseUrl}"`, | ||
| `model = "${providerModel}"`, | ||
| "", | ||
| "[[projects.agent.providers.models]]", | ||
| `model = "${providerModel}"` | ||
| ] : [], | ||
| "", | ||
| "[projects.display]", | ||
| 'mode = "quiet"', | ||
| "thinking_messages = false", | ||
| "tool_messages = false", | ||
| "", | ||
| "[[projects.platforms]]", | ||
| 'type = "shadowob"', | ||
| "", | ||
| "[projects.platforms.options]", | ||
| `token = "${token}"`, | ||
| `server_url = "${serverUrl}"`, | ||
| 'allow_from = "*"', | ||
| "listen_dms = true", | ||
| "share_session_in_channel = false", | ||
| 'progress_style = "compact"' | ||
| ].join("\n"); | ||
| const connectCommand = [ | ||
| "npx @shadowob/connector@latest connect", | ||
| "--target cc-connect", | ||
| `--server-url ${shellQuote(serverUrl)}`, | ||
| `--token ${shellQuote(token)}`, | ||
| `--work-dir ${shellQuote(workDir)}`, | ||
| `--project-name ${shellQuote(projectName)}`, | ||
| `--agent-type ${shellQuote(agentType)}` | ||
| ].join(" "); | ||
| const installCommand = `${connectCommand} --install`; | ||
| const startCommand = `${connectCommand} --install --start`; | ||
| const commands = [ | ||
| { | ||
| label: "Install ShadowOB cc-connect fork", | ||
| command: installCommand | ||
| }, | ||
| { label: "Create config directory", command: "mkdir -p ~/.cc-connect" }, | ||
| { | ||
| label: "Edit config", | ||
| command: "$EDITOR ~/.cc-connect/config.toml" | ||
| }, | ||
| { label: "Start ShadowOB cc-connect fork", command: startCommand } | ||
| ]; | ||
| return { | ||
| target: "cc-connect", | ||
| title: "cc-connect", | ||
| summary: `Use ${CC_CONNECT_FORK_REPO}@${CC_CONNECT_FORK_SHORT_REF} with ShadowOB Socket.IO support, Shadow CLI bin/skills, and a Buddy CLI profile.`, | ||
| connectCommand: startCommand, | ||
| quickCommand: startCommand, | ||
| commands, | ||
| configBlocks: [{ label: "~/.cc-connect/config.toml", language: "toml", content: tomlConfig }], | ||
| aiPrompt: [ | ||
| "Configure this Shadow Buddy in cc-connect.", | ||
| "", | ||
| `Shadow server URL: ${serverUrl}`, | ||
| `Buddy token: ${token}`, | ||
| `Project work_dir: ${workDir}`, | ||
| `Agent type: ${agentType}`, | ||
| "", | ||
| `Preferred one-line setup: ${startCommand}`, | ||
| `Install ${CC_CONNECT_FORK_REPO}@${CC_CONNECT_FORK_SHORT_REF}, install/configure the Shadow CLI and official Shadow skill files, add the TOML platform block, and start cc-connect.`, | ||
| "Inbox task status hooks are handled by the installed Shadow CLI skill and server-delivered cliPolicy; cc-connect does not implement hook execution in its runtime prompt.", | ||
| "The generated config injects Shadow Buddy collaboration rules into the agent system prompt and sets cc-connect display mode to quiet so internal tool/progress events do not spill into Shadow channels.", | ||
| modelProvider ? `Configure ${modelProvider.label ?? "Shadow official LLM proxy"} as provider ${modelProvider.id ?? "shadow-official"} for ${agentType}.` : "" | ||
| ].join("\n"), | ||
| docsUrl: CC_CONNECT_FORK_DOCS_URL, | ||
| capabilities: modelProviderCapabilities(modelProvider, [ | ||
| "channelMessages", | ||
| "dms", | ||
| "attachments", | ||
| "images", | ||
| "interactive", | ||
| "slashCommands", | ||
| "typing", | ||
| "streamingPreviews", | ||
| "forms", | ||
| "statusChecks", | ||
| "usageCosts", | ||
| "multiAgentBinding", | ||
| "shadowCliLogin", | ||
| "notifications" | ||
| ]) | ||
| }; | ||
| } | ||
| function createConnectorPlan(input) { | ||
| if (input.target === "openclaw") return buildOpenClawPlan(input); | ||
| if (input.target === "hermes") return buildHermesPlan(input); | ||
| if (input.target === "cc-connect") return buildCcConnectPlan(input); | ||
| throw new Error(`Unsupported connector target: ${String(input.target)}`); | ||
| } | ||
| function createConnectorPlans(input) { | ||
| return ["openclaw", "hermes", "cc-connect"].map( | ||
| (target) => createConnectorPlan({ ...input, target }) | ||
| ); | ||
| } | ||
| export { | ||
| CONNECTOR_RUNTIME_CATALOG, | ||
| connectorRuntimeCatalog, | ||
| connectorRuntimeById, | ||
| connectorRuntimeInstallCommands, | ||
| connectorRuntimeInstallCommand, | ||
| createConnectorPlan, | ||
| createConnectorPlans | ||
| }; |
+5
-5
@@ -307,3 +307,3 @@ "use strict"; | ||
| ] : [], | ||
| `SHADOW_MODEL_PROVIDER_ID=${shellQuote(provider.id ?? "shadow-official")}` | ||
| `SHADOWOB_MODEL_PROVIDER_ID=${shellQuote(provider.id ?? "shadow-official")}` | ||
| ]; | ||
@@ -425,6 +425,6 @@ } | ||
| const envBlock = [ | ||
| `SHADOW_BASE_URL=${shellQuote(serverUrl)}`, | ||
| `SHADOW_TOKEN=${shellQuote(token)}`, | ||
| "SHADOW_ALLOW_ALL_USERS=true", | ||
| "SHADOW_HEARTBEAT_INTERVAL_SECONDS=30", | ||
| `SHADOWOB_SERVER_URL=${shellQuote(serverUrl)}`, | ||
| `SHADOWOB_TOKEN=${shellQuote(token)}`, | ||
| "SHADOWOB_ALLOW_ALL_USERS=true", | ||
| "SHADOWOB_HEARTBEAT_INTERVAL_SECONDS=30", | ||
| ...modelProviderEnvLines(modelProvider) | ||
@@ -431,0 +431,0 @@ ].join("\n"); |
+1
-1
@@ -9,3 +9,3 @@ import { | ||
| createConnectorPlans | ||
| } from "./chunk-DQGW3WRU.js"; | ||
| } from "./chunk-XCXCPSCG.js"; | ||
| export { | ||
@@ -12,0 +12,0 @@ CONNECTOR_RUNTIME_CATALOG, |
+5
-5
@@ -307,3 +307,3 @@ "use strict"; | ||
| ] : [], | ||
| `SHADOW_MODEL_PROVIDER_ID=${shellQuote(provider.id ?? "shadow-official")}` | ||
| `SHADOWOB_MODEL_PROVIDER_ID=${shellQuote(provider.id ?? "shadow-official")}` | ||
| ]; | ||
@@ -425,6 +425,6 @@ } | ||
| const envBlock = [ | ||
| `SHADOW_BASE_URL=${shellQuote(serverUrl)}`, | ||
| `SHADOW_TOKEN=${shellQuote(token)}`, | ||
| "SHADOW_ALLOW_ALL_USERS=true", | ||
| "SHADOW_HEARTBEAT_INTERVAL_SECONDS=30", | ||
| `SHADOWOB_SERVER_URL=${shellQuote(serverUrl)}`, | ||
| `SHADOWOB_TOKEN=${shellQuote(token)}`, | ||
| "SHADOWOB_ALLOW_ALL_USERS=true", | ||
| "SHADOWOB_HEARTBEAT_INTERVAL_SECONDS=30", | ||
| ...modelProviderEnvLines(modelProvider) | ||
@@ -431,0 +431,0 @@ ].join("\n"); |
+1
-1
@@ -9,3 +9,3 @@ import { | ||
| createConnectorPlans | ||
| } from "./chunk-DQGW3WRU.js"; | ||
| } from "./chunk-XCXCPSCG.js"; | ||
| export { | ||
@@ -12,0 +12,0 @@ CONNECTOR_RUNTIME_CATALOG, |
@@ -46,3 +46,3 @@ "use strict"; | ||
| var import_node_path = require("path"); | ||
| var CONNECTOR_MANAGED_NODE_VERSION = process.env.SHADOW_CONNECTOR_NODE_VERSION?.trim() || "22.16.0"; | ||
| var CONNECTOR_MANAGED_NODE_VERSION = process.env.SHADOWOB_CONNECTOR_NODE_VERSION?.trim() || "22.16.0"; | ||
| var PATH_KEY = process.platform === "win32" ? "Path" : "PATH"; | ||
@@ -56,3 +56,3 @@ var loginShellPath; | ||
| function connectorHome() { | ||
| const override = process.env.SHADOW_CONNECTOR_HOME?.trim(); | ||
| const override = process.env.SHADOWOB_CONNECTOR_HOME?.trim(); | ||
| return override ? expandHome(override) : (0, import_node_path.resolve)((0, import_node_os.homedir)(), ".shadowob/connector"); | ||
@@ -87,3 +87,3 @@ } | ||
| function readLoginShellPath() { | ||
| if (process.env.SHADOW_CONNECTOR_SKIP_LOGIN_SHELL === "1") return []; | ||
| if (process.env.SHADOWOB_CONNECTOR_SKIP_LOGIN_SHELL === "1") return []; | ||
| if (loginShellPath !== void 0) return splitPath(loginShellPath ?? ""); | ||
@@ -179,3 +179,3 @@ const shells = dedupePaths( | ||
| ...env, | ||
| SHADOW_CONNECTOR_HOME: connectorHome(), | ||
| SHADOWOB_CONNECTOR_HOME: connectorHome(), | ||
| NPM_CONFIG_PREFIX: nodeGlobalRoot(), | ||
@@ -455,3 +455,3 @@ npm_config_prefix: nodeGlobalRoot() | ||
| function opencodeUrl(options) { | ||
| const value = options.opencodeUrl ?? options.env?.SHADOW_CONNECTOR_OPENCODE_URL ?? options.env?.OPENCODE_SERVER_URL ?? DEFAULT_OPENCODE_URL; | ||
| const value = options.opencodeUrl ?? options.env?.SHADOWOB_CONNECTOR_OPENCODE_URL ?? options.env?.OPENCODE_SERVER_URL ?? DEFAULT_OPENCODE_URL; | ||
| return value.replace(/\/+$/, ""); | ||
@@ -458,0 +458,0 @@ } |
@@ -31,3 +31,3 @@ // src/runtime-sessions.ts | ||
| import { dirname, resolve, sep } from "path"; | ||
| var CONNECTOR_MANAGED_NODE_VERSION = process.env.SHADOW_CONNECTOR_NODE_VERSION?.trim() || "22.16.0"; | ||
| var CONNECTOR_MANAGED_NODE_VERSION = process.env.SHADOWOB_CONNECTOR_NODE_VERSION?.trim() || "22.16.0"; | ||
| var PATH_KEY = process.platform === "win32" ? "Path" : "PATH"; | ||
@@ -41,3 +41,3 @@ var loginShellPath; | ||
| function connectorHome() { | ||
| const override = process.env.SHADOW_CONNECTOR_HOME?.trim(); | ||
| const override = process.env.SHADOWOB_CONNECTOR_HOME?.trim(); | ||
| return override ? expandHome(override) : resolve(homedir(), ".shadowob/connector"); | ||
@@ -72,3 +72,3 @@ } | ||
| function readLoginShellPath() { | ||
| if (process.env.SHADOW_CONNECTOR_SKIP_LOGIN_SHELL === "1") return []; | ||
| if (process.env.SHADOWOB_CONNECTOR_SKIP_LOGIN_SHELL === "1") return []; | ||
| if (loginShellPath !== void 0) return splitPath(loginShellPath ?? ""); | ||
@@ -164,3 +164,3 @@ const shells = dedupePaths( | ||
| ...env, | ||
| SHADOW_CONNECTOR_HOME: connectorHome(), | ||
| SHADOWOB_CONNECTOR_HOME: connectorHome(), | ||
| NPM_CONFIG_PREFIX: nodeGlobalRoot(), | ||
@@ -440,3 +440,3 @@ npm_config_prefix: nodeGlobalRoot() | ||
| function opencodeUrl(options) { | ||
| const value = options.opencodeUrl ?? options.env?.SHADOW_CONNECTOR_OPENCODE_URL ?? options.env?.OPENCODE_SERVER_URL ?? DEFAULT_OPENCODE_URL; | ||
| const value = options.opencodeUrl ?? options.env?.SHADOWOB_CONNECTOR_OPENCODE_URL ?? options.env?.OPENCODE_SERVER_URL ?? DEFAULT_OPENCODE_URL; | ||
| return value.replace(/\/+$/, ""); | ||
@@ -443,0 +443,0 @@ } |
@@ -8,7 +8,7 @@ name: shadowob | ||
| requires_env: | ||
| - name: SHADOW_BASE_URL | ||
| - name: SHADOWOB_SERVER_URL | ||
| description: "Shadow server base URL, for example https://shadowob.example.com" | ||
| prompt: "Shadow base URL" | ||
| password: false | ||
| - name: SHADOW_TOKEN | ||
| - name: SHADOWOB_TOKEN | ||
| description: "Shadow bot/user access token" | ||
@@ -18,69 +18,69 @@ prompt: "Shadow access token" | ||
| optional_env: | ||
| - name: SHADOW_CHANNEL_IDS | ||
| - name: SHADOWOB_CHANNEL_IDS | ||
| description: "Advanced override: comma-separated channel IDs to join. Normally omitted because Shadow policy is discovered dynamically." | ||
| prompt: "Shadow channel IDs" | ||
| password: false | ||
| - name: SHADOW_HOME_CHANNEL | ||
| - name: SHADOWOB_HOME_CHANNEL | ||
| description: "Default Shadow channel ID used by cron/send_message delivery." | ||
| prompt: "Shadow home channel" | ||
| password: false | ||
| - name: SHADOW_AGENT_ID | ||
| - name: SHADOWOB_AGENT_ID | ||
| description: "Advanced override: Shadow Buddy agent ID. Normally resolved from /api/auth/me." | ||
| prompt: "Shadow Buddy agent ID" | ||
| password: false | ||
| - name: SHADOW_HEARTBEAT_INTERVAL_SECONDS | ||
| - name: SHADOWOB_HEARTBEAT_INTERVAL_SECONDS | ||
| description: "Agent heartbeat interval in seconds. Defaults to 30." | ||
| prompt: "Heartbeat interval" | ||
| password: false | ||
| - name: SHADOW_SLASH_COMMANDS_JSON | ||
| - name: SHADOWOB_SLASH_COMMANDS_JSON | ||
| description: "JSON array of slash command definitions to register on startup." | ||
| prompt: "Slash commands JSON" | ||
| password: false | ||
| - name: SHADOW_SERVER_IDS | ||
| - name: SHADOWOB_SERVER_IDS | ||
| description: "Advanced override: comma-separated server IDs/slugs to discover manually." | ||
| prompt: "Shadow server IDs" | ||
| password: false | ||
| - name: SHADOW_AUTO_DISCOVER_CHANNELS | ||
| - name: SHADOWOB_AUTO_DISCOVER_CHANNELS | ||
| description: "Advanced override: true/false. Discover accessible server channels without the Buddy remote policy." | ||
| prompt: "Auto-discover channels?" | ||
| password: false | ||
| - name: SHADOW_ALLOWED_USERS | ||
| - name: SHADOWOB_ALLOWED_USERS | ||
| description: "Comma-separated allowed Shadow user IDs/usernames for Hermes gateway authorization." | ||
| prompt: "Allowed Shadow users" | ||
| password: false | ||
| - name: SHADOW_ALLOW_ALL_USERS | ||
| - name: SHADOWOB_ALLOW_ALL_USERS | ||
| description: "true/false. Allow all Shadow users." | ||
| prompt: "Allow all Shadow users?" | ||
| password: false | ||
| - name: SHADOW_BOT_USER_ID | ||
| - name: SHADOWOB_BUDDY_USER_ID | ||
| description: "Optional bot user ID. If omitted, adapter calls /api/auth/me." | ||
| prompt: "Shadow bot user ID" | ||
| password: false | ||
| - name: SHADOW_BOT_USERNAME | ||
| - name: SHADOWOB_BUDDY_USERNAME | ||
| description: "Optional bot username for mention-only filtering." | ||
| prompt: "Shadow bot username" | ||
| password: false | ||
| - name: SHADOW_MENTION_ONLY | ||
| - name: SHADOWOB_MENTION_ONLY | ||
| description: "true/false. In group channels, only process messages that mention the bot." | ||
| prompt: "Mention-only mode?" | ||
| password: false | ||
| - name: SHADOW_REPLY_TO_BOTS | ||
| - name: SHADOWOB_REPLY_TO_BUDDIES | ||
| description: "true/false. Process messages authored by other bot users. Defaults false." | ||
| prompt: "Reply to bot users?" | ||
| password: false | ||
| - name: SHADOW_REST_ONLY | ||
| - name: SHADOWOB_REST_ONLY | ||
| description: "true/false. Use REST polling instead of Socket.IO." | ||
| prompt: "REST-only mode?" | ||
| password: false | ||
| - name: SHADOW_POLL_INTERVAL_SECONDS | ||
| - name: SHADOWOB_POLL_INTERVAL_SECONDS | ||
| description: "REST polling interval in seconds. Defaults to 3." | ||
| prompt: "Poll interval" | ||
| password: false | ||
| - name: SHADOW_CATCHUP_MINUTES | ||
| - name: SHADOWOB_CATCHUP_MINUTES | ||
| description: "On startup, process recent messages newer than this window. Defaults to 0." | ||
| prompt: "Catch-up minutes" | ||
| password: false | ||
| - name: SHADOW_DOWNLOAD_MEDIA | ||
| - name: SHADOWOB_DOWNLOAD_MEDIA | ||
| description: "true/false. Download inbound attachments into Hermes cache. Defaults true." | ||
| prompt: "Download media?" | ||
| password: false |
@@ -20,5 +20,5 @@ # Hermes Shadow/OpenClaw Buddy Platform Plugin | ||
| - Dynamic channel and policy discovery through `/api/agents/:id/config` | ||
| - Optional slash command registration and slash-command prompt handling through `SHADOW_SLASH_COMMANDS_JSON` | ||
| - Optional slash command registration and slash-command prompt handling through `SHADOWOB_SLASH_COMMANDS_JSON` | ||
| - Interactive component sends via Shadow message metadata and interactive response forwarding to Hermes | ||
| - Cron/send_message standalone delivery through `SHADOW_HOME_CHANNEL` | ||
| - Cron/send_message standalone delivery through `SHADOWOB_HOME_CHANNEL` | ||
@@ -66,4 +66,4 @@ It deliberately does not implement the whole Shadow product surface. Workspace, commerce, wallet, cloud sandbox, marketplace, OAuth and dashboard stats should be added as Hermes tools or an MCP server, not as platform-adapter logic. | ||
| ```bash | ||
| export SHADOW_BASE_URL="https://your-shadow.example.com" | ||
| export SHADOW_TOKEN="shadow_access_token" | ||
| export SHADOWOB_SERVER_URL="https://your-shadow.example.com" | ||
| export SHADOWOB_TOKEN="shadow_access_token" | ||
| ``` | ||
@@ -74,6 +74,6 @@ | ||
| ```bash | ||
| export SHADOW_HEARTBEAT_INTERVAL_SECONDS=30 | ||
| export SHADOWOB_HEARTBEAT_INTERVAL_SECONDS=30 | ||
| # Optional slash commands registered at startup | ||
| export SHADOW_SLASH_COMMANDS_JSON='[{"name":"audit","description":"Run an audit"}]' | ||
| export SHADOWOB_SLASH_COMMANDS_JSON='[{"name":"audit","description":"Run an audit"}]' | ||
| ``` | ||
@@ -84,19 +84,19 @@ | ||
| ```bash | ||
| export SHADOW_ALLOWED_USERS="user_id_or_username_1,user_id_or_username_2" | ||
| export SHADOW_ALLOW_ALL_USERS=false | ||
| export SHADOW_BOT_USER_ID="bot_user_id" # optional; otherwise /api/auth/me is called | ||
| export SHADOW_BOT_USERNAME="bot_username" # optional; used by mention-only filter | ||
| export SHADOW_MENTION_ONLY=false # group/channel messages require @bot when true | ||
| export SHADOW_REPLY_TO_BOTS=false # loop guard | ||
| export SHADOW_REST_ONLY=false # true disables Socket.IO and uses polling | ||
| export SHADOW_POLL_INTERVAL_SECONDS=3 | ||
| export SHADOW_CATCHUP_MINUTES=0 # set >0 to process recent messages on startup | ||
| export SHADOW_DOWNLOAD_MEDIA=true | ||
| export SHADOWOB_ALLOWED_USERS="user_id_or_username_1,user_id_or_username_2" | ||
| export SHADOWOB_ALLOW_ALL_USERS=false | ||
| export SHADOWOB_BUDDY_USER_ID="bot_user_id" # optional; otherwise /api/auth/me is called | ||
| export SHADOWOB_BUDDY_USERNAME="bot_username" # optional; used by mention-only filter | ||
| export SHADOWOB_MENTION_ONLY=false # group/channel messages require @bot when true | ||
| export SHADOWOB_REPLY_TO_BUDDIES=false # loop guard | ||
| export SHADOWOB_REST_ONLY=false # true disables Socket.IO and uses polling | ||
| export SHADOWOB_POLL_INTERVAL_SECONDS=3 | ||
| export SHADOWOB_CATCHUP_MINUTES=0 # set >0 to process recent messages on startup | ||
| export SHADOWOB_DOWNLOAD_MEDIA=true | ||
| # Advanced compatibility overrides; normally leave these unset so Shadow policy drives routing. | ||
| export SHADOW_CHANNEL_IDS="channel_id_1,channel_id_2" | ||
| export SHADOW_HOME_CHANNEL="channel_id_1" | ||
| export SHADOW_AGENT_ID="agent_id_1" | ||
| export SHADOW_SERVER_IDS="server_id_or_slug_1,server_id_or_slug_2" | ||
| export SHADOW_AUTO_DISCOVER_CHANNELS=true | ||
| export SHADOWOB_CHANNEL_IDS="channel_id_1,channel_id_2" | ||
| export SHADOWOB_HOME_CHANNEL="channel_id_1" | ||
| export SHADOWOB_AGENT_ID="agent_id_1" | ||
| export SHADOWOB_SERVER_IDS="server_id_or_slug_1,server_id_or_slug_2" | ||
| export SHADOWOB_AUTO_DISCOVER_CHANNELS=true | ||
| ``` | ||
@@ -114,5 +114,5 @@ | ||
| enabled: true | ||
| token: "${SHADOW_TOKEN}" | ||
| token: "${SHADOWOB_TOKEN}" | ||
| extra: | ||
| base_url: "${SHADOW_BASE_URL}" | ||
| base_url: "${SHADOWOB_SERVER_URL}" | ||
| slash_commands: | ||
@@ -119,0 +119,0 @@ - name: "audit" |
@@ -571,3 +571,3 @@ """Minimal async Shadow SDK used by the Hermes Shadow platform plugin. | ||
| "python-socketio is required for Shadow realtime mode. " | ||
| "Install requirements.txt or set SHADOW_REST_ONLY=true." | ||
| "Install requirements.txt or set SHADOWOB_REST_ONLY=true." | ||
| ) from exc | ||
@@ -574,0 +574,0 @@ |
+2
-2
| { | ||
| "name": "@shadowob/connector", | ||
| "version": "1.1.60", | ||
| "version": "1.1.61", | ||
| "description": "Shadow connector helpers for OpenClaw, Hermes Agent, and cc-connect", | ||
@@ -75,3 +75,3 @@ "type": "module", | ||
| "yaml": "^2.8.4", | ||
| "@shadowob/shared": "1.1.60" | ||
| "@shadowob/shared": "1.1.61" | ||
| }, | ||
@@ -78,0 +78,0 @@ "scripts": { |
+5
-5
@@ -5,3 +5,3 @@ # Shadow Connector | ||
| The package exports pure plan builders for legacy app UIs and a `shadowob-connector` CLI for terminal setup. | ||
| The package exports pure plan builders for app UIs and a `shadowob-connector` CLI for terminal setup. | ||
@@ -169,6 +169,6 @@ ## CLI | ||
| ```bash | ||
| export SHADOW_BASE_URL="https://shadowob.com" | ||
| export SHADOW_TOKEN="buddy-token" | ||
| export SHADOW_HEARTBEAT_INTERVAL_SECONDS=30 | ||
| export SHADOW_SLASH_COMMANDS_JSON='[]' | ||
| export SHADOWOB_SERVER_URL="https://shadowob.com" | ||
| export SHADOWOB_TOKEN="buddy-token" | ||
| export SHADOWOB_HEARTBEAT_INTERVAL_SECONDS=30 | ||
| export SHADOWOB_SLASH_COMMANDS_JSON='[]' | ||
| ``` | ||
@@ -175,0 +175,0 @@ |
| import type { ShadowServerAppTokenIntrospection } from './types.js' | ||
| export function shadowServerUrl() { | ||
| return (process.env.SHADOW_SERVER_URL ?? 'http://localhost:3002').replace(/\/$/, '') | ||
| return (process.env.SHADOWOB_SERVER_URL ?? 'http://localhost:3002').replace(/\/$/, '') | ||
| } | ||
@@ -6,0 +6,0 @@ |
@@ -6,5 +6,5 @@ import manifestJson from '../shadow-app.local.json' with { type: 'json' } | ||
| const publicBaseUrl = ( | ||
| process.env.SHADOW_APP_PUBLIC_BASE_URL ?? `http://localhost:${port}` | ||
| process.env.SHADOWOB_APP_PUBLIC_BASE_URL ?? `http://localhost:${port}` | ||
| ).replace(/\/$/, '') | ||
| const apiBaseUrl = (process.env.SHADOW_APP_API_BASE_URL ?? publicBaseUrl).replace(/\/$/, '') | ||
| const apiBaseUrl = (process.env.SHADOWOB_APP_API_BASE_URL ?? publicBaseUrl).replace(/\/$/, '') | ||
| return { | ||
@@ -11,0 +11,0 @@ ...manifestJson, |
@@ -22,6 +22,6 @@ # Runtime, Publish, And Backup | ||
| Target declarative publish contract. Before using it, verify that the runtime CLI exposes this command with `shadowob cloud app --help`; if it is absent, do not invent an external tunnel or publish path. | ||
| Target declarative publish contract. Before using it, verify that the runtime CLI exposes this command with `shadowob app --help`; if it is absent, do not invent an external tunnel or publish path. | ||
| ```bash | ||
| shadowob cloud app publish \ | ||
| shadowob app publish \ | ||
| --port "<port>" \ | ||
@@ -34,3 +34,3 @@ --manifest-file ./shadow-app.local.json \ | ||
| Create or keep the App source under `$SHADOW_WORKSPACE`, `/workspace`, `/state`, `/tmp`, or the | ||
| Create or keep the App source under `$SHADOWOB_WORKSPACE`, `/workspace`, `/state`, `/tmp`, or the | ||
| standard Cloud runner home `/home/shadow`. `--source-path` and `--state-paths` must be absolute | ||
@@ -40,6 +40,6 @@ runtime paths under one of those roots. | ||
| Cloud runtimes auto-detect the current deployment and agent from | ||
| `SHADOW_CLOUD_DEPLOYMENT_ID` and `AGENT_ID`/`SHADOW_CLOUD_AGENT_ID`. Inside an Inbox task or | ||
| `SHADOWOB_CLOUD_DEPLOYMENT_ID` and `SHADOWOB_AGENT_ID`. Inside an Inbox task or | ||
| current channel, the CLI infers the target server from the task/channel context. Pass `--deployment`, | ||
| `--agent`, or `--server` explicitly only when working outside the managed runtime context. Do not | ||
| treat `SHADOW_SERVER_IDS` as a publish target; it is only a list of servers the runtime may observe. | ||
| treat `SHADOWOB_SERVER_IDS` as a publish target; it is only a list of servers the runtime may observe. | ||
@@ -53,3 +53,3 @@ Before publishing, keep the App service running without blocking the task shell: | ||
| When a scaffold does not provide `start:background`, use an equivalent `nohup ... &` command and write `.shadow-app.pid`. Do not run foreground server commands such as `pnpm start` or `node src/server.js` as the final tool call. A blocked shell prevents `shadowob cloud app publish`, backup creation, Inbox completion, and update tasks from running. | ||
| When a scaffold does not provide `start:background`, use an equivalent `nohup ... &` command and write `.shadow-app.pid`. Do not run foreground server commands such as `pnpm start` or `node src/server.js` as the final tool call. A blocked shell prevents `shadowob app publish`, backup creation, Inbox completion, and update tasks from running. | ||
@@ -59,3 +59,3 @@ For dynamic expose from inside a runtime, write desired state to: | ||
| ```text | ||
| $SHADOW_EXPOSURE_CONFIG | ||
| $SHADOWOB_EXPOSURE_CONFIG | ||
| ``` | ||
@@ -89,3 +89,3 @@ | ||
| Removing an entry from `desired.json` closes that dynamic exposure after sidecar reconcile. Dynamic expose only creates a controlled route; installing into a server must still use `shadowob cloud app publish` or the explicit App install/defaults/grant commands. | ||
| Removing an entry from `desired.json` closes that dynamic exposure after sidecar reconcile. Dynamic expose only creates a controlled route; installing into a server must still use `shadowob app publish` or the explicit App install/defaults/grant commands. | ||
@@ -95,3 +95,3 @@ Useful lifecycle commands: | ||
| ```bash | ||
| shadowob cloud app expose \ | ||
| shadowob app expose \ | ||
| --id "<app-key>" \ | ||
@@ -105,6 +105,6 @@ --port "<port>" \ | ||
| shadowob cloud app status --app-key "<app-key>" --json | ||
| shadowob cloud app backup --app-key "<app-key>" --json | ||
| shadowob cloud app restore --app-key "<app-key>" --backup "<backup-set-id>" --json | ||
| shadowob cloud app unpublish --app-key "<app-key>" --json | ||
| shadowob app status --app-key "<app-key>" --json | ||
| shadowob app backup --app-key "<app-key>" --json | ||
| shadowob app restore --app-key "<app-key>" --backup "<backup-set-id>" --json | ||
| shadowob app unpublish --app-key "<app-key>" --json | ||
| ``` | ||
@@ -176,2 +176,2 @@ | ||
| - Do not run ad hoc tunnel clients, allocate public domains, or expose arbitrary private URLs from inside the container. | ||
| - If `shadowob cloud app publish` returns 401/403 or any other error, mark the Inbox task failed with the exact blocker. Do not report the App as published or completed until the publish command returns success. | ||
| - If `shadowob app publish` returns 401/403 or any other error, mark the Inbox task failed with the exact blocker. Do not report the App as published or completed until the publish command returns success. |
@@ -54,3 +54,3 @@ # Scaffold | ||
| 6. Run local preview through `shadowob app preview --manifest-file`. | ||
| 7. For Cloud runtime publish, keep the project under `$SHADOW_WORKSPACE`, `/workspace`, or `/home/shadow`, then run `PORT=<port> pnpm start:background` and verify `/health` with `curl`. | ||
| 7. For Cloud runtime publish, keep the project under `$SHADOWOB_WORKSPACE`, `/workspace`, or `/home/shadow`, then run `PORT=<port> pnpm start:background` and verify `/health` with `curl`. | ||
| 8. Publish only after state paths and backup policy are declared. | ||
@@ -57,0 +57,0 @@ |
@@ -22,3 +22,3 @@ --- | ||
| - Use `shadow.app/1` manifests, stable `appKey` values, stable command names, and explicit `permission`, `action`, and `dataClass` on every command. | ||
| - Buddies must operate installed Apps through `shadowob app discover`, `shadowob app skills`, and `shadowob app call`; never hand a Buddy raw HTTP routes, app tokens, or legacy shared secrets. | ||
| - Buddies must operate installed Apps through `shadowob app discover`, `shadowob app skills`, and `shadowob app call`; never hand a Buddy raw HTTP routes, app tokens, or shared secrets. | ||
| - For local development, install with `--manifest-file`; production manifests, iframe URLs, API URLs, icon URLs, and OAuth redirect URIs must be stable HTTPS origins. | ||
@@ -25,0 +25,0 @@ - Never publish public `http://<ip>:<port>` origins in a manifest. If a proxy forwards to a private host, keep the private address out of the public manifest. |
@@ -347,21 +347,2 @@ --- | ||
| ```bash | ||
| # Legacy workspace apps | ||
| shadowob apps list <server-id> --json | ||
| # Get app | ||
| shadowob apps get <app-id> --json | ||
| # Create/Update/Delete | ||
| shadowob apps create <server-id> --name <name> --type <url|workspace|static> [--source-url <url>] [--description <desc>] [--settings <json>] --json | ||
| shadowob apps update <app-id> [--name <name>] [--description <desc>] [--source-url <url>] [--settings <json>] --json | ||
| shadowob apps delete <app-id> | ||
| # Publish from workspace | ||
| shadowob apps publish <server-id> --folder-id <id> [--name <name>] [--description <desc>] --json | ||
| # Download source | ||
| shadowob apps download <app-id> [--output <path>] | ||
| ``` | ||
| ## Notifications | ||
@@ -368,0 +349,0 @@ |
| // src/buddy-collaboration-guidance.ts | ||
| var BUDDY_COLLABORATION_SYSTEM_PROMPT = [ | ||
| "Shadow Buddy collaboration rules:", | ||
| "- Treat a Buddy collaboration as a bounded IM conversation, not an open-ended work session.", | ||
| "- Speak only when you add a distinct useful point. If another Buddy already covered it, stay brief or stay silent.", | ||
| "- If you only agree, use a structured Shadow reaction action when available; otherwise stay silent instead of posting acknowledgement text.", | ||
| "- Later collaboration turns may be routed into a Shadow thread by the platform. Do not announce thread routing yourself.", | ||
| "- Match the density of the triggering message. Short chat gets a short reply; long analysis requires an explicit user pull.", | ||
| "- For Shadow Inbox task status changes, use the mounted shadowob CLI. The CLI consumes server-delivered task policy after status updates.", | ||
| "- Do not run tools, create memories, create skills, write files, promote tasks, or run demos unless a human explicitly asks for current action.", | ||
| "- Keep runtime logs, tool progress, memory updates, skill views, and self-improvement reviews private. Do not post them as chat messages.", | ||
| "- If the user says to stop, stay quiet, not implement, or just discuss, stop the action chain immediately." | ||
| ].join("\n"); | ||
| // src/cc-connect-fork.ts | ||
| var CC_CONNECT_FORK_REPO = "buggyblues/cc-connect"; | ||
| var CC_CONNECT_FORK_REF = "8289423fa21e744e5fe625cba57b2c6c3c5c17ea"; | ||
| var CC_CONNECT_FORK_SHORT_REF = CC_CONNECT_FORK_REF.slice(0, 7); | ||
| var CC_CONNECT_FORK_DOCS_URL = `https://github.com/${CC_CONNECT_FORK_REPO}/blob/main/docs/shadowob.md`; | ||
| // src/model-provider.ts | ||
| function normalizeConnectorModelProvider(provider) { | ||
| const baseUrl = provider?.baseUrl?.trim(); | ||
| const apiKey = provider?.apiKey?.trim(); | ||
| const openAIBaseUrl = provider?.openAIBaseUrl?.trim() || baseUrl; | ||
| const openAIApiKey = provider?.openAIApiKey?.trim() || apiKey; | ||
| const anthropicBaseUrl = provider?.anthropicBaseUrl?.trim(); | ||
| const anthropicApiKey = provider?.anthropicApiKey?.trim(); | ||
| const model = provider?.model?.trim(); | ||
| if ((!openAIBaseUrl || !openAIApiKey) && (!anthropicBaseUrl || !anthropicApiKey)) return null; | ||
| if (!model) return null; | ||
| return { | ||
| id: provider?.id?.trim() || "shadow-official", | ||
| label: provider?.label?.trim() || "Shadow official LLM proxy", | ||
| baseUrl: openAIBaseUrl || anthropicBaseUrl, | ||
| apiKey: openAIApiKey || anthropicApiKey, | ||
| ...openAIBaseUrl ? { openAIBaseUrl } : {}, | ||
| ...openAIApiKey ? { openAIApiKey } : {}, | ||
| ...anthropicBaseUrl ? { anthropicBaseUrl } : {}, | ||
| ...anthropicApiKey ? { anthropicApiKey } : {}, | ||
| model | ||
| }; | ||
| } | ||
| function connectorModelProviderEndpoint(provider, style) { | ||
| if (!provider) return null; | ||
| const baseUrl = style === "anthropic" ? provider.anthropicBaseUrl?.trim() || provider.baseUrl?.trim() : provider.openAIBaseUrl?.trim() || provider.baseUrl?.trim(); | ||
| const apiKey = style === "anthropic" ? provider.anthropicApiKey?.trim() || provider.apiKey?.trim() : provider.openAIApiKey?.trim() || provider.apiKey?.trim(); | ||
| return baseUrl && apiKey ? { baseUrl, apiKey } : null; | ||
| } | ||
| function ccConnectModelRef(agentType, providerId, model) { | ||
| if (agentType !== "opencode") return model; | ||
| return model.startsWith(`${providerId}/`) ? model : `${providerId}/${model}`; | ||
| } | ||
| // src/runtime-catalog.ts | ||
| var HERMES_INSTALL_SCRIPT = "curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- --skip-setup --non-interactive --skip-browser"; | ||
| var HERMES_LINUX_INSTALL_SCRIPT = [ | ||
| `sh -c 'set -e; if ! command -v xz >/dev/null 2>&1; then if [ "$(id -u)" -eq 0 ]; then SUDO=""; elif command -v sudo >/dev/null 2>&1; then SUDO="sudo"; else echo "Hermes Agent installer requires xz; install xz-utils/xz and retry." >&2; exit 1; fi; if command -v apt-get >/dev/null 2>&1; then $SUDO apt-get update && $SUDO apt-get install -y xz-utils; elif command -v apk >/dev/null 2>&1; then $SUDO apk add --no-cache xz; elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y xz; elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y xz; elif command -v pacman >/dev/null 2>&1; then $SUDO pacman -Sy --noconfirm xz; else echo "Hermes Agent installer requires xz; install xz-utils/xz and retry." >&2; exit 1; fi; fi; curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- --skip-setup --non-interactive --skip-browser'` | ||
| ]; | ||
| var CONNECTOR_RUNTIME_CATALOG = [ | ||
| { | ||
| id: "openclaw", | ||
| label: "OpenClaw", | ||
| kind: "openclaw", | ||
| command: "openclaw", | ||
| iconId: "openclaw", | ||
| install: { | ||
| // Docs: https://docs.openclaw.ai/install/index | ||
| commands: { | ||
| darwin: ["curl -fsSL https://openclaw.ai/install.sh | bash -s -- --no-onboard"], | ||
| linux: ["curl -fsSL https://openclaw.ai/install.sh | bash -s -- --no-onboard"], | ||
| win32: [ | ||
| 'powershell -NoProfile -ExecutionPolicy Bypass -Command "& ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -NoOnboard"' | ||
| ], | ||
| default: ["curl -fsSL https://openclaw.ai/install.sh | bash -s -- --no-onboard"] | ||
| }, | ||
| helpUrl: "https://docs.openclaw.ai/install/index" | ||
| } | ||
| }, | ||
| { | ||
| id: "hermes", | ||
| label: "Hermes Agent", | ||
| kind: "cli", | ||
| command: "hermes", | ||
| iconId: "hermes", | ||
| install: { | ||
| // Docs: https://hermes-agent.nousresearch.com/docs/getting-started/installation/ | ||
| // Native Windows details: https://hermes-agent.nousresearch.com/docs/user-guide/windows-native | ||
| commands: { | ||
| darwin: [HERMES_INSTALL_SCRIPT], | ||
| linux: HERMES_LINUX_INSTALL_SCRIPT, | ||
| win32: [ | ||
| 'powershell -NoProfile -ExecutionPolicy Bypass -Command "iex (irm https://hermes-agent.nousresearch.com/install.ps1)"' | ||
| ], | ||
| default: ["pipx install hermes-agent"] | ||
| }, | ||
| helpUrl: "https://hermes-agent.nousresearch.com/docs/getting-started/installation" | ||
| } | ||
| }, | ||
| { | ||
| id: "claude-code", | ||
| label: "Claude Code", | ||
| kind: "cli", | ||
| command: "claude", | ||
| iconId: "claude-code", | ||
| install: { | ||
| // Docs: https://code.claude.com/docs/en/setup | ||
| commands: { | ||
| win32: [ | ||
| 'powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://claude.ai/install.ps1 | iex"' | ||
| ], | ||
| darwin: ["curl -fsSL https://claude.ai/install.sh | bash"], | ||
| linux: ["curl -fsSL https://claude.ai/install.sh | bash"], | ||
| default: ["npm install -g @anthropic-ai/claude-code"] | ||
| }, | ||
| helpUrl: "https://code.claude.com/docs/en/installation" | ||
| } | ||
| }, | ||
| { | ||
| id: "codex", | ||
| label: "Codex CLI", | ||
| kind: "cli", | ||
| command: "codex", | ||
| iconId: "codex", | ||
| install: { | ||
| // Docs: https://developers.openai.com/codex/cli | ||
| commands: { | ||
| default: ["npm install -g @openai/codex"] | ||
| }, | ||
| helpUrl: "https://help.openai.com/en/articles/11096431-openai-codex-cli-getting-started" | ||
| } | ||
| }, | ||
| { | ||
| id: "opencode", | ||
| label: "OpenCode", | ||
| kind: "cli", | ||
| command: "opencode", | ||
| iconId: "opencode", | ||
| install: { | ||
| // Docs: https://opencode.ai/docs/ | ||
| commands: { | ||
| darwin: ["curl -fsSL https://opencode.ai/install | bash"], | ||
| linux: ["curl -fsSL https://opencode.ai/install | bash"], | ||
| win32: ["npm install -g opencode-ai"], | ||
| default: ["npm install -g opencode-ai"] | ||
| }, | ||
| helpUrl: "https://opencli.co/cli/opencode" | ||
| } | ||
| }, | ||
| { | ||
| id: "cursor", | ||
| label: "Cursor CLI", | ||
| kind: "cli", | ||
| command: "cursor-agent", | ||
| commands: ["cursor-agent", "cursor"], | ||
| iconId: "cursor", | ||
| install: { | ||
| // Docs: https://docs.cursor.com/en/cli/installation | ||
| // Cursor documents Windows through WSL; the connector creates a Windows .cmd wrapper after install. | ||
| commands: { | ||
| default: ["curl https://cursor.com/install -fsS | bash"], | ||
| win32: [ | ||
| `powershell -NoProfile -ExecutionPolicy Bypass -Command "wsl.exe bash -lc 'curl https://cursor.com/install -fsS | bash'"` | ||
| ] | ||
| }, | ||
| helpUrl: "https://docs.cursor.com/en/cli/installation" | ||
| } | ||
| }, | ||
| { | ||
| id: "kimi", | ||
| label: "Kimi Code", | ||
| kind: "cli", | ||
| command: "kimi", | ||
| iconId: "kimi", | ||
| install: { | ||
| // Docs: https://www.kimi.com/code/docs/en/kimi-code-cli/getting-started.html | ||
| commands: { | ||
| darwin: ["curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash"], | ||
| linux: ["curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash"], | ||
| win32: [ | ||
| 'powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://code.kimi.com/kimi-code/install.ps1 | iex"' | ||
| ] | ||
| }, | ||
| helpUrl: "https://www.kimi.com/code/docs/en/" | ||
| } | ||
| }, | ||
| { | ||
| id: "copilot", | ||
| label: "GitHub Copilot CLI", | ||
| kind: "cli", | ||
| command: "copilot", | ||
| iconId: "copilot", | ||
| install: { | ||
| // Docs: https://docs.github.com/copilot/how-tos/copilot-cli/install-copilot-cli | ||
| commands: { | ||
| darwin: ["brew install copilot-cli", "curl -fsSL https://gh.io/copilot-install | bash"], | ||
| linux: ["brew install copilot-cli", "curl -fsSL https://gh.io/copilot-install | bash"], | ||
| win32: ["winget install GitHub.Copilot", "npm install -g @github/copilot"] | ||
| }, | ||
| helpUrl: "https://docs.github.com/en/copilot/how-tos/copilot-cli/install-copilot-cli" | ||
| } | ||
| }, | ||
| { | ||
| id: "antigravity", | ||
| label: "Antigravity CLI", | ||
| kind: "cli", | ||
| command: "agy", | ||
| commands: ["agy", "antigravity"], | ||
| iconId: "antigravity", | ||
| install: { | ||
| // Docs: https://antigravity.google/download | ||
| commands: { | ||
| darwin: ["curl -fsSL https://antigravity.google/cli/install.sh | bash"], | ||
| linux: ["curl -fsSL https://antigravity.google/cli/install.sh | bash"], | ||
| win32: [ | ||
| 'powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://antigravity.google/cli/install.ps1 | iex"' | ||
| ] | ||
| }, | ||
| helpUrl: "https://www.antigravity.google/product/antigravity-cli" | ||
| } | ||
| } | ||
| ]; | ||
| function connectorRuntimeCatalog() { | ||
| return CONNECTOR_RUNTIME_CATALOG.map((entry) => ({ ...entry })); | ||
| } | ||
| function connectorRuntimeById(runtimeId) { | ||
| if (!runtimeId) return null; | ||
| return CONNECTOR_RUNTIME_CATALOG.find((entry) => entry.id === runtimeId) ?? null; | ||
| } | ||
| function currentRuntimePlatform() { | ||
| return typeof process !== "undefined" && process.platform ? process.platform : "default"; | ||
| } | ||
| function connectorRuntimeInstallCommands(runtimeId, targetPlatform = currentRuntimePlatform()) { | ||
| const runtime = connectorRuntimeById(runtimeId); | ||
| if (!runtime) return []; | ||
| const commands = runtime.install.commands; | ||
| return commands?.[targetPlatform] ?? commands?.default ?? []; | ||
| } | ||
| function connectorRuntimeInstallCommand(runtimeId, targetPlatform = currentRuntimePlatform()) { | ||
| return connectorRuntimeInstallCommands(runtimeId, targetPlatform)[0] ?? null; | ||
| } | ||
| // src/index.ts | ||
| var DEFAULT_SERVER_URL = "https://shadowob.com"; | ||
| var DEFAULT_WORK_DIR = "."; | ||
| var DEFAULT_PROJECT_NAME = "shadow-buddy"; | ||
| var DEFAULT_CC_AGENT = "codex"; | ||
| var shellQuote = (value) => { | ||
| if (!value) return "''"; | ||
| return `'${value.replace(/'/g, `'\\''`)}'`; | ||
| }; | ||
| var normalizeServerUrl = (value) => { | ||
| const trimmed = value.trim() || DEFAULT_SERVER_URL; | ||
| return trimmed.endsWith("/api") ? trimmed.slice(0, -4) : trimmed.replace(/\/$/, ""); | ||
| }; | ||
| var tokenOrPlaceholder = (token) => token.trim() || "<BUDDY_TOKEN>"; | ||
| function tomlMultilineString(value) { | ||
| return `"""${value.replace(/"""/g, '\\"\\"\\"')}"""`; | ||
| } | ||
| function modelProviderEnvLines(provider) { | ||
| if (!provider) return []; | ||
| const openAI = connectorModelProviderEndpoint(provider, "openai"); | ||
| const anthropic = connectorModelProviderEndpoint(provider, "anthropic"); | ||
| return [ | ||
| ...openAI ? [ | ||
| `OPENAI_COMPATIBLE_BASE_URL=${shellQuote(openAI.baseUrl)}`, | ||
| `OPENAI_COMPATIBLE_API_KEY=${shellQuote(openAI.apiKey)}`, | ||
| `OPENAI_COMPATIBLE_MODEL_ID=${shellQuote(provider.model)}` | ||
| ] : [], | ||
| ...anthropic ? [ | ||
| `ANTHROPIC_COMPATIBLE_BASE_URL=${shellQuote(anthropic.baseUrl)}`, | ||
| `ANTHROPIC_COMPATIBLE_API_KEY=${shellQuote(anthropic.apiKey)}`, | ||
| `ANTHROPIC_COMPATIBLE_MODEL_ID=${shellQuote(provider.model)}` | ||
| ] : [], | ||
| `SHADOW_MODEL_PROVIDER_ID=${shellQuote(provider.id ?? "shadow-official")}` | ||
| ]; | ||
| } | ||
| function modelProviderCapabilities(provider, capabilities) { | ||
| return provider ? [...capabilities, "officialModelProvider"] : capabilities; | ||
| } | ||
| function buildOpenClawPlan(input) { | ||
| const token = tokenOrPlaceholder(input.token); | ||
| const serverUrl = normalizeServerUrl(input.serverUrl); | ||
| const modelProvider = normalizeConnectorModelProvider(input.modelProvider); | ||
| const openAIProvider = connectorModelProviderEndpoint(modelProvider, "openai"); | ||
| const jsonConfig = JSON.stringify( | ||
| { | ||
| channels: { | ||
| shadowob: { | ||
| token, | ||
| serverUrl | ||
| } | ||
| }, | ||
| ...modelProvider && openAIProvider ? { | ||
| models: { | ||
| mode: "merge", | ||
| pricing: { enabled: false }, | ||
| providers: { | ||
| [modelProvider.id ?? "shadow-official"]: { | ||
| api: "openai-completions", | ||
| apiKey: "${env:OPENAI_COMPATIBLE_API_KEY}", | ||
| baseUrl: openAIProvider.baseUrl, | ||
| request: { allowPrivateNetwork: true }, | ||
| models: [{ id: modelProvider.model, name: modelProvider.model }] | ||
| } | ||
| } | ||
| } | ||
| } : {} | ||
| }, | ||
| null, | ||
| 2 | ||
| ); | ||
| const commands = [ | ||
| { | ||
| label: "Install plugin", | ||
| command: "openclaw plugins install @shadowob/openclaw-shadowob" | ||
| }, | ||
| { | ||
| label: "Set Buddy token", | ||
| command: `openclaw config set channels.shadowob.token ${shellQuote(token)}` | ||
| }, | ||
| { | ||
| label: "Set Shadow server URL", | ||
| command: `openclaw config set channels.shadowob.serverUrl ${shellQuote(serverUrl)}` | ||
| }, | ||
| { | ||
| label: "Restart gateway", | ||
| command: "openclaw gateway restart" | ||
| } | ||
| ]; | ||
| const quickCommand = commands.map((item) => item.command).join(" && "); | ||
| const connectCommand = [ | ||
| "npx @shadowob/connector@latest connect", | ||
| "--target openclaw", | ||
| `--server-url ${shellQuote(serverUrl)}`, | ||
| `--token ${shellQuote(token)}` | ||
| ].join(" "); | ||
| return { | ||
| target: "openclaw", | ||
| title: "OpenClaw", | ||
| summary: "Install the Shadow channel plugin, Shadow CLI bin/skills, and a Buddy CLI profile for OpenClaw.", | ||
| connectCommand, | ||
| quickCommand, | ||
| commands, | ||
| configBlocks: [{ label: "~/.openclaw/openclaw.json", language: "json", content: jsonConfig }], | ||
| aiPrompt: [ | ||
| "Configure this Shadow Buddy in OpenClaw.", | ||
| "", | ||
| `Shadow server URL: ${serverUrl}`, | ||
| `Buddy token: ${token}`, | ||
| "", | ||
| `Preferred one-line setup: ${connectCommand}`, | ||
| "The connector installs/configures the Shadow CLI, official Shadow skill files, and the Buddy profile before applying the OpenClaw channel config.", | ||
| modelProvider ? `It also configures ${modelProvider.label ?? "Shadow official LLM proxy"} as an OpenAI-compatible model provider (${modelProvider.model}).` : "", | ||
| "", | ||
| "Run these steps in order:", | ||
| ...commands.map((item, index) => `${index + 1}. ${item.command}`), | ||
| "", | ||
| "Confirm each step and then verify the gateway is running." | ||
| ].join("\n"), | ||
| docsUrl: "/product/index.html", | ||
| capabilities: modelProviderCapabilities(modelProvider, [ | ||
| "channelMessages", | ||
| "dms", | ||
| "threads", | ||
| "mentions", | ||
| "attachments", | ||
| "images", | ||
| "interactive", | ||
| "slashCommands", | ||
| "onlineStatus", | ||
| "typing", | ||
| "activityStatus", | ||
| "reactions", | ||
| "editDelete", | ||
| "statusChecks", | ||
| "usageCosts", | ||
| "multiAgentBinding", | ||
| "shadowCliLogin", | ||
| "notifications", | ||
| "officialSkills", | ||
| "cronTasks" | ||
| ]) | ||
| }; | ||
| } | ||
| function buildHermesPlan(input) { | ||
| const token = tokenOrPlaceholder(input.token); | ||
| const serverUrl = normalizeServerUrl(input.serverUrl); | ||
| const modelProvider = normalizeConnectorModelProvider(input.modelProvider); | ||
| const openAIProvider = connectorModelProviderEndpoint(modelProvider, "openai"); | ||
| const envBlock = [ | ||
| `SHADOW_BASE_URL=${shellQuote(serverUrl)}`, | ||
| `SHADOW_TOKEN=${shellQuote(token)}`, | ||
| "SHADOW_ALLOW_ALL_USERS=true", | ||
| "SHADOW_HEARTBEAT_INTERVAL_SECONDS=30", | ||
| ...modelProviderEnvLines(modelProvider) | ||
| ].join("\n"); | ||
| const yamlConfig = [ | ||
| "plugins:", | ||
| " enabled:", | ||
| " - shadowob", | ||
| "", | ||
| "platforms:", | ||
| " shadowob:", | ||
| " enabled: true", | ||
| ` token: "${token}"`, | ||
| " extra:", | ||
| ` base_url: "${serverUrl}"`, | ||
| " mention_only: false", | ||
| " rest_only: false", | ||
| " catchup_minutes: 0", | ||
| " download_media: true", | ||
| " slash_commands: []", | ||
| ...modelProvider && openAIProvider ? [ | ||
| "", | ||
| "model:", | ||
| ` default: "${modelProvider.model}"`, | ||
| ` provider: "${modelProvider.id ?? "shadow-official"}"`, | ||
| "", | ||
| "custom_providers:", | ||
| ` - name: "${modelProvider.id ?? "shadow-official"}"`, | ||
| ` base_url: "${openAIProvider.baseUrl}"`, | ||
| " key_env: OPENAI_COMPATIBLE_API_KEY", | ||
| ` model: "${modelProvider.model}"` | ||
| ] : [] | ||
| ].join("\n"); | ||
| const commands = [ | ||
| { | ||
| label: "Copy plugin directory", | ||
| command: "mkdir -p ~/.hermes/plugins && cp -R ./packages/connector/hermes-shadowob-plugin ~/.hermes/plugins/shadowob" | ||
| }, | ||
| { | ||
| label: "Install plugin dependencies", | ||
| command: "python -m pip install -r ~/.hermes/plugins/shadowob/requirements.txt" | ||
| }, | ||
| { | ||
| label: "Enable plugin", | ||
| command: "hermes plugins enable shadowob" | ||
| }, | ||
| { | ||
| label: "Start gateway", | ||
| command: "hermes gateway" | ||
| } | ||
| ]; | ||
| const connectCommand = [ | ||
| "npx @shadowob/connector@latest connect", | ||
| "--target hermes", | ||
| `--server-url ${shellQuote(serverUrl)}`, | ||
| `--token ${shellQuote(token)}` | ||
| ].join(" "); | ||
| return { | ||
| target: "hermes", | ||
| title: "Hermes Agent", | ||
| summary: "Install the ShadowOB Hermes platform plugin, Shadow CLI bin/skills, and a Buddy CLI profile.", | ||
| connectCommand, | ||
| quickCommand: commands.map((item) => item.command).join(" && "), | ||
| commands, | ||
| configBlocks: [ | ||
| { label: "~/.hermes/.env", language: "bash", content: envBlock }, | ||
| { label: "~/.hermes/config.yaml", language: "yaml", content: yamlConfig } | ||
| ], | ||
| aiPrompt: [ | ||
| "Configure this Shadow Buddy in Hermes Agent.", | ||
| "", | ||
| `Shadow server URL: ${serverUrl}`, | ||
| `Buddy token: ${token}`, | ||
| "", | ||
| `Preferred one-line setup: ${connectCommand}`, | ||
| "The connector installs/configures the Shadow CLI, official Shadow skill files, and the Buddy profile before writing Hermes config. The plugin resolves the Buddy agent id and channel policy from Shadow at runtime.", | ||
| modelProvider && openAIProvider ? `It also configures Hermes custom model endpoint ${openAIProvider.baseUrl} with model ${modelProvider.model}.` : "" | ||
| ].join("\n"), | ||
| docsUrl: "https://hermes-agent.nousresearch.com/docs/user-guide/messaging", | ||
| capabilities: modelProviderCapabilities(modelProvider, [ | ||
| "channelMessages", | ||
| "dms", | ||
| "threads", | ||
| "attachments", | ||
| "images", | ||
| "interactive", | ||
| "slashCommands", | ||
| "onlineStatus", | ||
| "typing", | ||
| "activityStatus", | ||
| "cronDelivery", | ||
| "statusChecks", | ||
| "usageCosts", | ||
| "shadowCliLogin", | ||
| "notifications", | ||
| "officialSkills" | ||
| ]) | ||
| }; | ||
| } | ||
| function buildCcConnectPlan(input) { | ||
| const token = tokenOrPlaceholder(input.token); | ||
| const serverUrl = normalizeServerUrl(input.serverUrl); | ||
| const projectName = input.projectName?.trim() || DEFAULT_PROJECT_NAME; | ||
| const workDir = input.workDir?.trim() || DEFAULT_WORK_DIR; | ||
| const agentType = input.agentType?.trim() || DEFAULT_CC_AGENT; | ||
| const modelProvider = normalizeConnectorModelProvider(input.modelProvider); | ||
| const providerEndpoint = connectorModelProviderEndpoint( | ||
| modelProvider, | ||
| agentType === "claudecode" ? "anthropic" : "openai" | ||
| ); | ||
| const providerId = modelProvider?.id ?? "shadow-official"; | ||
| const providerModel = modelProvider ? ccConnectModelRef(agentType, providerId, modelProvider.model) : null; | ||
| const tomlConfig = [ | ||
| 'language = "zh"', | ||
| "", | ||
| "[[projects]]", | ||
| `name = "${projectName}"`, | ||
| "", | ||
| "[projects.agent]", | ||
| `type = "${agentType}"`, | ||
| "", | ||
| "[projects.agent.options]", | ||
| `work_dir = "${workDir}"`, | ||
| `system_prompt = ${tomlMultilineString(BUDDY_COLLABORATION_SYSTEM_PROMPT)}`, | ||
| ...modelProvider && providerEndpoint ? [ | ||
| `provider = "${providerId}"`, | ||
| `model = "${providerModel}"`, | ||
| "", | ||
| "[[projects.agent.providers]]", | ||
| `name = "${providerId}"`, | ||
| `api_key = "${providerEndpoint.apiKey}"`, | ||
| `base_url = "${providerEndpoint.baseUrl}"`, | ||
| `model = "${providerModel}"`, | ||
| "", | ||
| "[[projects.agent.providers.models]]", | ||
| `model = "${providerModel}"` | ||
| ] : [], | ||
| "", | ||
| "[projects.display]", | ||
| 'mode = "quiet"', | ||
| "thinking_messages = false", | ||
| "tool_messages = false", | ||
| "", | ||
| "[[projects.platforms]]", | ||
| 'type = "shadowob"', | ||
| "", | ||
| "[projects.platforms.options]", | ||
| `token = "${token}"`, | ||
| `server_url = "${serverUrl}"`, | ||
| 'allow_from = "*"', | ||
| "listen_dms = true", | ||
| "share_session_in_channel = false", | ||
| 'progress_style = "compact"' | ||
| ].join("\n"); | ||
| const connectCommand = [ | ||
| "npx @shadowob/connector@latest connect", | ||
| "--target cc-connect", | ||
| `--server-url ${shellQuote(serverUrl)}`, | ||
| `--token ${shellQuote(token)}`, | ||
| `--work-dir ${shellQuote(workDir)}`, | ||
| `--project-name ${shellQuote(projectName)}`, | ||
| `--agent-type ${shellQuote(agentType)}` | ||
| ].join(" "); | ||
| const installCommand = `${connectCommand} --install`; | ||
| const startCommand = `${connectCommand} --install --start`; | ||
| const commands = [ | ||
| { | ||
| label: "Install ShadowOB cc-connect fork", | ||
| command: installCommand | ||
| }, | ||
| { label: "Create config directory", command: "mkdir -p ~/.cc-connect" }, | ||
| { | ||
| label: "Edit config", | ||
| command: "$EDITOR ~/.cc-connect/config.toml" | ||
| }, | ||
| { label: "Start ShadowOB cc-connect fork", command: startCommand } | ||
| ]; | ||
| return { | ||
| target: "cc-connect", | ||
| title: "cc-connect", | ||
| summary: `Use ${CC_CONNECT_FORK_REPO}@${CC_CONNECT_FORK_SHORT_REF} with ShadowOB Socket.IO support, Shadow CLI bin/skills, and a Buddy CLI profile.`, | ||
| connectCommand: startCommand, | ||
| quickCommand: startCommand, | ||
| commands, | ||
| configBlocks: [{ label: "~/.cc-connect/config.toml", language: "toml", content: tomlConfig }], | ||
| aiPrompt: [ | ||
| "Configure this Shadow Buddy in cc-connect.", | ||
| "", | ||
| `Shadow server URL: ${serverUrl}`, | ||
| `Buddy token: ${token}`, | ||
| `Project work_dir: ${workDir}`, | ||
| `Agent type: ${agentType}`, | ||
| "", | ||
| `Preferred one-line setup: ${startCommand}`, | ||
| `Install ${CC_CONNECT_FORK_REPO}@${CC_CONNECT_FORK_SHORT_REF}, install/configure the Shadow CLI and official Shadow skill files, add the TOML platform block, and start cc-connect.`, | ||
| "Inbox task status hooks are handled by the installed Shadow CLI skill and server-delivered cliPolicy; cc-connect does not implement hook execution in its runtime prompt.", | ||
| "The generated config injects Shadow Buddy collaboration rules into the agent system prompt and sets cc-connect display mode to quiet so internal tool/progress events do not spill into Shadow channels.", | ||
| modelProvider ? `Configure ${modelProvider.label ?? "Shadow official LLM proxy"} as provider ${modelProvider.id ?? "shadow-official"} for ${agentType}.` : "" | ||
| ].join("\n"), | ||
| docsUrl: CC_CONNECT_FORK_DOCS_URL, | ||
| capabilities: modelProviderCapabilities(modelProvider, [ | ||
| "channelMessages", | ||
| "dms", | ||
| "attachments", | ||
| "images", | ||
| "interactive", | ||
| "slashCommands", | ||
| "typing", | ||
| "streamingPreviews", | ||
| "forms", | ||
| "statusChecks", | ||
| "usageCosts", | ||
| "multiAgentBinding", | ||
| "shadowCliLogin", | ||
| "notifications" | ||
| ]) | ||
| }; | ||
| } | ||
| function createConnectorPlan(input) { | ||
| if (input.target === "openclaw") return buildOpenClawPlan(input); | ||
| if (input.target === "hermes") return buildHermesPlan(input); | ||
| if (input.target === "cc-connect") return buildCcConnectPlan(input); | ||
| throw new Error(`Unsupported connector target: ${String(input.target)}`); | ||
| } | ||
| function createConnectorPlans(input) { | ||
| return ["openclaw", "hermes", "cc-connect"].map( | ||
| (target) => createConnectorPlan({ ...input, target }) | ||
| ); | ||
| } | ||
| export { | ||
| CONNECTOR_RUNTIME_CATALOG, | ||
| connectorRuntimeCatalog, | ||
| connectorRuntimeById, | ||
| connectorRuntimeInstallCommands, | ||
| connectorRuntimeInstallCommand, | ||
| createConnectorPlan, | ||
| createConnectorPlans | ||
| }; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 12 instances
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 12 instances
1039449
-0.14%22782
-0.1%117
0.86%+ Added
- Removed
Updated