1 line
8.2 KiB
JSON
1 line
8.2 KiB
JSON
{"repo": "bound-agents/bound", "files": {"packages/cli/src/commands/init.ts": {"dd84aa258de8396719d1e2a1d709ecc521370125": {"t": 1785846937.7664533, "n": 7548, "h": "d446b33eb40b6b6f", "c": "// Task 2: bound init command\n// Interactive config generation with presets\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { BOUND_NAMESPACE, deterministicUUID } from \"@bound/shared\";\n\nexport interface InitArgs {\n\tollama?: boolean;\n\tbedrock?: boolean;\n\tcerebras?: boolean;\n\tzai?: boolean;\n\topencodeGo?: boolean;\n\tumans?: boolean;\n\t/** Hub-only mode: no local inference backends; the node relays inference to spokes. */\n\thub?: boolean;\n\tregion?: string;\n\tname?: string;\n\twithSync?: boolean;\n\twithMcp?: boolean;\n\tforce?: boolean;\n\tconfigDir?: string;\n}\n\nexport async function runInit(args: InitArgs): Promise<void> {\n\tconst configDir = args.configDir || \"config\";\n\n\t// Check if config already exists\n\tconst allowlistPath = resolve(configDir, \"allowlist.json\");\n\tconst modelBackendsPath = resolve(configDir, \"model_backends.json\");\n\n\tif (!args.force && (existsSync(allowlistPath) || existsSync(modelBackendsPath))) {\n\t\tconsole.log(\"Config already exists. Use --force to overwrite.\");\n\t\t// Return rather than process.exit so that callers (CLI entrypoint and tests)\n\t\t// continue normally. The CLI's bound.ts already exits 0 after this function\n\t\t// returns; tests need the worker to stay alive for subsequent test cases.\n\t\treturn;\n\t}\n\n\t// Create config directory\n\tmkdirSync(configDir, { recursive: true });\n\n\tconst operatorName = args.name || process.env.USER || \"operator\";\n\t// Default preset: Ollama, reached through the OpenAI-compatible driver. Ollama serves an\n\t// OpenAI-compatible API at /v1 and ignores the bearer token, but the driver requires a\n\t// non-empty api_key, so we write a placeholder. The bare `init` (no preset flag) lands on\n\t// these defaults too, so the zero-flag path produces a config that actually starts.\n\tlet provider: \"openai-compatible\" | \"bedrock\" | \"cerebras\" | \"zai\" | \"opencode-go\" | \"umans\" =\n\t\t\"openai-compatible\";\n\tlet baseUrl = \"http://localhost:11434/v1\";\n\tlet apiKey: string | undefined = \"ollama\";\n\tlet region: string | undefined;\n\tlet model = \"llama3\";\n\t// umans is config-light + self-configuring: the namespace entry carries only\n\t// provider + id + api_key + default; the full model lineup (ids, context\n\t// windows, pricing, tiers) is fetched at runtime. We take a dedicated\n\t// config-write branch for it below.\n\tlet isUmans = false;\n\n\t// Determine configuration mode\n\tif (args.hub) {\n\t\t// Hub-only mode: no local inference. The node acts as a relay hub and\n\t\t// proxies LLM calls to spokes that have inference backends configured.\n\t\t// No provider/model selection needed.\n\t} else if (args.ollama) {\n\t\t// Ollama preset — same as the default; kept explicit so `--ollama` stays documented.\n\t\tprovider = \"openai-compatible\";\n\t\tbaseUrl = \"http://localhost:11434/v1\";\n\t\tapiKey = \"ollama\";\n\t\tmodel = \"llama3\";\n\t} else if (args.bedrock) {\n\t\t// Bedrock preset\n\t\tprovider = \"bedrock\";\n\t\tregion = args.region || \"us-east-1\";\n\t\tmodel = \"anthropic.claude-3-5-sonnet-20241022-v2:0\";\n\t\tapiKey = undefined; // override the default Ollama placeholder — Bedrock auths via AWS SigV4\n\t} else if (args.cerebras) {\n\t\t// Cerebras preset\n\t\tprovider = \"cerebras\";\n\t\tbaseUrl = \"https://api.cerebras.ai/v1\";\n\t\tmodel = \"llama-4-scout-17b-16e-instruct\";\n\t\tapiKey = process.env.CEREBRAS_API_KEY;\n\n\t\tif (!apiKey) {\n\t\t\tconsole.log(\"CEREBRAS_API_KEY not found in environment.\");\n\t\t}\n\t} else if (args.zai) {\n\t\t// z.AI preset (subscription-based, prices stay at 0)\n\t\tprovider = \"zai\";\n\t\tbaseUrl = \"https://api.z.ai/api/coding/paas/v4\";\n\t\tmodel = \"glm-4.7\";\n\t\tapiKey = process.env.ZAI_API_KEY;\n\n\t\tif (!apiKey) {\n\t\t\tconsole.log(\"ZAI_API_KEY not found in environment.\");\n\t\t}\n\t} else if (args.opencodeGo) {\n\t\t// OpenCode Go preset\n\t\tprovider = \"opencode-go\";\n\t\tbaseUrl = \"https://opencode.ai/zen/go/v1\";\n\t\tmodel = \"glm-5.1\";\n\t\tapiKey = process.env.OPENCODE_API_KEY;\n\n\t\tif (!apiKey) {\n\t\t\tconsole.log(\"OPENCODE_API_KEY not found in environment.\");\n\t\t}\n\t} else if (args.umans) {\n\t\t// umans preset — config-light + self-configuring. The full model lineup,\n\t\t// pricing, context windows, tiers, and concurrency limit are fetched at\n\t\t// runtime, so the config carries only provider + id + api_key + default.\n\t\tprovider = \"umans\";\n\t\tisUmans = true;\n\t\tapiKey = process.env.UMANS_API_KEY;\n\n\t\tif (!apiKey) {\n\t\t\tconsole.log(\"UMANS_API_KEY not found in environment.\");\n\t\t}\n\t}\n\n\t// Generate deterministic UUID for operator\n\tdeterministicUUID(BOUND_NAMESPACE, operatorName);\n\n\t// Create allowlist.json\n\tconst allowlistConfig = {\n\t\tdefault_web_user: operatorName,\n\t\tusers: {\n\t\t\t[operatorName]: {\n\t\t\t\tdisplay_name: operatorName,\n\t\t\t},\n\t\t},\n\t};\n\n\t// Create model_backends.json\n\tlet modelBackendsConfig: { backends: unknown[]; default: string };\n\tif (args.hub) {\n\t\t// Hub-only: no inference backends. Inference is relayed to spokes.\n\t\tmodelBackendsConfig = { backends: [], default: \"\" };\n\t} else if (isUmans) {\n\t\t// Config-light umans namespace. NO model/tier/context_window/base_url/\n\t\t// pricing — all fetched/derived at runtime. `default` MUST be the\n\t\t// namespace id (\"umans\"), not a concrete model id: a concrete id does\n\t\t// not exist at startup and would crash the default-existence check.\n\t\t// biome-ignore lint/suspicious/noExplicitAny: config is dynamic\n\t\tconst umansBackend: any = { id: \"umans\", provider: \"umans\" };\n\t\tif (apiKey) umansBackend.api_key = apiKey;\n\t\tmodelBackendsConfig = { backends: [umansBackend], default: \"umans\" };\n\t} else {\n\t\t// biome-ignore lint/suspicious/noExplicitAny: config is dynamic\n\t\tconst backendConfig: any = {\n\t\t\tid: provider,\n\t\t\tprovider,\n\t\t\tmodel,\n\t\t\tcontext_window: 8192,\n\t\t\ttier: 3,\n\t\t};\n\n\t\tif (baseUrl && provider !== \"bedrock\") {\n\t\t\tbackendConfig.base_url = baseUrl;\n\t\t}\n\n\t\tif (apiKey) {\n\t\t\tbackendConfig.api_key = apiKey;\n\t\t}\n\n\t\tif (region) {\n\t\t\tbackendConfig.region = region;\n\t\t}\n\n\t\tmodelBackendsConfig = {\n\t\t\tbackends: [backendConfig],\n\t\t\tdefault: provider,\n\t\t};\n\t}\n\n\t// Write config files\n\twriteFileSync(allowlistPath, `${JSON.stringify(allowlistConfig, null, 2)}\\n`);\n\twriteFileSync(modelBackendsPath, `${JSON.stringify(modelBackendsConfig, null, 2)}\\n`);\n\n\tif (args.hub) {\n\t\tconsole.log(`\nHub initialized successfully!\n\nCreated:\n - ${configDir}/allowlist.json\n - ${configDir}/model_backends.json (empty — hub relays inference to spokes)\n\nOperator: ${operatorName}\n\nNext steps:\n 1. Configure keyring.json with spoke public keys\n 2. Configure sync.json with this hub's URL (or leave blank — hub doesn't point to another hub)\n 3. Set BIND_HOST=0.0.0.0 so spokes can reach this host\n 4. Run: bound start\n 5. On each spoke: set sync.hub to this host's URL and add this hub to keyring.json\n`);\n\t} else {\n\t\tconsole.log(`\nConfig initialized successfully!\n\nCreated:\n - ${configDir}/allowlist.json\n - ${configDir}/model_backends.json\n\nOperator: ${operatorName}\nProvider: ${provider}\n${isUmans ? \"Model: (self-configuring — lineup fetched at runtime)\" : `Model: ${model}`}\n\nNext steps:\n 1. Review the config files\n 2. Run: bound start\n 3. Open http://localhost:3000 in your browser\n`);\n\t}\n\n\t// Create optional config templates if requested\n\tif (args.withSync) {\n\t\tconst syncPath = resolve(configDir, \"sync.json\");\n\t\tconst syncConfig = {\n\t\t\thub: \"primary-host\",\n\t\t};\n\t\twriteFileSync(syncPath, `${JSON.stringify(syncConfig, null, 2)}\\n`);\n\t\tconsole.log(` - ${configDir}/sync.json (template)`);\n\t}\n\n\tif (args.withMcp) {\n\t\tconst mcpPath = resolve(configDir, \"mcp.json\");\n\t\tconst mcpConfig = {\n\t\t\tservers: [],\n\t\t};\n\t\twriteFileSync(mcpPath, `${JSON.stringify(mcpConfig, null, 2)}\\n`);\n\t\tconsole.log(` - ${configDir}/mcp.json (template)`);\n\t}\n}\n"}}}} |