From e1dea36ea7a549bbf426c0c537fe531c6bef9f85 Mon Sep 17 00:00:00 2001 From: "lisheng.lisheng" Date: Fri, 19 Jun 2026 23:25:05 +0800 Subject: [PATCH 1/3] feat: add `bl agent setup` command for one-click agent configuration Add a new command that configures popular coding agents (Claude Code, Qwen Code, OpenCode, OpenClaw, Hermes, Codex) to use DashScope API with a single copy-paste command from the web console. Supports Linux, macOS, and Windows. Non-destructively merges into existing config files with automatic backup. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/cli/src/commands/agent/setup.ts | 87 +++++++++++++++++++ packages/cli/src/commands/agent/writers.ts | 20 +++++ .../src/commands/agent/writers/claude-code.ts | 36 ++++++++ .../cli/src/commands/agent/writers/codex.ts | 42 +++++++++ .../cli/src/commands/agent/writers/hermes.ts | 41 +++++++++ .../src/commands/agent/writers/openclaw.ts | 50 +++++++++++ .../src/commands/agent/writers/opencode.ts | 31 +++++++ .../src/commands/agent/writers/qwen-code.ts | 49 +++++++++++ .../cli/src/commands/agent/writers/utils.ts | 40 +++++++++ packages/cli/src/commands/catalog.ts | 2 + skills/bailian-cli/reference/agent.md | 57 ++++++++++++ skills/bailian-cli/reference/index.md | 2 + 12 files changed, 457 insertions(+) create mode 100644 packages/cli/src/commands/agent/setup.ts create mode 100644 packages/cli/src/commands/agent/writers.ts create mode 100644 packages/cli/src/commands/agent/writers/claude-code.ts create mode 100644 packages/cli/src/commands/agent/writers/codex.ts create mode 100644 packages/cli/src/commands/agent/writers/hermes.ts create mode 100644 packages/cli/src/commands/agent/writers/openclaw.ts create mode 100644 packages/cli/src/commands/agent/writers/opencode.ts create mode 100644 packages/cli/src/commands/agent/writers/qwen-code.ts create mode 100644 packages/cli/src/commands/agent/writers/utils.ts create mode 100644 skills/bailian-cli/reference/agent.md diff --git a/packages/cli/src/commands/agent/setup.ts b/packages/cli/src/commands/agent/setup.ts new file mode 100644 index 0000000..289dca6 --- /dev/null +++ b/packages/cli/src/commands/agent/setup.ts @@ -0,0 +1,87 @@ +import { platform } from "os"; +import { + defineCommand, + BailianError, + ExitCode, + type Config, + type GlobalFlags, +} from "bailian-cli-core"; +import { AGENTS, VALID_AGENT_NAMES, type WriteParams } from "./writers.ts"; + +export default defineCommand({ + name: "agent setup", + description: "Configure a coding agent to use DashScope API", + skipDefaultApiKeySetup: true, + usage: "bl agent setup --agent --base-url --api-key --model ", + options: [ + { + flag: "--agent ", + description: `Target agent: ${VALID_AGENT_NAMES.join(", ")}`, + }, + { flag: "--base-url ", description: "API base URL" }, + { flag: "--api-key ", description: "API key" }, + { flag: "--model ", description: "Default model name" }, + ], + examples: [ + "npx bailian-cli agent setup --agent claude-code --base-url https://dashscope.aliyuncs.com/apps/anthropic --api-key sk-xxxxx --model qwen3.7-max", + "npx bailian-cli agent setup --agent qwen-code --base-url https://dashscope.aliyuncs.com/compatible-mode/v1 --api-key sk-xxxxx --model qwen3.6-plus", + "npx bailian-cli agent setup --agent opencode --base-url https://dashscope.aliyuncs.com/apps/anthropic/v1 --api-key sk-xxxxx --model qwen3.7-max", + "npx bailian-cli agent setup --agent openclaw --base-url https://dashscope.aliyuncs.com/apps/anthropic --api-key sk-xxxxx --model qwen3.6-plus", + "npx bailian-cli agent setup --agent hermes --base-url https://dashscope.aliyuncs.com/apps/anthropic --api-key sk-xxxxx --model qwen3.7-max", + "npx bailian-cli agent setup --agent codex --base-url https://dashscope.aliyuncs.com/compatible-mode/v1 --api-key sk-xxxxx --model qwen3.7-max", + ], + async run(_config: Config, flags: GlobalFlags) { + const agent = flags.agent as string | undefined; + const baseUrl = flags.baseUrl as string | undefined; + const apiKey = flags.apiKey as string | undefined; + const model = flags.model as string | undefined; + + if (!agent || !baseUrl || !apiKey || !model) { + throw new BailianError( + "All flags are required: --agent, --base-url, --api-key, --model", + ExitCode.USAGE, + "bl agent setup --agent --base-url --api-key --model ", + ); + } + + const agentDef = AGENTS[agent]; + if (!agentDef) { + throw new BailianError( + `Unknown agent "${agent}". Valid agents: ${VALID_AGENT_NAMES.join(", ")}`, + ExitCode.USAGE, + ); + } + + // Hermes does not support native Windows + if (agent === "hermes" && platform() === "win32") { + process.stderr.write( + "Warning: Hermes Agent does not support native Windows. Please use WSL2.\n", + ); + } + + const params: WriteParams = { baseUrl, apiKey, model }; + + if (_config.dryRun) { + process.stdout.write(`[dry-run] Would configure ${agentDef.label} with:\n`); + process.stdout.write(` base-url: ${baseUrl}\n`); + process.stdout.write( + ` api-key: ${apiKey.slice(0, 6)}${"*".repeat(Math.max(0, apiKey.length - 6))}\n`, + ); + process.stdout.write(` model: ${model}\n`); + return; + } + + const summary = agentDef.write(params); + + const isTTY = process.stderr.isTTY; + const green = isTTY ? "\x1b[32m" : ""; + const cyan = isTTY ? "\x1b[36m" : ""; + const reset = isTTY ? "\x1b[0m" : ""; + + process.stderr.write(`\n${green}✔ ${agentDef.label} configured successfully.${reset}\n\n`); + for (const p of summary.paths) { + process.stderr.write(` Written: ${cyan}${p}${reset}\n`); + } + process.stderr.write(`\n ${summary.nextStep}\n\n`); + }, +}); diff --git a/packages/cli/src/commands/agent/writers.ts b/packages/cli/src/commands/agent/writers.ts new file mode 100644 index 0000000..3ff0630 --- /dev/null +++ b/packages/cli/src/commands/agent/writers.ts @@ -0,0 +1,20 @@ +export type { WriteParams, WriteSummary, AgentDef } from "./writers/utils.ts"; + +import type { AgentDef } from "./writers/utils.ts"; +import claudeCode from "./writers/claude-code.ts"; +import qwenCode from "./writers/qwen-code.ts"; +import opencode from "./writers/opencode.ts"; +import openclaw from "./writers/openclaw.ts"; +import hermes from "./writers/hermes.ts"; +import codex from "./writers/codex.ts"; + +export const AGENTS: Record = { + "claude-code": claudeCode, + "qwen-code": qwenCode, + opencode, + openclaw, + hermes, + codex, +}; + +export const VALID_AGENT_NAMES = Object.keys(AGENTS); diff --git a/packages/cli/src/commands/agent/writers/claude-code.ts b/packages/cli/src/commands/agent/writers/claude-code.ts new file mode 100644 index 0000000..bf07d68 --- /dev/null +++ b/packages/cli/src/commands/agent/writers/claude-code.ts @@ -0,0 +1,36 @@ +import { homedir } from "os"; +import { join } from "path"; +import { backup, readJson, writeJsonAtomic, type AgentDef } from "./utils.ts"; + +export default { + label: "Claude Code", + write({ baseUrl, apiKey, model }) { + const settingsPath = join(homedir(), ".claude", "settings.json"); + const onboardingPath = join(homedir(), ".claude.json"); + + // settings.json — merge env + backup(settingsPath); + const settings = readJson(settingsPath); + const env = (settings.env ?? {}) as Record; + env.ANTHROPIC_AUTH_TOKEN = apiKey; + env.ANTHROPIC_BASE_URL = baseUrl; + env.ANTHROPIC_MODEL = model; + env.ANTHROPIC_DEFAULT_HAIKU_MODEL = model; + env.ANTHROPIC_DEFAULT_SONNET_MODEL = model; + env.ANTHROPIC_DEFAULT_OPUS_MODEL = model; + env.CLAUDE_CODE_SUBAGENT_MODEL = model; + settings.env = env; + writeJsonAtomic(settingsPath, settings); + + // .claude.json — ensure hasCompletedOnboarding + backup(onboardingPath); + const onboarding = readJson(onboardingPath); + onboarding.hasCompletedOnboarding = true; + writeJsonAtomic(onboardingPath, onboarding); + + return { + paths: [settingsPath, onboardingPath], + nextStep: "Run `claude` to start using Claude Code with DashScope.", + }; + }, +} satisfies AgentDef; diff --git a/packages/cli/src/commands/agent/writers/codex.ts b/packages/cli/src/commands/agent/writers/codex.ts new file mode 100644 index 0000000..a7c4d6d --- /dev/null +++ b/packages/cli/src/commands/agent/writers/codex.ts @@ -0,0 +1,42 @@ +import { homedir } from "os"; +import { join } from "path"; +import { mkdirSync, writeFileSync, renameSync } from "fs"; +import { backup, type AgentDef } from "./utils.ts"; + +export default { + label: "Codex", + write({ baseUrl, apiKey, model }) { + const configPath = join(homedir(), ".codex", "config.toml"); + + backup(configPath); + + const toml = [ + `model_provider = "Model_Studio"`, + `model = "${model}"`, + ``, + `[model_providers.Model_Studio]`, + `name = "Model_Studio"`, + `base_url = "${baseUrl}"`, + `env_key = "OPENAI_API_KEY"`, + `wire_api = "responses"`, + ``, + ].join("\n"); + + mkdirSync(join(configPath, ".."), { recursive: true }); + const tmp = configPath + ".tmp"; + writeFileSync(tmp, toml, { mode: 0o600 }); + renameSync(tmp, configPath); + + // Also hint about OPENAI_API_KEY env var + const shell = process.platform === "win32" ? "powershell" : "shell"; + const envHint = + shell === "powershell" + ? `Set env: [Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "${apiKey}", "User")` + : `Set env: export OPENAI_API_KEY="${apiKey}"`; + + return { + paths: [configPath], + nextStep: `${envHint}\n Then run \`codex\` to start using Codex with DashScope.`, + }; + }, +} satisfies AgentDef; diff --git a/packages/cli/src/commands/agent/writers/hermes.ts b/packages/cli/src/commands/agent/writers/hermes.ts new file mode 100644 index 0000000..57e8a95 --- /dev/null +++ b/packages/cli/src/commands/agent/writers/hermes.ts @@ -0,0 +1,41 @@ +import { homedir } from "os"; +import { join } from "path"; +import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from "fs"; +import yaml from "yaml"; +import { backup, type AgentDef } from "./utils.ts"; + +export default { + label: "Hermes Agent", + write({ baseUrl, apiKey, model }) { + const configPath = join(homedir(), ".hermes", "config.yaml"); + + backup(configPath); + + let config: Record = {}; + if (existsSync(configPath)) { + try { + config = (yaml.parse(readFileSync(configPath, "utf-8")) ?? {}) as Record; + } catch { + config = {}; + } + } + + config.model = { + default: model, + provider: "custom", + base_url: baseUrl, + api_mode: "anthropic_messages", + api_key: apiKey, + }; + + mkdirSync(join(configPath, ".."), { recursive: true }); + const tmp = configPath + ".tmp"; + writeFileSync(tmp, yaml.stringify(config), { mode: 0o600 }); + renameSync(tmp, configPath); + + return { + paths: [configPath], + nextStep: 'Run `hermes chat -q "hello"` to verify.', + }; + }, +} satisfies AgentDef; diff --git a/packages/cli/src/commands/agent/writers/openclaw.ts b/packages/cli/src/commands/agent/writers/openclaw.ts new file mode 100644 index 0000000..58656bc --- /dev/null +++ b/packages/cli/src/commands/agent/writers/openclaw.ts @@ -0,0 +1,50 @@ +import { homedir } from "os"; +import { join } from "path"; +import { backup, readJson, writeJsonAtomic, type AgentDef } from "./utils.ts"; + +export default { + label: "OpenClaw", + write({ baseUrl, apiKey, model }) { + const configPath = join(homedir(), ".openclaw", "openclaw.json"); + + backup(configPath); + const config = readJson(configPath); + + // models.providers.bailian + const models = (config.models ?? {}) as Record; + models.mode = "merge"; + const providers = (models.providers ?? {}) as Record; + providers.bailian = { + baseUrl, + apiKey, + api: "anthropic-messages", + models: [ + { + id: model, + name: model, + reasoning: false, + input: ["text", "image"], + contextWindow: 1000000, + maxTokens: 65536, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + }, + ], + }; + models.providers = providers; + config.models = models; + + // agents.defaults + const agents = (config.agents ?? {}) as Record; + const defaults = (agents.defaults ?? {}) as Record; + defaults.model = { primary: `bailian/${model}` }; + agents.defaults = defaults; + config.agents = agents; + + writeJsonAtomic(configPath, config); + + return { + paths: [configPath], + nextStep: "Run `openclaw` to start using OpenClaw with DashScope.", + }; + }, +} satisfies AgentDef; diff --git a/packages/cli/src/commands/agent/writers/opencode.ts b/packages/cli/src/commands/agent/writers/opencode.ts new file mode 100644 index 0000000..4077aa5 --- /dev/null +++ b/packages/cli/src/commands/agent/writers/opencode.ts @@ -0,0 +1,31 @@ +import { homedir } from "os"; +import { join } from "path"; +import { backup, readJson, writeJsonAtomic, type AgentDef } from "./utils.ts"; + +export default { + label: "OpenCode", + write({ baseUrl, apiKey, model }) { + const configPath = join(homedir(), ".config", "opencode", "opencode.json"); + + backup(configPath); + const config = readJson(configPath); + + if (!config.$schema) config.$schema = "https://opencode.ai/config.json"; + + const provider = (config.provider ?? {}) as Record; + provider.bailian = { + npm: "@ai-sdk/anthropic", + name: "Alibaba Cloud Model Studio", + options: { baseURL: baseUrl, apiKey }, + models: { [model]: { name: model } }, + }; + config.provider = provider; + + writeJsonAtomic(configPath, config); + + return { + paths: [configPath], + nextStep: "Run `opencode` then type `/models` to select your model.", + }; + }, +} satisfies AgentDef; diff --git a/packages/cli/src/commands/agent/writers/qwen-code.ts b/packages/cli/src/commands/agent/writers/qwen-code.ts new file mode 100644 index 0000000..36a6eae --- /dev/null +++ b/packages/cli/src/commands/agent/writers/qwen-code.ts @@ -0,0 +1,49 @@ +import { homedir } from "os"; +import { join } from "path"; +import { backup, readJson, writeJsonAtomic, type AgentDef } from "./utils.ts"; + +export default { + label: "Qwen Code", + write({ baseUrl, apiKey, model }) { + const settingsPath = join(homedir(), ".qwen", "settings.json"); + + backup(settingsPath); + const settings = readJson(settingsPath); + + // env + const env = (settings.env ?? {}) as Record; + env.BAILIAN_API_KEY = apiKey; + settings.env = env; + + // modelProviders.openai — append or update + const providers = (settings.modelProviders ?? {}) as Record; + const openaiModels = (providers.openai ?? []) as Array>; + const existing = openaiModels.find((m) => m.id === model); + if (existing) { + existing.baseUrl = baseUrl; + existing.envKey = "BAILIAN_API_KEY"; + existing.name = `[Bailian] ${model}`; + } else { + openaiModels.push({ + id: model, + name: `[Bailian] ${model}`, + baseUrl, + envKey: "BAILIAN_API_KEY", + }); + } + providers.openai = openaiModels; + settings.modelProviders = providers; + + // security & model & version + settings.security = { auth: { selectedType: "openai" } }; + settings.model = { name: model }; + settings.$version = 3; + + writeJsonAtomic(settingsPath, settings); + + return { + paths: [settingsPath], + nextStep: "Run `qwen` to start using Qwen Code with DashScope.", + }; + }, +} satisfies AgentDef; diff --git a/packages/cli/src/commands/agent/writers/utils.ts b/packages/cli/src/commands/agent/writers/utils.ts new file mode 100644 index 0000000..ea0f524 --- /dev/null +++ b/packages/cli/src/commands/agent/writers/utils.ts @@ -0,0 +1,40 @@ +import { join } from "path"; +import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync, copyFileSync } from "fs"; + +export interface WriteParams { + baseUrl: string; + apiKey: string; + model: string; +} + +export interface WriteSummary { + paths: string[]; + nextStep: string; +} + +export interface AgentDef { + label: string; + write(params: WriteParams): WriteSummary; +} + +export function readJson(path: string): Record { + if (!existsSync(path)) return {}; + try { + return JSON.parse(readFileSync(path, "utf-8")) as Record; + } catch { + return {}; + } +} + +export function writeJsonAtomic(path: string, data: unknown): void { + mkdirSync(join(path, ".."), { recursive: true }); + const tmp = path + ".tmp"; + writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n", { mode: 0o600 }); + renameSync(tmp, path); +} + +export function backup(path: string): void { + if (!existsSync(path)) return; + const ts = Math.floor(Date.now() / 1000); + copyFileSync(path, `${path}.bak.${ts}`); +} diff --git a/packages/cli/src/commands/catalog.ts b/packages/cli/src/commands/catalog.ts index ae48fcc..a20e921 100644 --- a/packages/cli/src/commands/catalog.ts +++ b/packages/cli/src/commands/catalog.ts @@ -46,6 +46,7 @@ import quotaList from "./quota/list.ts"; import quotaRequest from "./quota/request.ts"; import quotaHistory from "./quota/history.ts"; import quotaCheck from "./quota/check.ts"; +import agentSetup from "./agent/setup.ts"; /** Command registry map (no dependency on registry.ts — safe for build-time import). */ export const commands: Record = { @@ -94,5 +95,6 @@ export const commands: Record = { "quota request": quotaRequest, "quota history": quotaHistory, "quota check": quotaCheck, + "agent setup": agentSetup, update: update, }; diff --git a/skills/bailian-cli/reference/agent.md b/skills/bailian-cli/reference/agent.md new file mode 100644 index 0000000..84e5544 --- /dev/null +++ b/skills/bailian-cli/reference/agent.md @@ -0,0 +1,57 @@ +# `bl agent` commands + +> Auto-generated from `packages/cli/src/commands/catalog.ts`. Do not edit by hand. +> Regenerate: `pnpm --filter bailian-cli run generate:reference`. + +Index: [index.md](index.md) + +## Commands in this group + +| Command | Description | +| ---------------- | --------------------------------------------- | +| `bl agent setup` | Configure a coding agent to use DashScope API | + +## Command details + +### `bl agent setup` + +| Field | Value | +| --------------- | -------------------------------------------------------------------------------- | +| **Name** | `agent setup` | +| **Description** | Configure a coding agent to use DashScope API | +| **Usage** | `bl agent setup --agent --base-url --api-key --model ` | + +#### Options + +| Flag | Type | Required | Description | +| ------------------ | ------ | -------- | ----------------------------------------------------------------------- | +| `--agent ` | string | no | Target agent: claude-code, qwen-code, opencode, openclaw, hermes, codex | +| `--base-url ` | string | no | API base URL | +| `--api-key ` | string | no | API key | +| `--model ` | string | no | Default model name | + +#### Examples + +```bash +npx bailian-cli agent setup --agent claude-code --base-url https://dashscope.aliyuncs.com/apps/anthropic --api-key sk-xxxxx --model qwen3.7-max +``` + +```bash +npx bailian-cli agent setup --agent qwen-code --base-url https://dashscope.aliyuncs.com/compatible-mode/v1 --api-key sk-xxxxx --model qwen3.6-plus +``` + +```bash +npx bailian-cli agent setup --agent opencode --base-url https://dashscope.aliyuncs.com/apps/anthropic/v1 --api-key sk-xxxxx --model qwen3.7-max +``` + +```bash +npx bailian-cli agent setup --agent openclaw --base-url https://dashscope.aliyuncs.com/apps/anthropic --api-key sk-xxxxx --model qwen3.6-plus +``` + +```bash +npx bailian-cli agent setup --agent hermes --base-url https://dashscope.aliyuncs.com/apps/anthropic --api-key sk-xxxxx --model qwen3.7-max +``` + +```bash +npx bailian-cli agent setup --agent codex --base-url https://dashscope.aliyuncs.com/compatible-mode/v1 --api-key sk-xxxxx --model qwen3.7-max +``` diff --git a/skills/bailian-cli/reference/index.md b/skills/bailian-cli/reference/index.md index 890f43b..897f8a9 100644 --- a/skills/bailian-cli/reference/index.md +++ b/skills/bailian-cli/reference/index.md @@ -11,6 +11,7 @@ Use this index for the full quick index and global flags. | Command | Description | Detail | | -------------------------- | ----------------------------------------------------------------------------------------------------- | ---------------------------- | | `bl advisor recommend` | Recommend the best models for your use case (intent analysis → candidate recall → LLM ranking) | [advisor.md](advisor.md) | +| `bl agent setup` | Configure a coding agent to use DashScope API | [agent.md](agent.md) | | `bl app call` | Call a Bailian application (agent or workflow) | [app.md](app.md) | | `bl app list` | List Bailian applications | [app.md](app.md) | | `bl auth login` | Authenticate with API key or console browser login (credentials can coexist) | [auth.md](auth.md) | @@ -62,6 +63,7 @@ Use this index for the full quick index and global flags. | Group | Commands | Reference | | ----------- | ---------------------------------------------------------------------------- | ---------------------------- | | `advisor` | `recommend` | [advisor.md](advisor.md) | +| `agent` | `setup` | [agent.md](agent.md) | | `app` | `call`, `list` | [app.md](app.md) | | `auth` | `login`, `logout`, `status` | [auth.md](auth.md) | | `config` | `export-schema`, `set`, `show` | [config.md](config.md) | From 717822de0d40aaa562f6c9e252a294e556bc8d25 Mon Sep 17 00:00:00 2001 From: "lisheng.lisheng" Date: Sat, 20 Jun 2026 23:02:40 +0800 Subject: [PATCH 2/3] fix: infer API protocol from base-url and add codex auth.json - OpenCode/OpenClaw/Hermes: dynamically choose API protocol based on whether the base-url is an anthropic endpoint or openai-compatible - Codex: write ~/.codex/auth.json alongside config.toml so users don't need to manually export OPENAI_API_KEY - Add shared isAnthropicEndpoint() helper in writers/utils.ts Co-Authored-By: Claude Opus 4.6 (1M context) --- .../cli/src/commands/agent/writers/codex.ts | 18 +++++++++--------- .../cli/src/commands/agent/writers/hermes.ts | 5 +++-- .../cli/src/commands/agent/writers/openclaw.ts | 5 +++-- .../cli/src/commands/agent/writers/opencode.ts | 5 +++-- .../cli/src/commands/agent/writers/utils.ts | 4 ++++ 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/commands/agent/writers/codex.ts b/packages/cli/src/commands/agent/writers/codex.ts index a7c4d6d..a84bd63 100644 --- a/packages/cli/src/commands/agent/writers/codex.ts +++ b/packages/cli/src/commands/agent/writers/codex.ts @@ -1,7 +1,7 @@ import { homedir } from "os"; import { join } from "path"; import { mkdirSync, writeFileSync, renameSync } from "fs"; -import { backup, type AgentDef } from "./utils.ts"; +import { backup, readJson, writeJsonAtomic, type AgentDef } from "./utils.ts"; export default { label: "Codex", @@ -27,16 +27,16 @@ export default { writeFileSync(tmp, toml, { mode: 0o600 }); renameSync(tmp, configPath); - // Also hint about OPENAI_API_KEY env var - const shell = process.platform === "win32" ? "powershell" : "shell"; - const envHint = - shell === "powershell" - ? `Set env: [Environment]::SetEnvironmentVariable("OPENAI_API_KEY", "${apiKey}", "User")` - : `Set env: export OPENAI_API_KEY="${apiKey}"`; + // auth.json — store API key for Codex to read + const authPath = join(homedir(), ".codex", "auth.json"); + backup(authPath); + const auth = readJson(authPath); + auth.OPENAI_API_KEY = apiKey; + writeJsonAtomic(authPath, auth); return { - paths: [configPath], - nextStep: `${envHint}\n Then run \`codex\` to start using Codex with DashScope.`, + paths: [configPath, authPath], + nextStep: "Run `codex` to start using Codex with DashScope.", }; }, } satisfies AgentDef; diff --git a/packages/cli/src/commands/agent/writers/hermes.ts b/packages/cli/src/commands/agent/writers/hermes.ts index 57e8a95..d1e2aa9 100644 --- a/packages/cli/src/commands/agent/writers/hermes.ts +++ b/packages/cli/src/commands/agent/writers/hermes.ts @@ -2,7 +2,7 @@ import { homedir } from "os"; import { join } from "path"; import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from "fs"; import yaml from "yaml"; -import { backup, type AgentDef } from "./utils.ts"; +import { backup, isAnthropicEndpoint, type AgentDef } from "./utils.ts"; export default { label: "Hermes Agent", @@ -20,11 +20,12 @@ export default { } } + const apiMode = isAnthropicEndpoint(baseUrl) ? "anthropic_messages" : "chat_completions"; config.model = { default: model, provider: "custom", base_url: baseUrl, - api_mode: "anthropic_messages", + api_mode: apiMode, api_key: apiKey, }; diff --git a/packages/cli/src/commands/agent/writers/openclaw.ts b/packages/cli/src/commands/agent/writers/openclaw.ts index 58656bc..533181c 100644 --- a/packages/cli/src/commands/agent/writers/openclaw.ts +++ b/packages/cli/src/commands/agent/writers/openclaw.ts @@ -1,6 +1,6 @@ import { homedir } from "os"; import { join } from "path"; -import { backup, readJson, writeJsonAtomic, type AgentDef } from "./utils.ts"; +import { backup, readJson, writeJsonAtomic, isAnthropicEndpoint, type AgentDef } from "./utils.ts"; export default { label: "OpenClaw", @@ -14,10 +14,11 @@ export default { const models = (config.models ?? {}) as Record; models.mode = "merge"; const providers = (models.providers ?? {}) as Record; + const api = isAnthropicEndpoint(baseUrl) ? "anthropic-messages" : "openai-completions"; providers.bailian = { baseUrl, apiKey, - api: "anthropic-messages", + api, models: [ { id: model, diff --git a/packages/cli/src/commands/agent/writers/opencode.ts b/packages/cli/src/commands/agent/writers/opencode.ts index 4077aa5..d4ae556 100644 --- a/packages/cli/src/commands/agent/writers/opencode.ts +++ b/packages/cli/src/commands/agent/writers/opencode.ts @@ -1,6 +1,6 @@ import { homedir } from "os"; import { join } from "path"; -import { backup, readJson, writeJsonAtomic, type AgentDef } from "./utils.ts"; +import { backup, readJson, writeJsonAtomic, isAnthropicEndpoint, type AgentDef } from "./utils.ts"; export default { label: "OpenCode", @@ -13,8 +13,9 @@ export default { if (!config.$schema) config.$schema = "https://opencode.ai/config.json"; const provider = (config.provider ?? {}) as Record; + const npm = isAnthropicEndpoint(baseUrl) ? "@ai-sdk/anthropic" : "@ai-sdk/openai-compatible"; provider.bailian = { - npm: "@ai-sdk/anthropic", + npm, name: "Alibaba Cloud Model Studio", options: { baseURL: baseUrl, apiKey }, models: { [model]: { name: model } }, diff --git a/packages/cli/src/commands/agent/writers/utils.ts b/packages/cli/src/commands/agent/writers/utils.ts index ea0f524..1e57ced 100644 --- a/packages/cli/src/commands/agent/writers/utils.ts +++ b/packages/cli/src/commands/agent/writers/utils.ts @@ -38,3 +38,7 @@ export function backup(path: string): void { const ts = Math.floor(Date.now() / 1000); copyFileSync(path, `${path}.bak.${ts}`); } + +export function isAnthropicEndpoint(baseUrl: string): boolean { + return baseUrl.includes("/apps/anthropic"); +} From 64473c6cd03a95cd133deef9d5aaecd7e575e279 Mon Sep 17 00:00:00 2001 From: "lisheng.lisheng" Date: Mon, 22 Jun 2026 11:37:11 +0800 Subject: [PATCH 3/3] feat: add agent setup scripts for curl/irm one-liner usage Two variants: - via-bailian-cli/: uses npx bailian-cli (requires Node.js 22+) - pure/: zero dependencies, writes config files directly in bash/PowerShell Supports all 6 agents with API protocol auto-detection from base-url. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../agent/scripts/pure/agent-setup.ps1 | 287 ++++++++++++ .../agent/scripts/pure/agent-setup.sh | 443 ++++++++++++++++++ .../scripts/via-bailian-cli/agent-setup.ps1 | 86 ++++ .../scripts/via-bailian-cli/agent-setup.sh | 106 +++++ 4 files changed, 922 insertions(+) create mode 100644 packages/cli/src/commands/agent/scripts/pure/agent-setup.ps1 create mode 100755 packages/cli/src/commands/agent/scripts/pure/agent-setup.sh create mode 100644 packages/cli/src/commands/agent/scripts/via-bailian-cli/agent-setup.ps1 create mode 100755 packages/cli/src/commands/agent/scripts/via-bailian-cli/agent-setup.sh diff --git a/packages/cli/src/commands/agent/scripts/pure/agent-setup.ps1 b/packages/cli/src/commands/agent/scripts/pure/agent-setup.ps1 new file mode 100644 index 0000000..8355a3e --- /dev/null +++ b/packages/cli/src/commands/agent/scripts/pure/agent-setup.ps1 @@ -0,0 +1,287 @@ +# Bailian — Pure Agent Setup (no Node.js / npx required) +# +# Directly writes config files for coding agents to use DashScope API. +# +# Usage (set env vars, then pipe): +# $env:BL_AGENT="claude-code" +# $env:BL_BASE_URL="https://dashscope.aliyuncs.com/apps/anthropic" +# $env:BL_API_KEY="sk-xxxxx" +# $env:BL_MODEL="qwen3.7-max" +# irm https://xxx/agent-setup.ps1 | iex + +$ErrorActionPreference = "Stop" + +function Write-Err { Write-Host "ERROR: $args" -ForegroundColor Red } +function Write-Info { Write-Host "INFO: $args" -ForegroundColor Blue } +function Write-Ok { Write-Host "[OK] $args" -ForegroundColor Green } +function Write-Warn { Write-Host "WARNING: $args" -ForegroundColor Yellow } + +function Test-AnthropicEndpoint([string]$Url) { + return $Url -match '/apps/anthropic' +} + +function Backup-File([string]$Path) { + if (Test-Path $Path) { + $ts = [int][double]::Parse((Get-Date -UFormat %s)) + Copy-Item $Path "$Path.bak.$ts" + } +} + +function Write-SafeFile([string]$Path, [string]$Content) { + $dir = Split-Path $Path -Parent + if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } + $tmp = "$Path.tmp" + Set-Content -Path $tmp -Value $Content -Encoding UTF8 -NoNewline + Move-Item -Path $tmp -Destination $Path -Force +} + +# ── Agent writers ───────────────────────────────────────────────────────────── + +function Setup-ClaudeCode([string]$BaseUrl, [string]$ApiKey, [string]$Model, [bool]$DryRun) { + $settings = Join-Path $HOME ".claude" "settings.json" + $onboarding = Join-Path $HOME ".claude.json" + + if ($DryRun) { Write-Info "[dry-run] Would write: $settings, $onboarding"; return } + + # settings.json — merge env if possible + $data = @{} + if (Test-Path $settings) { + try { $data = Get-Content $settings -Raw | ConvertFrom-Json -AsHashtable } catch { $data = @{} } + } + if (-not $data.ContainsKey("env")) { $data["env"] = @{} } + $data["env"]["ANTHROPIC_AUTH_TOKEN"] = $ApiKey + $data["env"]["ANTHROPIC_BASE_URL"] = $BaseUrl + $data["env"]["ANTHROPIC_MODEL"] = $Model + $data["env"]["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = $Model + $data["env"]["ANTHROPIC_DEFAULT_SONNET_MODEL"] = $Model + $data["env"]["ANTHROPIC_DEFAULT_OPUS_MODEL"] = $Model + $data["env"]["CLAUDE_CODE_SUBAGENT_MODEL"] = $Model + + Backup-File $settings + Write-SafeFile $settings ($data | ConvertTo-Json -Depth 10) + + # .claude.json — ensure hasCompletedOnboarding + $ob = @{} + if (Test-Path $onboarding) { + try { $ob = Get-Content $onboarding -Raw | ConvertFrom-Json -AsHashtable } catch { $ob = @{} } + } + $ob["hasCompletedOnboarding"] = $true + + Backup-File $onboarding + Write-SafeFile $onboarding ($ob | ConvertTo-Json -Depth 10) + + Write-Ok "Claude Code configured." + Write-Host " Written: $settings" + Write-Host " Written: $onboarding" + Write-Host "" + Write-Host " Run ``claude`` to start using Claude Code with DashScope." +} + +function Setup-QwenCode([string]$BaseUrl, [string]$ApiKey, [string]$Model, [bool]$DryRun) { + $settings = Join-Path $HOME ".qwen" "settings.json" + + if ($DryRun) { Write-Info "[dry-run] Would write: $settings"; return } + + $data = @{ + env = @{ BAILIAN_API_KEY = $ApiKey } + modelProviders = @{ + openai = @( + @{ + id = $Model + name = "[Bailian] $Model" + baseUrl = $BaseUrl + envKey = "BAILIAN_API_KEY" + } + ) + } + security = @{ auth = @{ selectedType = "openai" } } + model = @{ name = $Model } + '$version' = 3 + } + + Backup-File $settings + Write-SafeFile $settings ($data | ConvertTo-Json -Depth 10) + + Write-Ok "Qwen Code configured." + Write-Host " Written: $settings" + Write-Host "" + Write-Host " Run ``qwen`` to start using Qwen Code with DashScope." +} + +function Setup-OpenCode([string]$BaseUrl, [string]$ApiKey, [string]$Model, [bool]$DryRun) { + $config = Join-Path $HOME ".config" "opencode" "opencode.json" + + $npm = if (Test-AnthropicEndpoint $BaseUrl) { "@ai-sdk/anthropic" } else { "@ai-sdk/openai-compatible" } + + if ($DryRun) { Write-Info "[dry-run] Would write: $config (npm: $npm)"; return } + + $data = @{ + '$schema' = "https://opencode.ai/config.json" + provider = @{ + bailian = @{ + npm = $npm + name = "Alibaba Cloud Model Studio" + options = @{ baseURL = $BaseUrl; apiKey = $ApiKey } + models = @{ $Model = @{ name = $Model } } + } + } + } + + Backup-File $config + Write-SafeFile $config ($data | ConvertTo-Json -Depth 10) + + Write-Ok "OpenCode configured." + Write-Host " Written: $config" + Write-Host "" + Write-Host " Run ``opencode`` then type ``/models`` to select your model." +} + +function Setup-OpenClaw([string]$BaseUrl, [string]$ApiKey, [string]$Model, [bool]$DryRun) { + $config = Join-Path $HOME ".openclaw" "openclaw.json" + + $api = if (Test-AnthropicEndpoint $BaseUrl) { "anthropic-messages" } else { "openai-completions" } + + if ($DryRun) { Write-Info "[dry-run] Would write: $config (api: $api)"; return } + + $data = @{ + models = @{ + mode = "merge" + providers = @{ + bailian = @{ + baseUrl = $BaseUrl + apiKey = $ApiKey + api = $api + models = @( + @{ + id = $Model + name = $Model + reasoning = $false + input = @("text", "image") + contextWindow = 1000000 + maxTokens = 65536 + cost = @{ input = 0; output = 0; cacheRead = 0; cacheWrite = 0 } + } + ) + } + } + } + agents = @{ + defaults = @{ + model = @{ primary = "bailian/$Model" } + } + } + } + + Backup-File $config + Write-SafeFile $config ($data | ConvertTo-Json -Depth 10) + + Write-Ok "OpenClaw configured." + Write-Host " Written: $config" + Write-Host "" + Write-Host " Run ``openclaw`` to start using OpenClaw with DashScope." +} + +function Setup-Hermes([string]$BaseUrl, [string]$ApiKey, [string]$Model, [bool]$DryRun) { + $config = Join-Path $HOME ".hermes" "config.yaml" + + $apiMode = if (Test-AnthropicEndpoint $BaseUrl) { "anthropic_messages" } else { "chat_completions" } + + if ($DryRun) { Write-Info "[dry-run] Would write: $config (api_mode: $apiMode)"; return } + + Write-Warn "Hermes Agent does not support native Windows. This config is for WSL2." + + $yaml = @" +model: + default: $Model + provider: custom + base_url: $BaseUrl + api_mode: $apiMode + api_key: $ApiKey +"@ + + Backup-File $config + Write-SafeFile $config $yaml + + Write-Ok "Hermes Agent configured." + Write-Host " Written: $config" + Write-Host "" + Write-Host " Run ``hermes chat -q `"hello`"`` to verify." +} + +function Setup-Codex([string]$BaseUrl, [string]$ApiKey, [string]$Model, [bool]$DryRun) { + $config = Join-Path $HOME ".codex" "config.toml" + $auth = Join-Path $HOME ".codex" "auth.json" + + if ($DryRun) { Write-Info "[dry-run] Would write: $config, $auth"; return } + + $toml = @" +model_provider = "Model_Studio" +model = "$Model" + +[model_providers.Model_Studio] +name = "Model_Studio" +base_url = "$BaseUrl" +env_key = "OPENAI_API_KEY" +wire_api = "responses" +"@ + + Backup-File $config + Write-SafeFile $config $toml + + Backup-File $auth + Write-SafeFile $auth "{`n `"OPENAI_API_KEY`": `"$ApiKey`"`n}" + + Write-Ok "Codex configured." + Write-Host " Written: $config" + Write-Host " Written: $auth" + Write-Host "" + Write-Host " Run ``codex`` to start using Codex with DashScope." +} + +# ── Read parameters from environment variables ──────────────────────────────── + +$Agent = $env:BL_AGENT +$BaseUrl = $env:BL_BASE_URL +$ApiKey = $env:BL_API_KEY +$Model = $env:BL_MODEL +$DryRun = $env:BL_DRY_RUN -eq "1" + +if (-not $Agent -or -not $BaseUrl -or -not $ApiKey -or -not $Model) { + Write-Err "Missing required environment variables." + Write-Host "" + Write-Host "Set these before running:" + Write-Host ' $env:BL_AGENT = "claude-code"' + Write-Host ' $env:BL_BASE_URL = "https://dashscope.aliyuncs.com/apps/anthropic"' + Write-Host ' $env:BL_API_KEY = "sk-xxxxx"' + Write-Host ' $env:BL_MODEL = "qwen3.7-max"' + Write-Host "" + Write-Host "Then run:" + Write-Host ' irm https://xxx/agent-setup.ps1 | iex' + exit 1 +} + +# ── Dispatch ────────────────────────────────────────────────────────────────── + +Write-Host "" + +switch ($Agent) { + "claude-code" { Setup-ClaudeCode $BaseUrl $ApiKey $Model $DryRun } + "qwen-code" { Setup-QwenCode $BaseUrl $ApiKey $Model $DryRun } + "opencode" { Setup-OpenCode $BaseUrl $ApiKey $Model $DryRun } + "openclaw" { Setup-OpenClaw $BaseUrl $ApiKey $Model $DryRun } + "hermes" { Setup-Hermes $BaseUrl $ApiKey $Model $DryRun } + "codex" { Setup-Codex $BaseUrl $ApiKey $Model $DryRun } + default { + Write-Err "Unknown agent `"$Agent`". Valid agents: claude-code, qwen-code, opencode, openclaw, hermes, codex" + exit 1 + } +} + +Write-Host "" + +# Clean up env vars +Remove-Item Env:BL_AGENT -ErrorAction SilentlyContinue +Remove-Item Env:BL_BASE_URL -ErrorAction SilentlyContinue +Remove-Item Env:BL_API_KEY -ErrorAction SilentlyContinue +Remove-Item Env:BL_MODEL -ErrorAction SilentlyContinue +Remove-Item Env:BL_DRY_RUN -ErrorAction SilentlyContinue diff --git a/packages/cli/src/commands/agent/scripts/pure/agent-setup.sh b/packages/cli/src/commands/agent/scripts/pure/agent-setup.sh new file mode 100755 index 0000000..63b6a6f --- /dev/null +++ b/packages/cli/src/commands/agent/scripts/pure/agent-setup.sh @@ -0,0 +1,443 @@ +#!/usr/bin/env bash +# Bailian — Pure Agent Setup (no Node.js / npx required) +# +# Directly writes config files for coding agents to use DashScope API. +# +# Usage: +# curl -fsSL /agent-setup.sh | bash -s -- \ +# --agent claude-code \ +# --base-url https://dashscope.aliyuncs.com/apps/anthropic \ +# --api-key sk-xxxxx \ +# --model qwen3.7-max + +set -eo pipefail + +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +log_info() { printf "${BLUE}INFO:${NC} %s\n" "$1"; } +log_success() { printf "${GREEN}✔${NC} %s\n" "$1"; } +log_warning() { printf "${YELLOW}WARNING:${NC} %s\n" "$1" >&2; } +log_error() { printf "${RED}ERROR:${NC} %s\n" "$1" >&2; } + +VALID_AGENTS="claude-code, qwen-code, opencode, openclaw, hermes, codex" + +print_usage() { + echo "" + printf "${CYAN}Bailian — Agent Setup (pure, no Node.js required)${NC}\n" + cat <<'EOF' + +Usage: + curl -fsSL /agent-setup.sh | bash -s -- [OPTIONS] + +Required Options: + --agent Target agent: claude-code, qwen-code, opencode, openclaw, hermes, codex + --base-url API base URL + --api-key API key + --model Default model name + +Optional: + --dry-run Show what would be written without modifying files + -h, --help Show this help message + +Examples: + curl -fsSL /agent-setup.sh | bash -s -- \ + --agent claude-code \ + --base-url https://dashscope.aliyuncs.com/apps/anthropic \ + --api-key sk-xxxxx \ + --model qwen3.7-max +EOF +} + +# ── Helpers ─────────────────────────────────────────────────────────────────── + +is_anthropic_endpoint() { + case "$1" in + */apps/anthropic*) return 0 ;; + *) return 1 ;; + esac +} + +backup_file() { + local f="$1" + [ -f "$f" ] && cp "$f" "${f}.bak.$(date +%s)" +} + +ensure_dir() { + mkdir -p "$1" +} + +write_file() { + local path="$1" content="$2" + ensure_dir "$(dirname "$path")" + local tmp="${path}.tmp" + printf '%s' "$content" > "$tmp" + chmod 600 "$tmp" + mv "$tmp" "$path" +} + +# Minimal JSON merge: read existing file, set a top-level key to a JSON value. +# Uses python3/python if available, otherwise falls back to writing fresh. +json_set_key() { + local file="$1" key="$2" value="$3" + if [ -f "$file" ] && command -v python3 >/dev/null 2>&1; then + python3 -c " +import json, sys +try: + with open('$file') as f: data = json.load(f) +except: data = {} +data['$key'] = json.loads('''$value''') +print(json.dumps(data, indent=2)) +" 2>/dev/null && return 0 + fi + # fallback: cannot merge, caller handles + return 1 +} + +# Read existing JSON file, merge env keys, write back +merge_json_env() { + local file="$1" + shift + # remaining args are key=value pairs + if [ -f "$file" ] && command -v python3 >/dev/null 2>&1; then + local py_pairs="" + while [ $# -gt 0 ]; do + local k="${1%%=*}" v="${1#*=}" + py_pairs="${py_pairs}d.setdefault('env',{})['${k}']='${v}';" + shift + done + python3 -c " +import json +try: + with open('$file') as f: d = json.load(f) +except: d = {} +${py_pairs} +print(json.dumps(d, indent=2)) +" 2>/dev/null && return 0 + fi + return 1 +} + +mask_key() { + local k="$1" + printf '%s' "${k:0:6}" + printf '%*s' $((${#k} - 6)) '' | tr ' ' '*' +} + +# ── Agent writers ───────────────────────────────────────────────────────────── + +setup_claude_code() { + local base_url="$1" api_key="$2" model="$3" dry_run="$4" + local settings="$HOME/.claude/settings.json" + local onboarding="$HOME/.claude.json" + + if [ "$dry_run" = "1" ]; then + log_info "[dry-run] Would write: $settings, $onboarding" + return + fi + + backup_file "$settings" + local merged + if merged=$(merge_json_env "$settings" \ + "ANTHROPIC_AUTH_TOKEN=$api_key" \ + "ANTHROPIC_BASE_URL=$base_url" \ + "ANTHROPIC_MODEL=$model" \ + "ANTHROPIC_DEFAULT_HAIKU_MODEL=$model" \ + "ANTHROPIC_DEFAULT_SONNET_MODEL=$model" \ + "ANTHROPIC_DEFAULT_OPUS_MODEL=$model" \ + "CLAUDE_CODE_SUBAGENT_MODEL=$model"); then + write_file "$settings" "$merged" + else + write_file "$settings" "$(cat </dev/null 2>&1; then + local ob + ob=$(python3 -c " +import json +try: + with open('$onboarding') as f: d = json.load(f) +except: d = {} +d['hasCompletedOnboarding'] = True +print(json.dumps(d, indent=2)) +" 2>/dev/null) && write_file "$onboarding" "$ob" + else + write_file "$onboarding" '{"hasCompletedOnboarding":true}' + fi + + log_success "Claude Code configured." + echo " Written: $settings" + echo " Written: $onboarding" + echo "" + echo " Run \`claude\` to start using Claude Code with DashScope." +} + +setup_qwen_code() { + local base_url="$1" api_key="$2" model="$3" dry_run="$4" + local settings="$HOME/.qwen/settings.json" + + if [ "$dry_run" = "1" ]; then + log_info "[dry-run] Would write: $settings" + return + fi + + backup_file "$settings" + write_file "$settings" "$(cat <$null + if ([int]$nodeMajor -lt 22) { + $nodeVersion = & node -v 2>$null + Write-Err "Node.js $nodeVersion is installed, but version 22+ is required." + Write-Host "" + Write-Host "Upgrade Node.js: https://nodejs.org/" + exit 1 + } +} + +# ── Read parameters from environment variables ──────────────────────────────── + +$Agent = $env:BL_AGENT +$BaseUrl = $env:BL_BASE_URL +$ApiKey = $env:BL_API_KEY +$Model = $env:BL_MODEL +$DryRun = $env:BL_DRY_RUN + +if (-not $Agent -or -not $BaseUrl -or -not $ApiKey -or -not $Model) { + Write-Err "Missing required environment variables." + Write-Host "" + Write-Host "Set these before running:" + Write-Host ' $env:BL_AGENT = "claude-code"' + Write-Host ' $env:BL_BASE_URL = "https://dashscope.aliyuncs.com/apps/anthropic"' + Write-Host ' $env:BL_API_KEY = "sk-xxxxx"' + Write-Host ' $env:BL_MODEL = "qwen3.7-max"' + Write-Host "" + Write-Host "Then run:" + Write-Host ' irm https://xxx/agent-setup.ps1 | iex' + exit 1 +} + +# ── Main ────────────────────────────────────────────────────────────────────── + +Test-Node + +$npxArgs = @("bailian-cli@latest", "agent", "setup", + "--agent", $Agent, + "--base-url", $BaseUrl, + "--api-key", $ApiKey, + "--model", $Model) + +if ($DryRun -eq "1") { + $npxArgs += "--dry-run" +} + +Write-Info "Running: npx $($npxArgs -join ' ')" +Write-Host "" + +& npx @npxArgs + +# Clean up env vars +Remove-Item Env:BL_AGENT -ErrorAction SilentlyContinue +Remove-Item Env:BL_BASE_URL -ErrorAction SilentlyContinue +Remove-Item Env:BL_API_KEY -ErrorAction SilentlyContinue +Remove-Item Env:BL_MODEL -ErrorAction SilentlyContinue +Remove-Item Env:BL_DRY_RUN -ErrorAction SilentlyContinue diff --git a/packages/cli/src/commands/agent/scripts/via-bailian-cli/agent-setup.sh b/packages/cli/src/commands/agent/scripts/via-bailian-cli/agent-setup.sh new file mode 100755 index 0000000..c17ec3e --- /dev/null +++ b/packages/cli/src/commands/agent/scripts/via-bailian-cli/agent-setup.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +# Bailian CLI — Agent Setup Script +# +# Configures a coding agent (Claude Code, Qwen Code, OpenCode, OpenClaw, +# Hermes, Codex) to use DashScope API with a single command. +# +# Usage: +# curl -fsSL /agent-setup.sh | bash -s -- \ +# --agent claude-code \ +# --base-url https://dashscope.aliyuncs.com/apps/anthropic \ +# --api-key sk-xxxxx \ +# --model qwen3.7-max + +set -eo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +log_info() { printf "${BLUE}INFO:${NC} %s\n" "$1"; } +log_success() { printf "${GREEN}✔${NC} %s\n" "$1"; } +log_warning() { printf "${YELLOW}WARNING:${NC} %s\n" "$1" >&2; } +log_error() { printf "${RED}ERROR:${NC} %s\n" "$1" >&2; } + +print_usage() { + echo "" + printf "${CYAN}Bailian CLI — Agent Setup${NC}\n" + cat </agent-setup.sh | bash -s -- [OPTIONS] + +Required Options: + --agent Target agent: claude-code, qwen-code, opencode, openclaw, hermes, codex + --base-url API base URL + --api-key API key + --model Default model name + +Optional: + --dry-run Show what would be written without modifying files + -h, --help Show this help message + +Examples: + curl -fsSL /agent-setup.sh | bash -s -- \\ + --agent claude-code \\ + --base-url https://dashscope.aliyuncs.com/apps/anthropic \\ + --api-key sk-xxxxx \\ + --model qwen3.7-max + +EOF +} + +# ── Check Node.js ───────────────────────────────────────────────────────────── + +check_node() { + if ! command -v node >/dev/null 2>&1; then + log_error "Node.js is not installed." + echo "" + echo "Install Node.js 22+ from one of:" + echo " • https://nodejs.org/" + echo " • brew install node (macOS)" + echo " • apt install nodejs (Debian/Ubuntu)" + echo " • dnf install nodejs (Fedora)" + exit 1 + fi + + local node_major + node_major=$(node -p "Number(process.versions.node.split('.')[0])" 2>/dev/null || echo "0") + + if [ "$node_major" -lt 22 ] 2>/dev/null; then + local node_version + node_version=$(node -v 2>/dev/null || echo "unknown") + log_error "Node.js ${node_version} is installed, but version 22+ is required." + echo "" + echo "Upgrade Node.js: https://nodejs.org/" + exit 1 + fi +} + +# ── Parse args ──────────────────────────────────────────────────────────────── + +if [ $# -eq 0 ]; then + print_usage + exit 1 +fi + +for arg in "$@"; do + case "$arg" in + -h|--help) + print_usage + exit 0 + ;; + esac +done + +# ── Main ────────────────────────────────────────────────────────────────────── + +check_node + +log_info "Running: npx bailian-cli@latest agent setup $*" +echo "" + +npx bailian-cli@latest agent setup "$@"