{"repo": "adriel007/matrioska", "files": {"src/matrioska/cli/init_wizard.py": {"7b5683c5cd36cb1e791c80e4eff8c1bd266d92ba": {"t": 1785674548.011795, "n": 9943, "h": "8195412eb2da4e0d", "c": "\"\"\"Interactive `.env` scaffolding for `matrioska init`.\n\nPrompts the user for provider, model, and API key, detects existing\n`.env`, and writes a Matrioska-ready environment file. Optionally\nseeds `MATRIOSKA.md` with project conventions.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport sys\nfrom pathlib import Path\nfrom typing import Dict, List, Optional, Tuple\n\n# Provider catalog: (id, display name, default base_url, suggested model, key_hint)\n_PROVIDERS: List[Tuple[str, str, str, str, str]] = [\n (\"openai\", \"OpenAI\", \"https://api.openai.com/v1\", \"gpt-4o-mini\", \"sk-...\"),\n (\"anthropic\", \"Anthropic\", \"https://api.anthropic.com\", \"claude-sonnet-4\", \"sk-ant-...\"),\n (\"groq\", \"Groq\", \"https://api.groq.com/openai/v1\", \"llama-3.3-70b-versatile\", \"gsk_...\"),\n (\"openrouter\", \"OpenRouter\", \"https://openrouter.ai/api/v1\", \"anthropic/claude-sonnet-4\", \"sk-or-...\"),\n (\"deepseek\", \"DeepSeek\", \"https://api.deepseek.com/v1\", \"deepseek-chat\", \"ds_...\"),\n (\"xai\", \"xAI (Grok)\", \"https://api.x.ai/v1\", \"grok-2-latest\", \"xai-...\"),\n (\"mistral\", \"Mistral\", \"https://api.mistral.ai/v1\", \"mistral-large-latest\", \"...\"),\n (\"together\", \"Together\", \"https://api.together.xyz/v1\", \"Qwen/Qwen2.5-Coder-32B-Instruct\", \"...\"),\n (\"nvidia\", \"NVIDIA NIM\", \"https://integrate.api.nvidia.com/v1\", \"meta/llama-3.1-70b-instruct\", \"nvapi-...\"),\n (\"ollama\", \"Ollama (local)\", \"http://localhost:11434/v1\", \"llama3.1:8b\", \"ollama\"),\n]\n\n\ndef _ask(prompt: str, default: str = \"\") -> str:\n \"\"\"Prompt the user, returning the input (or default if blank).\"\"\"\n suffix = f\" [{default}]\" if default else \"\"\n try:\n ans = input(f\"{prompt}{suffix}: \").strip()\n except EOFError:\n return default\n return ans or default\n\n\ndef _ask_choice(prompt: str, choices: List[str], default_idx: int = 0) -> int:\n \"\"\"Numeric multiple-choice prompt. Returns the chosen index.\"\"\"\n print(prompt)\n for i, label in enumerate(choices, 1):\n marker = \"*\" if i - 1 == default_idx else \" \"\n print(f\" {marker} {i}) {label}\")\n while True:\n raw = _ask(\"Choose\", default=str(default_idx + 1))\n if not raw.isdigit():\n print(\" → please enter a number\")\n continue\n n = int(raw)\n if 1 <= n <= len(choices):\n return n - 1\n print(f\" → out of range (1–{len(choices)})\")\n\n\ndef _ask_yes_no(prompt: str, default: bool = True) -> bool:\n suffix = \"Y/n\" if default else \"y/N\"\n raw = _ask(f\"{prompt} ({suffix})\", default=\"Y\" if default else \"N\").lower()\n return raw.startswith(\"y\") if raw else default\n\n\ndef _read_existing(env_path: Path) -> Dict[str, str]:\n out: Dict[str, str] = {}\n if not env_path.exists():\n return out\n for raw in env_path.read_text(encoding=\"utf-8\", errors=\"ignore\").splitlines():\n line = raw.strip()\n if not line or line.startswith(\"#\") or \"=\" not in line:\n continue\n k, _, v = line.partition(\"=\")\n out[k.strip()] = v.strip().strip('\"').strip(\"'\")\n return out\n\n\ndef _render_env(values: Dict[str, str]) -> str:\n \"\"\"Render the .env file with grouped sections + comments.\"\"\"\n sections = [\n (\"Provider\", [\"MATRIOSKA_PROVIDER\", \"MATRIOSKA_BASE_URL\",\n \"MATRIOSKA_API_KEY\", \"MATRIOSKA_API_KEYS\", \"MATRIOSKA_EXTRA_ENDPOINTS\"]),\n (\"Models\", [\"MATRIOSKA_MODEL\", \"MATRIOSKA_ARCHITECT_MODEL\",\n \"MATRIOSKA_GENERATOR_MODEL\", \"MATRIOSKA_VALIDATOR_MODEL\",\n \"MATRIOSKA_JUDGE_MODEL\", \"MATRIOSKA_REPAIRER_MODEL\"]),\n (\"Pipeline\", [\"MATRIOSKA_MAX_REPAIRS\", \"MATRIOSKA_MAX_DEPTH\",\n \"MATRIOSKA_PARALLEL\", \"MATRIOSKA_ARCHITECT_CANDIDATES\"]),\n (\"Phases\", [\"MATRIOSKA_ENABLE_TOT\", \"MATRIOSKA_ENABLE_REFLEXION\",\n \"MATRIOSKA_ENABLE_TEST_DESIGN\", \"MATRIOSKA_USE_ACI_REPAIR\",\n \"MATRIOSKA_ENABLE_SANDBOX\"]),\n (\"Vault & misc\", [\"MATRIOSKA_ENABLE_VAULT\", \"MATRIOSKA_VAULT_DIR\",\n \"MATRIOSKA_LOG_LEVEL\"]),\n ]\n lines = [\n \"# Generated by `matrioska init`\",\n \"# Edit freely — every key can also be overridden via CLI flag.\",\n \"\",\n ]\n written = set()\n for title, keys in sections:\n block: List[str] = []\n for k in keys:\n if k in values and values[k] != \"\":\n block.append(f'{k}={values[k]}')\n written.add(k)\n if block:\n lines.append(f\"# ── {title} ─────────────────────────────────────────\")\n lines.extend(block)\n lines.append(\"\")\n # Spill remaining keys\n leftovers = [k for k in values if k not in written and values[k] != \"\"]\n if leftovers:\n lines.append(\"# ── Other ──────────────────────────────────────────────\")\n for k in leftovers:\n lines.append(f'{k}={values[k]}')\n lines.append(\"\")\n return \"\\n\".join(lines).rstrip() + \"\\n\"\n\n\n_MATRIOSKA_MD_TEMPLATE = \"\"\"# {project_name}\n\n## Project conventions\n\nNotes injected into the Architect on every run. Edit freely.\n\n### Stack\n- Language: Python 3.11+\n- Frameworks: \n- Storage: \n\n### Style\n- Prefer small, focused modules\n- Avoid unnecessary abstractions until 3rd repetition\n- Type hints on public functions; docstrings only when the why is non-obvious\n\n### Don'ts\n- No global mutable state\n- No silent exception swallowing\n\n### Notes for the agent\n- \n\"\"\"\n\n\ndef run_init(cwd: Optional[Path] = None) -> int:\n \"\"\"Interactive `.env` scaffolder. Returns process exit code.\"\"\"\n cwd = cwd or Path.cwd()\n env_path = cwd / \".env\"\n\n print(\"─\" * 60)\n print(\" Matrioska init\")\n print(\"─\" * 60)\n print(f\" Target: {env_path}\")\n print()\n\n existing = _read_existing(env_path)\n if existing:\n print(f\"Found existing .env with {len(existing)} key(s).\")\n action = _ask_choice(\n \"What should I do?\",\n [\"Merge (keep existing values, fill missing)\",\n \"Overwrite (replace with new values)\",\n \"Abort\"],\n default_idx=0,\n )\n if action == 2:\n print(\"Aborted.\")\n return 0\n merge = action == 0\n else:\n merge = False\n\n # Provider selection\n labels = [f\"{disp} — model: {model}\" for _id, disp, _url, model, _hint in _PROVIDERS]\n current_provider = existing.get(\"MATRIOSKA_PROVIDER\", \"\")\n default_idx = next(\n (i for i, (pid, *_r) in enumerate(_PROVIDERS) if pid == current_provider),\n 0,\n )\n idx = _ask_choice(\"\\nPick a provider:\", labels, default_idx=default_idx)\n pid, _disp, default_url, default_model, key_hint = _PROVIDERS[idx]\n\n base_url = _ask(\n \"Base URL\",\n default=existing.get(\"MATRIOSKA_BASE_URL\") or default_url,\n )\n\n api_key = \"\"\n if pid != \"ollama\":\n api_key_default = existing.get(\"MATRIOSKA_API_KEY\", \"\")\n prompt_label = \"API key\" + (f\" (hint: {key_hint})\" if not api_key_default else \" (press Enter to keep current)\")\n api_key = _ask(prompt_label, default=api_key_default)\n else:\n api_key = \"ollama\"\n\n model = _ask(\"Default model\", default=existing.get(\"MATRIOSKA_MODEL\") or default_model)\n\n use_role_models = _ask_yes_no(\"\\nSet per-role model overrides? (architect/generator/validator/judge)\", default=False)\n arch_model = gen_model = val_model = judge_model = rep_model = \"\"\n if use_role_models:\n arch_model = _ask(\"Architect model\", default=existing.get(\"MATRIOSKA_ARCHITECT_MODEL\") or model)\n gen_model = _ask(\"Generator model\", default=existing.get(\"MATRIOSKA_GENERATOR_MODEL\") or model)\n val_model = _ask(\"Validator model\", default=existing.get(\"MATRIOSKA_VALIDATOR_MODEL\") or model)\n judge_model = _ask(\"Judge model\", default=existing.get(\"MATRIOSKA_JUDGE_MODEL\") or model)\n rep_model = _ask(\"Repairer model\", default=existing.get(\"MATRIOSKA_REPAIRER_MODEL\") or model)\n\n multikey = \"\"\n if _ask_yes_no(\"\\nUse multiple API keys for rotation? (comma-separated)\", default=False):\n multikey = _ask(\"Comma-separated API keys\", default=existing.get(\"MATRIOSKA_API_KEYS\", \"\"))\n\n # Build values dict (merge-respecting)\n values: Dict[str, str] = dict(existing) if merge else {}\n values.update({\n \"MATRIOSKA_PROVIDER\": pid,\n \"MATRIOSKA_BASE_URL\": base_url,\n \"MATRIOSKA_API_KEY\": api_key,\n \"MATRIOSKA_MODEL\": model,\n })\n if multikey:\n values[\"MATRIOSKA_API_KEYS\"] = multikey\n if use_role_models:\n values.update({\n \"MATRIOSKA_ARCHITECT_MODEL\": arch_model,\n \"MATRIOSKA_GENERATOR_MODEL\": gen_model,\n \"MATRIOSKA_VALIDATOR_MODEL\": val_model,\n \"MATRIOSKA_JUDGE_MODEL\": judge_model,\n \"MATRIOSKA_REPAIRER_MODEL\": rep_model,\n })\n\n # Write .env\n env_path.write_text(_render_env(values), encoding=\"utf-8\")\n print(f\"\\n✓ Wrote {env_path}\")\n\n # MATRIOSKA.md scaffold\n md_path = cwd / \"MATRIOSKA.md\"\n if not md_path.exists():\n if _ask_yes_no(\"\\nCreate MATRIOSKA.md (project conventions injected into the Architect)?\", default=True):\n project_name = _ask(\"Project name\", default=cwd.name)\n md_path.write_text(\n _MATRIOSKA_MD_TEMPLATE.format(project_name=project_name),\n encoding=\"utf-8\",\n )\n print(f\"✓ Wrote {md_path}\")\n\n print(\"\\nNext steps:\")\n print(f\" matrioska run --task \\\"Create a CLI todo app with SQLite\\\"\")\n print(f\" matrioska run --task '…' --quick # rapid iteration\")\n print(f\" matrioska run --task '…' --mode plan # plan only\")\n return 0\n"}}}}