Files

1 line
31 KiB
JSON

{"repo": "0chencc/clawgod", "files": {"tools/provider-import/src/main.rs": {"507405a053c917c3a27c162e3f66c3d1897d4591": {"t": 1785746242.3834007, "n": 14415, "h": "cff01cf2e42f4435", "c": "use serde_json::{json, Value};\nuse std::env;\nuse std::fs;\nuse std::io::{self, Write};\nuse std::path::PathBuf;\nuse std::process;\n\nfn home_dir() -> PathBuf {\n env::var(\"HOME\")\n .or_else(|_| env::var(\"USERPROFILE\"))\n .map(PathBuf::from)\n .expect(\"Cannot determine home directory\")\n}\n\nfn clawgod_dir() -> PathBuf {\n home_dir().join(\".clawgod\")\n}\n\nfn provider_json_path() -> PathBuf {\n clawgod_dir().join(\"provider.json\")\n}\n\n// ─── Key discovery ───────────────────────────────────────────\n\nfn detect_grok_key() -> Option<String> {\n // 1. GROK_API_KEY env\n if let Ok(key) = env::var(\"GROK_API_KEY\") {\n if !key.is_empty() {\n eprintln!(\" Found key in GROK_API_KEY environment variable\");\n return Some(key);\n }\n }\n\n // 2. ~/.grok/user-settings.json\n let settings_path = home_dir().join(\".grok\").join(\"user-settings.json\");\n if let Ok(contents) = fs::read_to_string(&settings_path) {\n if let Ok(parsed) = serde_json::from_str::<Value>(&contents) {\n if let Some(key) = parsed[\"apiKey\"].as_str() {\n if !key.is_empty() {\n eprintln!(\" Found key in {}\", settings_path.display());\n return Some(key.to_string());\n }\n }\n }\n }\n\n // 3. XAI_API_KEY env (fallback)\n if let Ok(key) = env::var(\"XAI_API_KEY\") {\n if !key.is_empty() {\n eprintln!(\" Found key in XAI_API_KEY environment variable\");\n return Some(key);\n }\n }\n\n None\n}\n\n// ─── Key validation ──────────────────────────────────────────\n\nstruct ValidationResult {\n valid: bool,\n models: Vec<String>,\n error: Option<String>,\n}\n\nfn validate_key(key: &str, base_url: &str) -> ValidationResult {\n let url = format!(\"{}/models\", base_url.trim_end_matches('/'));\n\n match ureq::get(&url)\n .set(\"Authorization\", &format!(\"Bearer {}\", key))\n .set(\"Content-Type\", \"application/json\")\n .call()\n {\n Ok(resp) => {\n let body: Value = resp.into_json().unwrap_or(json!({}));\n let models: Vec<String> = body[\"data\"]\n .as_array()\n .map(|arr| {\n arr.iter()\n .filter_map(|m| m[\"id\"].as_str().map(String::from))\n .collect()\n })\n .unwrap_or_default();\n\n ValidationResult {\n valid: true,\n models,\n error: None,\n }\n }\n Err(ureq::Error::Status(401, _)) => ValidationResult {\n valid: false,\n models: vec![],\n error: Some(\"Invalid API key (401 Unauthorized)\".into()),\n },\n Err(ureq::Error::Status(code, resp)) => {\n let body = resp.into_string().unwrap_or_default();\n ValidationResult {\n valid: false,\n models: vec![],\n error: Some(format!(\"HTTP {} — {}\", code, body)),\n }\n }\n Err(e) => ValidationResult {\n valid: false,\n models: vec![],\n error: Some(format!(\"Connection failed: {}\", e)),\n },\n }\n}\n\n// ─── Provider config ─────────────────────────────────────────\n\nfn write_provider(provider_type: &str, key: &str, base_url: &str, model: &str, small_model: &str) {\n let config = json!({\n \"type\": provider_type,\n \"apiKey\": key,\n \"baseURL\": base_url,\n \"model\": model,\n \"smallModel\": small_model,\n \"timeoutMs\": 3000000\n });\n\n let path = provider_json_path();\n if let Some(parent) = path.parent() {\n let _ = fs::create_dir_all(parent);\n }\n\n let content = serde_json::to_string_pretty(&config).unwrap() + \"\\n\";\n fs::write(&path, &content).unwrap_or_else(|e| {\n eprintln!(\" Failed to write {}: {}\", path.display(), e);\n process::exit(1);\n });\n\n eprintln!(\" Wrote {}\", path.display());\n}\n\nfn read_current_provider() -> Option<Value> {\n let path = provider_json_path();\n fs::read_to_string(&path)\n .ok()\n .and_then(|s| serde_json::from_str(&s).ok())\n}\n\n// ─── Interactive model selection ─────────────────────────────\n\nfn select_model(models: &[String], default: &str) -> String {\n if models.is_empty() {\n return default.to_string();\n }\n\n eprintln!(\"\\n Available models:\");\n for (i, m) in models.iter().enumerate() {\n let marker = if m == default { \" (default)\" } else { \"\" };\n eprintln!(\" [{}] {}{}\", i + 1, m, marker);\n }\n\n eprint!(\"\\n Select model [default: {}]: \", default);\n io::stderr().flush().unwrap();\n\n let mut input = String::new();\n io::stdin().read_line(&mut input).unwrap();\n let input = input.trim();\n\n if input.is_empty() {\n return default.to_string();\n }\n\n if let Ok(idx) = input.parse::<usize>() {\n if idx >= 1 && idx <= models.len() {\n return models[idx - 1].clone();\n }\n }\n\n // Treat as literal model name\n input.to_string()\n}\n\nfn prompt_key() -> String {\n eprint!(\" Enter API key: \");\n io::stderr().flush().unwrap();\n let mut input = String::new();\n io::stdin().read_line(&mut input).unwrap();\n input.trim().to_string()\n}\n\n// ─── Subcommands ─────────────────────────────────────────────\n\nfn cmd_grok(args: &[String]) {\n eprintln!(\"\\n ── Grok / xAI Import ──\\n\");\n\n let mut key = None;\n let mut model_override = None;\n let mut small_model_override = None;\n let mut base_url = \"https://api.x.ai/v1\".to_string();\n\n let mut i = 0;\n while i < args.len() {\n match args[i].as_str() {\n \"--key\" | \"-k\" => {\n i += 1;\n key = args.get(i).cloned();\n }\n \"--model\" | \"-m\" => {\n i += 1;\n model_override = args.get(i).cloned();\n }\n \"--small-model\" => {\n i += 1;\n small_model_override = args.get(i).cloned();\n }\n \"--base-url\" => {\n i += 1;\n if let Some(u) = args.get(i) {\n base_url = u.clone();\n }\n }\n _ => {}\n }\n i += 1;\n }\n\n // Detect key\n let api_key = match key {\n Some(k) => {\n eprintln!(\" Using provided key\");\n k\n }\n None => {\n eprintln!(\" Searching for grok-cli credentials...\");\n match detect_grok_key() {\n Some(k) => k,\n None => {\n eprintln!(\" No grok-cli key found.\");\n let k = prompt_key();\n if k.is_empty() {\n eprintln!(\" Aborted.\");\n process::exit(1);\n }\n k\n }\n }\n }\n };\n\n // Validate\n eprintln!(\"\\n Verifying key with {}...\", base_url);\n let result = validate_key(&api_key, &base_url);\n\n if !result.valid {\n eprintln!(\" FAILED: {}\", result.error.unwrap_or_default());\n process::exit(1);\n }\n\n eprintln!(\" Key valid.\");\n\n // Model selection\n let default_model = \"grok-4\";\n let default_small = \"grok-3-mini\";\n\n let model = match model_override {\n Some(m) => m,\n None => select_model(&result.models, default_model),\n };\n\n let small_model = match small_model_override {\n Some(m) => m,\n None => {\n let candidates: Vec<&str> = vec![\"grok-3-mini\", \"grok-3-mini-fast\"];\n let auto = result\n .models\n .iter()\n .find(|m| candidates.contains(&m.as_str()))\n .map(|s| s.as_str())\n .unwrap_or(default_small);\n auto.to_string()\n }\n };\n\n // Write\n eprintln!(\"\\n model: {}\", model);\n eprintln!(\" smallModel: {}\", small_model);\n write_provider(\"grok\", &api_key, &base_url, &model, &small_model);\n eprintln!(\"\\n Done. Run `claude` to start.\\n\");\n}\n\nfn cmd_openai_compat(args: &[String]) {\n eprintln!(\"\\n ── OpenAI-Compatible Import ──\\n\");\n\n let mut key = None;\n let mut base_url = None;\n let mut model = None;\n let mut small_model = None;\n\n let mut i = 0;\n while i < args.len() {\n match args[i].as_str() {\n \"--key\" | \"-k\" => {\n i += 1;\n key = args.get(i).cloned();\n }\n \"--base-url\" | \"-u\" => {\n i += 1;\n base_url = args.get(i).cloned();\n }\n \"--model\" | \"-m\" => {\n i += 1;\n model = args.get(i).cloned();\n }\n \"--small-model\" => {\n i += 1;\n small_model = args.get(i).cloned();\n }\n _ => {}\n }\n i += 1;\n }\n\n let api_key = key.unwrap_or_else(|| {\n let k = prompt_key();\n if k.is_empty() {\n eprintln!(\" Aborted.\");\n process::exit(1);\n }\n k\n });\n\n let url = base_url.unwrap_or_else(|| {\n eprint!(\" Base URL: \");\n io::stderr().flush().unwrap();\n let mut input = String::new();\n io::stdin().read_line(&mut input).unwrap();\n let v = input.trim().to_string();\n if v.is_empty() {\n eprintln!(\" Aborted.\");\n process::exit(1);\n }\n v\n });\n\n // Validate\n eprintln!(\"\\n Verifying key with {}...\", url);\n let result = validate_key(&api_key, &url);\n\n if !result.valid {\n eprintln!(\" WARNING: {}\", result.error.unwrap_or_default());\n eprintln!(\" Continuing anyway (some APIs don't expose /models)...\\n\");\n } else {\n eprintln!(\" Key valid.\");\n }\n\n let selected_model = model.unwrap_or_else(|| {\n if result.valid && !result.models.is_empty() {\n select_model(&result.models, &result.models[0])\n } else {\n eprint!(\" Model name: \");\n io::stderr().flush().unwrap();\n let mut input = String::new();\n io::stdin().read_line(&mut input).unwrap();\n input.trim().to_string()\n }\n });\n\n let selected_small = small_model.unwrap_or_else(|| selected_model.clone());\n\n eprintln!(\"\\n model: {}\", selected_model);\n eprintln!(\" smallModel: {}\", selected_small);\n write_provider(\"openai-compat\", &api_key, &url, &selected_model, &selected_small);\n eprintln!(\"\\n Done. Run `claude` to start.\\n\");\n}\n\nfn cmd_status() {\n eprintln!(\"\\n ── Provider Status ──\\n\");\n\n match read_current_provider() {\n Some(config) => {\n let ptype = config[\"type\"].as_str().unwrap_or(\"anthropic\");\n let model = config[\"model\"].as_str().unwrap_or(\"(default)\");\n let small = config[\"smallModel\"].as_str().unwrap_or(\"(default)\");\n let url = config[\"baseURL\"].as_str().unwrap_or(\"https://api.anthropic.com\");\n let has_key = config[\"apiKey\"].as_str().map(|k| !k.is_empty()).unwrap_or(false);\n\n eprintln!(\" type: {}\", ptype);\n eprintln!(\" model: {}\", model);\n eprintln!(\" smallModel: {}\", small);\n eprintln!(\" baseURL: {}\", url);\n eprintln!(\" apiKey: {}\", if has_key { \"(set)\" } else { \"(empty — using OAuth)\" });\n\n if has_key && (ptype == \"grok\" || ptype == \"openai-compat\") {\n let key = config[\"apiKey\"].as_str().unwrap();\n eprintln!(\"\\n Verifying...\");\n let result = validate_key(key, url);\n if result.valid {\n eprintln!(\" Key valid. {} model(s) available.\", result.models.len());\n } else {\n eprintln!(\" Key INVALID: {}\", result.error.unwrap_or_default());\n }\n }\n }\n None => {\n eprintln!(\" No provider.json found at {}\", provider_json_path().display());\n eprintln!(\" Using default (Anthropic OAuth).\");\n }\n }\n eprintln!();\n}\n\nfn cmd_reset() {\n let default_config = json!({\n \"apiKey\": \"\",\n \"baseURL\": \"https://api.anthropic.com\",\n \"model\": \"\",\n \"smallModel\": \"\",\n \"timeoutMs\": 3000000\n });\n\n let path = provider_json_path();\n let content = serde_json::to_string_pretty(&default_config).unwrap() + \"\\n\";\n fs::write(&path, &content).unwrap_or_else(|e| {\n eprintln!(\" Failed to write {}: {}\", path.display(), e);\n process::exit(1);\n });\n eprintln!(\"\\n Reset provider.json to default (Anthropic).\\n\");\n}\n\n// ─── Main ────────────────────────────────────────────────────\n\nfn print_usage() {\n eprintln!(\n r#\"\n clawgod-import — Import third-party API providers into clawgod\n\n Usage:\n clawgod-import grok [options] Import from grok-cli / xAI\n clawgod-import openai-compat [options] Import any OpenAI-compatible API\n clawgod-import status Show current provider config\n clawgod-import reset Reset to default (Anthropic)\n\n Options (grok):\n -k, --key <KEY> API key (auto-detects from ~/.grok/ if omitted)\n -m, --model <MODEL> Model name (default: grok-4)\n --small-model <MODEL> Small/fast model (default: grok-3-mini)\n --base-url <URL> API base URL (default: https://api.x.ai/v1)\n\n Options (openai-compat):\n -k, --key <KEY> API key\n -u, --base-url <URL> API base URL (required)\n -m, --model <MODEL> Model name\n --small-model <MODEL> Small/fast model\n\n Examples:\n clawgod-import grok # auto-detect grok-cli key\n clawgod-import grok -k xai-abc123 # manual key\n clawgod-import openai-compat -k sk-xxx \\\n -u https://api.deepseek.com/v1 -m deepseek-chat\n\"#\n );\n}\n\nfn main() {\n let args: Vec<String> = env::args().collect();\n\n match args.get(1).map(|s| s.as_str()) {\n Some(\"grok\") => cmd_grok(&args[2..]),\n Some(\"openai-compat\") | Some(\"openai\") => cmd_openai_compat(&args[2..]),\n Some(\"status\") => cmd_status(),\n Some(\"reset\") => cmd_reset(),\n Some(\"--help\") | Some(\"-h\") | Some(\"help\") => print_usage(),\n _ => {\n print_usage();\n if args.len() > 1 {\n process::exit(1);\n }\n }\n }\n}\n"}, "bbf2eca462e4016f339832d8828a918f83b796f7": {"t": 1785760978.7206767, "n": 14415, "h": "cff01cf2e42f4435", "c": "use serde_json::{json, Value};\nuse std::env;\nuse std::fs;\nuse std::io::{self, Write};\nuse std::path::PathBuf;\nuse std::process;\n\nfn home_dir() -> PathBuf {\n env::var(\"HOME\")\n .or_else(|_| env::var(\"USERPROFILE\"))\n .map(PathBuf::from)\n .expect(\"Cannot determine home directory\")\n}\n\nfn clawgod_dir() -> PathBuf {\n home_dir().join(\".clawgod\")\n}\n\nfn provider_json_path() -> PathBuf {\n clawgod_dir().join(\"provider.json\")\n}\n\n// ─── Key discovery ───────────────────────────────────────────\n\nfn detect_grok_key() -> Option<String> {\n // 1. GROK_API_KEY env\n if let Ok(key) = env::var(\"GROK_API_KEY\") {\n if !key.is_empty() {\n eprintln!(\" Found key in GROK_API_KEY environment variable\");\n return Some(key);\n }\n }\n\n // 2. ~/.grok/user-settings.json\n let settings_path = home_dir().join(\".grok\").join(\"user-settings.json\");\n if let Ok(contents) = fs::read_to_string(&settings_path) {\n if let Ok(parsed) = serde_json::from_str::<Value>(&contents) {\n if let Some(key) = parsed[\"apiKey\"].as_str() {\n if !key.is_empty() {\n eprintln!(\" Found key in {}\", settings_path.display());\n return Some(key.to_string());\n }\n }\n }\n }\n\n // 3. XAI_API_KEY env (fallback)\n if let Ok(key) = env::var(\"XAI_API_KEY\") {\n if !key.is_empty() {\n eprintln!(\" Found key in XAI_API_KEY environment variable\");\n return Some(key);\n }\n }\n\n None\n}\n\n// ─── Key validation ──────────────────────────────────────────\n\nstruct ValidationResult {\n valid: bool,\n models: Vec<String>,\n error: Option<String>,\n}\n\nfn validate_key(key: &str, base_url: &str) -> ValidationResult {\n let url = format!(\"{}/models\", base_url.trim_end_matches('/'));\n\n match ureq::get(&url)\n .set(\"Authorization\", &format!(\"Bearer {}\", key))\n .set(\"Content-Type\", \"application/json\")\n .call()\n {\n Ok(resp) => {\n let body: Value = resp.into_json().unwrap_or(json!({}));\n let models: Vec<String> = body[\"data\"]\n .as_array()\n .map(|arr| {\n arr.iter()\n .filter_map(|m| m[\"id\"].as_str().map(String::from))\n .collect()\n })\n .unwrap_or_default();\n\n ValidationResult {\n valid: true,\n models,\n error: None,\n }\n }\n Err(ureq::Error::Status(401, _)) => ValidationResult {\n valid: false,\n models: vec![],\n error: Some(\"Invalid API key (401 Unauthorized)\".into()),\n },\n Err(ureq::Error::Status(code, resp)) => {\n let body = resp.into_string().unwrap_or_default();\n ValidationResult {\n valid: false,\n models: vec![],\n error: Some(format!(\"HTTP {} — {}\", code, body)),\n }\n }\n Err(e) => ValidationResult {\n valid: false,\n models: vec![],\n error: Some(format!(\"Connection failed: {}\", e)),\n },\n }\n}\n\n// ─── Provider config ─────────────────────────────────────────\n\nfn write_provider(provider_type: &str, key: &str, base_url: &str, model: &str, small_model: &str) {\n let config = json!({\n \"type\": provider_type,\n \"apiKey\": key,\n \"baseURL\": base_url,\n \"model\": model,\n \"smallModel\": small_model,\n \"timeoutMs\": 3000000\n });\n\n let path = provider_json_path();\n if let Some(parent) = path.parent() {\n let _ = fs::create_dir_all(parent);\n }\n\n let content = serde_json::to_string_pretty(&config).unwrap() + \"\\n\";\n fs::write(&path, &content).unwrap_or_else(|e| {\n eprintln!(\" Failed to write {}: {}\", path.display(), e);\n process::exit(1);\n });\n\n eprintln!(\" Wrote {}\", path.display());\n}\n\nfn read_current_provider() -> Option<Value> {\n let path = provider_json_path();\n fs::read_to_string(&path)\n .ok()\n .and_then(|s| serde_json::from_str(&s).ok())\n}\n\n// ─── Interactive model selection ─────────────────────────────\n\nfn select_model(models: &[String], default: &str) -> String {\n if models.is_empty() {\n return default.to_string();\n }\n\n eprintln!(\"\\n Available models:\");\n for (i, m) in models.iter().enumerate() {\n let marker = if m == default { \" (default)\" } else { \"\" };\n eprintln!(\" [{}] {}{}\", i + 1, m, marker);\n }\n\n eprint!(\"\\n Select model [default: {}]: \", default);\n io::stderr().flush().unwrap();\n\n let mut input = String::new();\n io::stdin().read_line(&mut input).unwrap();\n let input = input.trim();\n\n if input.is_empty() {\n return default.to_string();\n }\n\n if let Ok(idx) = input.parse::<usize>() {\n if idx >= 1 && idx <= models.len() {\n return models[idx - 1].clone();\n }\n }\n\n // Treat as literal model name\n input.to_string()\n}\n\nfn prompt_key() -> String {\n eprint!(\" Enter API key: \");\n io::stderr().flush().unwrap();\n let mut input = String::new();\n io::stdin().read_line(&mut input).unwrap();\n input.trim().to_string()\n}\n\n// ─── Subcommands ─────────────────────────────────────────────\n\nfn cmd_grok(args: &[String]) {\n eprintln!(\"\\n ── Grok / xAI Import ──\\n\");\n\n let mut key = None;\n let mut model_override = None;\n let mut small_model_override = None;\n let mut base_url = \"https://api.x.ai/v1\".to_string();\n\n let mut i = 0;\n while i < args.len() {\n match args[i].as_str() {\n \"--key\" | \"-k\" => {\n i += 1;\n key = args.get(i).cloned();\n }\n \"--model\" | \"-m\" => {\n i += 1;\n model_override = args.get(i).cloned();\n }\n \"--small-model\" => {\n i += 1;\n small_model_override = args.get(i).cloned();\n }\n \"--base-url\" => {\n i += 1;\n if let Some(u) = args.get(i) {\n base_url = u.clone();\n }\n }\n _ => {}\n }\n i += 1;\n }\n\n // Detect key\n let api_key = match key {\n Some(k) => {\n eprintln!(\" Using provided key\");\n k\n }\n None => {\n eprintln!(\" Searching for grok-cli credentials...\");\n match detect_grok_key() {\n Some(k) => k,\n None => {\n eprintln!(\" No grok-cli key found.\");\n let k = prompt_key();\n if k.is_empty() {\n eprintln!(\" Aborted.\");\n process::exit(1);\n }\n k\n }\n }\n }\n };\n\n // Validate\n eprintln!(\"\\n Verifying key with {}...\", base_url);\n let result = validate_key(&api_key, &base_url);\n\n if !result.valid {\n eprintln!(\" FAILED: {}\", result.error.unwrap_or_default());\n process::exit(1);\n }\n\n eprintln!(\" Key valid.\");\n\n // Model selection\n let default_model = \"grok-4\";\n let default_small = \"grok-3-mini\";\n\n let model = match model_override {\n Some(m) => m,\n None => select_model(&result.models, default_model),\n };\n\n let small_model = match small_model_override {\n Some(m) => m,\n None => {\n let candidates: Vec<&str> = vec![\"grok-3-mini\", \"grok-3-mini-fast\"];\n let auto = result\n .models\n .iter()\n .find(|m| candidates.contains(&m.as_str()))\n .map(|s| s.as_str())\n .unwrap_or(default_small);\n auto.to_string()\n }\n };\n\n // Write\n eprintln!(\"\\n model: {}\", model);\n eprintln!(\" smallModel: {}\", small_model);\n write_provider(\"grok\", &api_key, &base_url, &model, &small_model);\n eprintln!(\"\\n Done. Run `claude` to start.\\n\");\n}\n\nfn cmd_openai_compat(args: &[String]) {\n eprintln!(\"\\n ── OpenAI-Compatible Import ──\\n\");\n\n let mut key = None;\n let mut base_url = None;\n let mut model = None;\n let mut small_model = None;\n\n let mut i = 0;\n while i < args.len() {\n match args[i].as_str() {\n \"--key\" | \"-k\" => {\n i += 1;\n key = args.get(i).cloned();\n }\n \"--base-url\" | \"-u\" => {\n i += 1;\n base_url = args.get(i).cloned();\n }\n \"--model\" | \"-m\" => {\n i += 1;\n model = args.get(i).cloned();\n }\n \"--small-model\" => {\n i += 1;\n small_model = args.get(i).cloned();\n }\n _ => {}\n }\n i += 1;\n }\n\n let api_key = key.unwrap_or_else(|| {\n let k = prompt_key();\n if k.is_empty() {\n eprintln!(\" Aborted.\");\n process::exit(1);\n }\n k\n });\n\n let url = base_url.unwrap_or_else(|| {\n eprint!(\" Base URL: \");\n io::stderr().flush().unwrap();\n let mut input = String::new();\n io::stdin().read_line(&mut input).unwrap();\n let v = input.trim().to_string();\n if v.is_empty() {\n eprintln!(\" Aborted.\");\n process::exit(1);\n }\n v\n });\n\n // Validate\n eprintln!(\"\\n Verifying key with {}...\", url);\n let result = validate_key(&api_key, &url);\n\n if !result.valid {\n eprintln!(\" WARNING: {}\", result.error.unwrap_or_default());\n eprintln!(\" Continuing anyway (some APIs don't expose /models)...\\n\");\n } else {\n eprintln!(\" Key valid.\");\n }\n\n let selected_model = model.unwrap_or_else(|| {\n if result.valid && !result.models.is_empty() {\n select_model(&result.models, &result.models[0])\n } else {\n eprint!(\" Model name: \");\n io::stderr().flush().unwrap();\n let mut input = String::new();\n io::stdin().read_line(&mut input).unwrap();\n input.trim().to_string()\n }\n });\n\n let selected_small = small_model.unwrap_or_else(|| selected_model.clone());\n\n eprintln!(\"\\n model: {}\", selected_model);\n eprintln!(\" smallModel: {}\", selected_small);\n write_provider(\"openai-compat\", &api_key, &url, &selected_model, &selected_small);\n eprintln!(\"\\n Done. Run `claude` to start.\\n\");\n}\n\nfn cmd_status() {\n eprintln!(\"\\n ── Provider Status ──\\n\");\n\n match read_current_provider() {\n Some(config) => {\n let ptype = config[\"type\"].as_str().unwrap_or(\"anthropic\");\n let model = config[\"model\"].as_str().unwrap_or(\"(default)\");\n let small = config[\"smallModel\"].as_str().unwrap_or(\"(default)\");\n let url = config[\"baseURL\"].as_str().unwrap_or(\"https://api.anthropic.com\");\n let has_key = config[\"apiKey\"].as_str().map(|k| !k.is_empty()).unwrap_or(false);\n\n eprintln!(\" type: {}\", ptype);\n eprintln!(\" model: {}\", model);\n eprintln!(\" smallModel: {}\", small);\n eprintln!(\" baseURL: {}\", url);\n eprintln!(\" apiKey: {}\", if has_key { \"(set)\" } else { \"(empty — using OAuth)\" });\n\n if has_key && (ptype == \"grok\" || ptype == \"openai-compat\") {\n let key = config[\"apiKey\"].as_str().unwrap();\n eprintln!(\"\\n Verifying...\");\n let result = validate_key(key, url);\n if result.valid {\n eprintln!(\" Key valid. {} model(s) available.\", result.models.len());\n } else {\n eprintln!(\" Key INVALID: {}\", result.error.unwrap_or_default());\n }\n }\n }\n None => {\n eprintln!(\" No provider.json found at {}\", provider_json_path().display());\n eprintln!(\" Using default (Anthropic OAuth).\");\n }\n }\n eprintln!();\n}\n\nfn cmd_reset() {\n let default_config = json!({\n \"apiKey\": \"\",\n \"baseURL\": \"https://api.anthropic.com\",\n \"model\": \"\",\n \"smallModel\": \"\",\n \"timeoutMs\": 3000000\n });\n\n let path = provider_json_path();\n let content = serde_json::to_string_pretty(&default_config).unwrap() + \"\\n\";\n fs::write(&path, &content).unwrap_or_else(|e| {\n eprintln!(\" Failed to write {}: {}\", path.display(), e);\n process::exit(1);\n });\n eprintln!(\"\\n Reset provider.json to default (Anthropic).\\n\");\n}\n\n// ─── Main ────────────────────────────────────────────────────\n\nfn print_usage() {\n eprintln!(\n r#\"\n clawgod-import — Import third-party API providers into clawgod\n\n Usage:\n clawgod-import grok [options] Import from grok-cli / xAI\n clawgod-import openai-compat [options] Import any OpenAI-compatible API\n clawgod-import status Show current provider config\n clawgod-import reset Reset to default (Anthropic)\n\n Options (grok):\n -k, --key <KEY> API key (auto-detects from ~/.grok/ if omitted)\n -m, --model <MODEL> Model name (default: grok-4)\n --small-model <MODEL> Small/fast model (default: grok-3-mini)\n --base-url <URL> API base URL (default: https://api.x.ai/v1)\n\n Options (openai-compat):\n -k, --key <KEY> API key\n -u, --base-url <URL> API base URL (required)\n -m, --model <MODEL> Model name\n --small-model <MODEL> Small/fast model\n\n Examples:\n clawgod-import grok # auto-detect grok-cli key\n clawgod-import grok -k xai-abc123 # manual key\n clawgod-import openai-compat -k sk-xxx \\\n -u https://api.deepseek.com/v1 -m deepseek-chat\n\"#\n );\n}\n\nfn main() {\n let args: Vec<String> = env::args().collect();\n\n match args.get(1).map(|s| s.as_str()) {\n Some(\"grok\") => cmd_grok(&args[2..]),\n Some(\"openai-compat\") | Some(\"openai\") => cmd_openai_compat(&args[2..]),\n Some(\"status\") => cmd_status(),\n Some(\"reset\") => cmd_reset(),\n Some(\"--help\") | Some(\"-h\") | Some(\"help\") => print_usage(),\n _ => {\n print_usage();\n if args.len() > 1 {\n process::exit(1);\n }\n }\n }\n}\n"}}}}