1 line
93 KiB
JSON
1 line
93 KiB
JSON
{"repo": "audiofool934/cc-copilot", "files": {"cccopilot/config.py": {"9d276a78fecdd6c40c4581cf54fd60ad39182151": {"t": 1785674094.6740031, "n": 9806, "h": "1cc23f72e9e30e4d", "c": "\"\"\"Optional config file so you set a default backend / model / keys once.\n\nLooked up at ``$CC_COPILOT_CONFIG`` or ``~/.cc-copilot.toml``. Holds:\n\n backend = \"codex\" # default LLM backend\n model = \"deepseek-v4-flash\"# default model for it\n [env] # exported as env vars (real env wins)\n DEEPSEEK_API_KEY = \"sk-…\"\n CC_COPILOT_API_BASE = \"http://localhost:11434\"\n\nPrecedence everywhere: explicit CLI flag > real environment variable > this\nfile > built-in default. The deterministic core (`brief`/`check`) ignores all\nof it — only the language features read a backend.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport sys\n\n\nTEMPLATE = '''\\\n# cc-copilot config (~/.cc-copilot.toml)\n# Defaults for the LLM-backed commands (ask / chat / brief --narrate).\n# The deterministic core (brief / check / alerts) needs none of this.\n\n# Default backend:\n# codex | claude | openai | deepseek | openrouter | moonshot | qwen | zai\n# groq | xai | gemini-api | ollama | ollama-cloud | gemini | llm\nbackend = \"codex\"\n\n# Default model for that backend (optional — omit to use the backend's own default)\n# model = \"deepseek-v4-flash\"\n\n# Secrets + provider settings, exported as environment variables.\n# Real env vars always win over these. Keep this file private (chmod 600).\n[env]\n# DEEPSEEK_API_KEY = \"sk-...\"\n# OPENAI_API_KEY = \"sk-...\"\n# OPENROUTER_API_KEY = \"sk-...\"\n# More providers: MOONSHOT_API_KEY, DASHSCOPE_API_KEY, GROQ_API_KEY,\n# XAI_API_KEY, GEMINI_API_KEY, ZAI_API_KEY, OLLAMA_API_KEY.\n# Any OpenAI-compatible endpoint (vLLM, LM Studio, Ollama, a proxy, …):\n# CC_COPILOT_API_BASE = \"http://localhost:11434\"\n# CC_COPILOT_API_KEY = \"...\"\n# CC_COPILOT_MODEL = \"qwen2.5\"\n\n# Persist your copilot Q&A so switching sessions / relaunching restores prior\n# chats. Stored locally under $CC_COPILOT_STATE_DIR (default ~/.local/state/\n# cc-copilot), dir 0700 / files 0600. These files hold your questions and the\n# copilot's answers in plaintext — set enabled = false to keep everything in\n# memory only. Never written under ~/.claude or ~/.codex.\n[history]\nenabled = true\n# dir = \"~/.local/state/cc-copilot\" # or set $CC_COPILOT_STATE_DIR\n\n# Which coding agents to observe. Default: every agent whose storage exists on\n# this machine (Claude Code under ~/.claude, Codex under ~/.codex). One cockpit\n# can watch sessions from several agents at once, grouped by project. Restrict\n# the set here, or per-invocation with --agent / $CC_COPILOT_AGENTS.\n# [agents]\n# enabled = [\"claude\", \"codex\"]\n\n# How the local claude/codex narrator CLI is confined when it powers recaps,\n# chat, since, /goal, and /loop. Read-only is the current default posture, not a\n# permanent restriction - relax it when you want the narrator to use its tools.\n# read-only (default) = tool-disabled, fail-closed if the CLI can't be confined\n# unconfined = narrator may use its tools (like the gemini/llm backends)\n# Per-invocation override: $CC_COPILOT_NARRATOR_SANDBOX=unconfined\n# [narrator]\n# sandbox = \"read-only\"\n'''\n\n\ndef path() -> str:\n return os.environ.get(\"CC_COPILOT_CONFIG\") or os.path.expanduser(\"~/.cc-copilot.toml\")\n\n\ndef _load_simple(p: str) -> dict:\n \"\"\"Minimal fallback parser (key = \"value\", [tables], # comments) for Pythons\n without ``tomllib``. Every ``[section]`` nests into its own dict, so e.g.\n ``[history] enabled = false`` reads back as ``data[\"history\"][\"enabled\"]``.\"\"\"\n data, section = {}, None\n try:\n with open(p, encoding=\"utf-8\") as fh:\n lines = fh.read().splitlines()\n except OSError:\n return {}\n for raw in lines:\n s = raw.strip()\n if not s or s.startswith(\"#\"):\n continue\n if s.startswith(\"[\") and s.endswith(\"]\"):\n section = s[1:-1].strip()\n data.setdefault(section, {})\n continue\n if \"=\" not in s:\n continue\n k, v = s.split(\"=\", 1)\n k, v = k.strip(), v.strip().strip('\"').strip(\"'\")\n if v.lower() in (\"true\", \"false\"):\n v = v.lower() == \"true\"\n target = data[section] if section is not None else data\n target[k] = v\n return data\n\n\ndef load() -> dict:\n \"\"\"Parse the config and export its ``[env]`` table (without clobbering real\n env vars). Returns the top-level settings dict.\"\"\"\n p = path()\n if not os.path.isfile(p):\n return {}\n try:\n import tomllib\n with open(p, \"rb\") as f:\n data = tomllib.load(f)\n except ImportError:\n data = _load_simple(p)\n except Exception as e: # malformed TOML — warn, don't crash\n sys.stderr.write(f\"# cc-copilot: ignoring malformed config {p}: {e}\\n\")\n return {}\n env = data.get(\"env\")\n if isinstance(env, dict):\n for k, v in env.items():\n os.environ.setdefault(str(k), str(v)) # real env wins\n return data\n\n\ndef apply_defaults(args) -> None:\n \"\"\"Fill argparse defaults from the config, only where the user didn't say.\"\"\"\n data = load()\n # backend: surface as CC_COPILOT_BACKEND so resolve() picks it up when no\n # --backend flag and no real env var were given.\n if getattr(args, \"backend\", None) is None and not os.environ.get(\"CC_COPILOT_BACKEND\"):\n b = data.get(\"backend\")\n if b:\n os.environ[\"CC_COPILOT_BACKEND\"] = str(b)\n # model: a plain default when --model wasn't passed.\n if hasattr(args, \"model\") and getattr(args, \"model\", None) is None and data.get(\"model\"):\n args.model = str(data[\"model\"])\n # state dir: surface [history].dir as CC_COPILOT_STATE_DIR (mirrors backend).\n if not os.environ.get(\"CC_COPILOT_STATE_DIR\"):\n h = data.get(\"history\")\n d = h.get(\"dir\") if isinstance(h, dict) else None\n if d:\n os.environ[\"CC_COPILOT_STATE_DIR\"] = os.path.expanduser(str(d))\n # persist: fill the chat/cockpit toggle from [history].enabled when unset.\n if hasattr(args, \"persist\") and getattr(args, \"persist\", None) is None:\n args.persist = history_enabled()\n # agent filter: surface --agent as CC_COPILOT_AGENTS so the sources\n # dispatcher scopes discovery (mirrors how --backend is surfaced).\n agent = getattr(args, \"agent\", None)\n if agent:\n os.environ[\"CC_COPILOT_AGENTS\"] = \",\".join(agent)\n\n\ndef history_enabled() -> bool:\n \"\"\"Whether to persist copilot conversations. Env wins, then the file, then on.\n\n ``CC_COPILOT_HISTORY=0|false|no|off`` (or empty) forces it off for a single\n invocation; otherwise ``[history].enabled`` from the config decides; default on.\n \"\"\"\n env = os.environ.get(\"CC_COPILOT_HISTORY\")\n if env is not None:\n return env.strip().lower() not in (\"0\", \"false\", \"no\", \"off\", \"\")\n h = load().get(\"history\")\n if isinstance(h, dict) and \"enabled\" in h:\n return bool(h[\"enabled\"])\n return True\n\n\ndef agents_enabled():\n \"\"\"Agent sources to observe, or ``None`` for 'all available'.\n\n Reads ``[agents] enabled`` from the config. Accepts a TOML array\n (``[\"claude\", \"codex\"]``) or, under the no-tomllib fallback parser, a\n comma/space string. ``$CC_COPILOT_AGENTS`` is handled in the sources\n dispatcher and takes precedence over this.\n \"\"\"\n a = load().get(\"agents\")\n if isinstance(a, dict) and a.get(\"enabled\") is not None:\n v = a[\"enabled\"]\n if isinstance(v, (list, tuple)):\n names = [str(x).strip().lower() for x in v if str(x).strip()]\n else:\n # Fallback parser (no stdlib tomllib on 3.9/3.10) hands us the raw\n # text `[\"claude\", \"codex\"]`; unwrap the brackets and per-token\n # quotes so it doesn't degrade to ['[\"claude\"', '\"codex\"]'].\n raw = str(v).strip()\n if raw.startswith(\"[\") and raw.endswith(\"]\"):\n raw = raw[1:-1]\n names = [tok.strip().strip(\"'\\\"\").lower()\n for tok in raw.replace(\",\", \" \").split()]\n names = [n for n in names if n]\n return names or None\n return None\n\n\n_VALID_SANDBOX = {\"read-only\", \"unconfined\"}\n\n\ndef _narrator_sandbox_from(data: dict) -> str:\n \"\"\"Read ``[narrator].sandbox`` from an already-loaded config dict.\n\n Returns ``\"\"`` when unset so the caller can leave the default in place\n rather than overwrite it. Unknown values collapse to the safe default\n (``read-only``) so a typo never silently widens the narrator.\n \"\"\"\n n = data.get(\"narrator\")\n if isinstance(n, dict):\n v = n.get(\"sandbox\")\n if isinstance(v, str):\n v = v.strip().lower()\n return v if v in _VALID_SANDBOX else \"read-only\"\n return \"\"\n\n\ndef narrator_sandbox() -> str:\n \"\"\"The local claude/codex narrator's confinement: ``read-only`` (default) or\n ``unconfined``.\n\n Precedence: ``$CC_COPILOT_NARRATOR_SANDBOX`` wins, then ``[narrator].sandbox``\n from the config file, then ``read-only``. Read-only is the current default\n posture, not a permanent restriction - it is opt-out here. Unknown values\n fall back to ``read-only`` so a typo never widens the narrator.\n \"\"\"\n env = os.environ.get(\"CC_COPILOT_NARRATOR_SANDBOX\")\n if isinstance(env, str) and env.strip():\n v = env.strip().lower()\n return v if v in _VALID_SANDBOX else \"read-only\"\n from_file = _narrator_sandbox_from(load())\n return from_file or \"read-only\"\n\n\ndef init_file() -> str:\n \"\"\"Write the template to the config path if absent. Returns a status line.\"\"\"\n p = path()\n if os.path.exists(p):\n return f\"config already exists: {p} (left untouched)\"\n with open(p, \"w\", encoding=\"utf-8\") as f:\n f.write(TEMPLATE)\n try:\n os.chmod(p, 0o600)\n except OSError:\n pass\n return f\"wrote starter config: {p} (chmod 600) — edit it to set backend/model/keys\"\n"}, "7f217cbf4c2ae80b28cd3303803546ca5b552b90": {"t": 1785768225.4408364, "n": 9806, "h": "1cc23f72e9e30e4d", "c": "\"\"\"Optional config file so you set a default backend / model / keys once.\n\nLooked up at ``$CC_COPILOT_CONFIG`` or ``~/.cc-copilot.toml``. Holds:\n\n backend = \"codex\" # default LLM backend\n model = \"deepseek-v4-flash\"# default model for it\n [env] # exported as env vars (real env wins)\n DEEPSEEK_API_KEY = \"sk-…\"\n CC_COPILOT_API_BASE = \"http://localhost:11434\"\n\nPrecedence everywhere: explicit CLI flag > real environment variable > this\nfile > built-in default. The deterministic core (`brief`/`check`) ignores all\nof it — only the language features read a backend.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport sys\n\n\nTEMPLATE = '''\\\n# cc-copilot config (~/.cc-copilot.toml)\n# Defaults for the LLM-backed commands (ask / chat / brief --narrate).\n# The deterministic core (brief / check / alerts) needs none of this.\n\n# Default backend:\n# codex | claude | openai | deepseek | openrouter | moonshot | qwen | zai\n# groq | xai | gemini-api | ollama | ollama-cloud | gemini | llm\nbackend = \"codex\"\n\n# Default model for that backend (optional — omit to use the backend's own default)\n# model = \"deepseek-v4-flash\"\n\n# Secrets + provider settings, exported as environment variables.\n# Real env vars always win over these. Keep this file private (chmod 600).\n[env]\n# DEEPSEEK_API_KEY = \"sk-...\"\n# OPENAI_API_KEY = \"sk-...\"\n# OPENROUTER_API_KEY = \"sk-...\"\n# More providers: MOONSHOT_API_KEY, DASHSCOPE_API_KEY, GROQ_API_KEY,\n# XAI_API_KEY, GEMINI_API_KEY, ZAI_API_KEY, OLLAMA_API_KEY.\n# Any OpenAI-compatible endpoint (vLLM, LM Studio, Ollama, a proxy, …):\n# CC_COPILOT_API_BASE = \"http://localhost:11434\"\n# CC_COPILOT_API_KEY = \"...\"\n# CC_COPILOT_MODEL = \"qwen2.5\"\n\n# Persist your copilot Q&A so switching sessions / relaunching restores prior\n# chats. Stored locally under $CC_COPILOT_STATE_DIR (default ~/.local/state/\n# cc-copilot), dir 0700 / files 0600. These files hold your questions and the\n# copilot's answers in plaintext — set enabled = false to keep everything in\n# memory only. Never written under ~/.claude or ~/.codex.\n[history]\nenabled = true\n# dir = \"~/.local/state/cc-copilot\" # or set $CC_COPILOT_STATE_DIR\n\n# Which coding agents to observe. Default: every agent whose storage exists on\n# this machine (Claude Code under ~/.claude, Codex under ~/.codex). One cockpit\n# can watch sessions from several agents at once, grouped by project. Restrict\n# the set here, or per-invocation with --agent / $CC_COPILOT_AGENTS.\n# [agents]\n# enabled = [\"claude\", \"codex\"]\n\n# How the local claude/codex narrator CLI is confined when it powers recaps,\n# chat, since, /goal, and /loop. Read-only is the current default posture, not a\n# permanent restriction - relax it when you want the narrator to use its tools.\n# read-only (default) = tool-disabled, fail-closed if the CLI can't be confined\n# unconfined = narrator may use its tools (like the gemini/llm backends)\n# Per-invocation override: $CC_COPILOT_NARRATOR_SANDBOX=unconfined\n# [narrator]\n# sandbox = \"read-only\"\n'''\n\n\ndef path() -> str:\n return os.environ.get(\"CC_COPILOT_CONFIG\") or os.path.expanduser(\"~/.cc-copilot.toml\")\n\n\ndef _load_simple(p: str) -> dict:\n \"\"\"Minimal fallback parser (key = \"value\", [tables], # comments) for Pythons\n without ``tomllib``. Every ``[section]`` nests into its own dict, so e.g.\n ``[history] enabled = false`` reads back as ``data[\"history\"][\"enabled\"]``.\"\"\"\n data, section = {}, None\n try:\n with open(p, encoding=\"utf-8\") as fh:\n lines = fh.read().splitlines()\n except OSError:\n return {}\n for raw in lines:\n s = raw.strip()\n if not s or s.startswith(\"#\"):\n continue\n if s.startswith(\"[\") and s.endswith(\"]\"):\n section = s[1:-1].strip()\n data.setdefault(section, {})\n continue\n if \"=\" not in s:\n continue\n k, v = s.split(\"=\", 1)\n k, v = k.strip(), v.strip().strip('\"').strip(\"'\")\n if v.lower() in (\"true\", \"false\"):\n v = v.lower() == \"true\"\n target = data[section] if section is not None else data\n target[k] = v\n return data\n\n\ndef load() -> dict:\n \"\"\"Parse the config and export its ``[env]`` table (without clobbering real\n env vars). Returns the top-level settings dict.\"\"\"\n p = path()\n if not os.path.isfile(p):\n return {}\n try:\n import tomllib\n with open(p, \"rb\") as f:\n data = tomllib.load(f)\n except ImportError:\n data = _load_simple(p)\n except Exception as e: # malformed TOML — warn, don't crash\n sys.stderr.write(f\"# cc-copilot: ignoring malformed config {p}: {e}\\n\")\n return {}\n env = data.get(\"env\")\n if isinstance(env, dict):\n for k, v in env.items():\n os.environ.setdefault(str(k), str(v)) # real env wins\n return data\n\n\ndef apply_defaults(args) -> None:\n \"\"\"Fill argparse defaults from the config, only where the user didn't say.\"\"\"\n data = load()\n # backend: surface as CC_COPILOT_BACKEND so resolve() picks it up when no\n # --backend flag and no real env var were given.\n if getattr(args, \"backend\", None) is None and not os.environ.get(\"CC_COPILOT_BACKEND\"):\n b = data.get(\"backend\")\n if b:\n os.environ[\"CC_COPILOT_BACKEND\"] = str(b)\n # model: a plain default when --model wasn't passed.\n if hasattr(args, \"model\") and getattr(args, \"model\", None) is None and data.get(\"model\"):\n args.model = str(data[\"model\"])\n # state dir: surface [history].dir as CC_COPILOT_STATE_DIR (mirrors backend).\n if not os.environ.get(\"CC_COPILOT_STATE_DIR\"):\n h = data.get(\"history\")\n d = h.get(\"dir\") if isinstance(h, dict) else None\n if d:\n os.environ[\"CC_COPILOT_STATE_DIR\"] = os.path.expanduser(str(d))\n # persist: fill the chat/cockpit toggle from [history].enabled when unset.\n if hasattr(args, \"persist\") and getattr(args, \"persist\", None) is None:\n args.persist = history_enabled()\n # agent filter: surface --agent as CC_COPILOT_AGENTS so the sources\n # dispatcher scopes discovery (mirrors how --backend is surfaced).\n agent = getattr(args, \"agent\", None)\n if agent:\n os.environ[\"CC_COPILOT_AGENTS\"] = \",\".join(agent)\n\n\ndef history_enabled() -> bool:\n \"\"\"Whether to persist copilot conversations. Env wins, then the file, then on.\n\n ``CC_COPILOT_HISTORY=0|false|no|off`` (or empty) forces it off for a single\n invocation; otherwise ``[history].enabled`` from the config decides; default on.\n \"\"\"\n env = os.environ.get(\"CC_COPILOT_HISTORY\")\n if env is not None:\n return env.strip().lower() not in (\"0\", \"false\", \"no\", \"off\", \"\")\n h = load().get(\"history\")\n if isinstance(h, dict) and \"enabled\" in h:\n return bool(h[\"enabled\"])\n return True\n\n\ndef agents_enabled():\n \"\"\"Agent sources to observe, or ``None`` for 'all available'.\n\n Reads ``[agents] enabled`` from the config. Accepts a TOML array\n (``[\"claude\", \"codex\"]``) or, under the no-tomllib fallback parser, a\n comma/space string. ``$CC_COPILOT_AGENTS`` is handled in the sources\n dispatcher and takes precedence over this.\n \"\"\"\n a = load().get(\"agents\")\n if isinstance(a, dict) and a.get(\"enabled\") is not None:\n v = a[\"enabled\"]\n if isinstance(v, (list, tuple)):\n names = [str(x).strip().lower() for x in v if str(x).strip()]\n else:\n # Fallback parser (no stdlib tomllib on 3.9/3.10) hands us the raw\n # text `[\"claude\", \"codex\"]`; unwrap the brackets and per-token\n # quotes so it doesn't degrade to ['[\"claude\"', '\"codex\"]'].\n raw = str(v).strip()\n if raw.startswith(\"[\") and raw.endswith(\"]\"):\n raw = raw[1:-1]\n names = [tok.strip().strip(\"'\\\"\").lower()\n for tok in raw.replace(\",\", \" \").split()]\n names = [n for n in names if n]\n return names or None\n return None\n\n\n_VALID_SANDBOX = {\"read-only\", \"unconfined\"}\n\n\ndef _narrator_sandbox_from(data: dict) -> str:\n \"\"\"Read ``[narrator].sandbox`` from an already-loaded config dict.\n\n Returns ``\"\"`` when unset so the caller can leave the default in place\n rather than overwrite it. Unknown values collapse to the safe default\n (``read-only``) so a typo never silently widens the narrator.\n \"\"\"\n n = data.get(\"narrator\")\n if isinstance(n, dict):\n v = n.get(\"sandbox\")\n if isinstance(v, str):\n v = v.strip().lower()\n return v if v in _VALID_SANDBOX else \"read-only\"\n return \"\"\n\n\ndef narrator_sandbox() -> str:\n \"\"\"The local claude/codex narrator's confinement: ``read-only`` (default) or\n ``unconfined``.\n\n Precedence: ``$CC_COPILOT_NARRATOR_SANDBOX`` wins, then ``[narrator].sandbox``\n from the config file, then ``read-only``. Read-only is the current default\n posture, not a permanent restriction - it is opt-out here. Unknown values\n fall back to ``read-only`` so a typo never widens the narrator.\n \"\"\"\n env = os.environ.get(\"CC_COPILOT_NARRATOR_SANDBOX\")\n if isinstance(env, str) and env.strip():\n v = env.strip().lower()\n return v if v in _VALID_SANDBOX else \"read-only\"\n from_file = _narrator_sandbox_from(load())\n return from_file or \"read-only\"\n\n\ndef init_file() -> str:\n \"\"\"Write the template to the config path if absent. Returns a status line.\"\"\"\n p = path()\n if os.path.exists(p):\n return f\"config already exists: {p} (left untouched)\"\n with open(p, \"w\", encoding=\"utf-8\") as f:\n f.write(TEMPLATE)\n try:\n os.chmod(p, 0o600)\n except OSError:\n pass\n return f\"wrote starter config: {p} (chmod 600) — edit it to set backend/model/keys\"\n"}, "dd3491338fe823596e336db4f9a72f4403067279": {"t": 1785768231.4450922, "n": 7954, "h": "31a1e0b8d4a7075c", "c": "\"\"\"Optional config file so you set a default backend / model / keys once.\n\nLooked up at ``$CC_COPILOT_CONFIG`` or ``~/.cc-copilot.toml``. Holds:\n\n backend = \"codex\" # default LLM backend\n model = \"deepseek-v4-flash\"# default model for it\n [env] # exported as env vars (real env wins)\n DEEPSEEK_API_KEY = \"sk-…\"\n CC_COPILOT_API_BASE = \"http://localhost:11434\"\n\nPrecedence everywhere: explicit CLI flag > real environment variable > this\nfile > built-in default. The deterministic core (`brief`/`check`) ignores all\nof it — only the language features read a backend.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport sys\n\n\nTEMPLATE = '''\\\n# cc-copilot config (~/.cc-copilot.toml)\n# Defaults for the LLM-backed commands (ask / chat / brief --narrate).\n# The deterministic core (brief / check / alerts) needs none of this.\n\n# Default backend:\n# codex | claude | openai | deepseek | openrouter | moonshot | qwen | zai\n# groq | xai | gemini-api | ollama | ollama-cloud | gemini | llm\nbackend = \"codex\"\n\n# Default model for that backend (optional — omit to use the backend's own default)\n# model = \"deepseek-v4-flash\"\n\n# Secrets + provider settings, exported as environment variables.\n# Real env vars always win over these. Keep this file private (chmod 600).\n[env]\n# DEEPSEEK_API_KEY = \"sk-...\"\n# OPENAI_API_KEY = \"sk-...\"\n# OPENROUTER_API_KEY = \"sk-...\"\n# More providers: MOONSHOT_API_KEY, DASHSCOPE_API_KEY, GROQ_API_KEY,\n# XAI_API_KEY, GEMINI_API_KEY, ZAI_API_KEY, OLLAMA_API_KEY.\n# Any OpenAI-compatible endpoint (vLLM, LM Studio, Ollama, a proxy, …):\n# CC_COPILOT_API_BASE = \"http://localhost:11434\"\n# CC_COPILOT_API_KEY = \"...\"\n# CC_COPILOT_MODEL = \"qwen2.5\"\n\n# Persist your copilot Q&A so switching sessions / relaunching restores prior\n# chats. Stored locally under $CC_COPILOT_STATE_DIR (default ~/.local/state/\n# cc-copilot), dir 0700 / files 0600. These files hold your questions and the\n# copilot's answers in plaintext — set enabled = false to keep everything in\n# memory only. Never written under ~/.claude or ~/.codex.\n[history]\nenabled = true\n# dir = \"~/.local/state/cc-copilot\" # or set $CC_COPILOT_STATE_DIR\n\n# Which coding agents to observe. Default: every agent whose storage exists on\n# this machine (Claude Code under ~/.claude, Codex under ~/.codex). One cockpit\n# can watch sessions from several agents at once, grouped by project. Restrict\n# the set here, or per-invocation with --agent / $CC_COPILOT_AGENTS.\n# [agents]\n# enabled = [\"claude\", \"codex\"]\n'''\n\n\ndef path() -> str:\n return os.environ.get(\"CC_COPILOT_CONFIG\") or os.path.expanduser(\"~/.cc-copilot.toml\")\n\n\ndef _load_simple(p: str) -> dict:\n \"\"\"Minimal fallback parser (key = \"value\", [tables], # comments) for Pythons\n without ``tomllib``. Every ``[section]`` nests into its own dict, so e.g.\n ``[history] enabled = false`` reads back as ``data[\"history\"][\"enabled\"]``.\"\"\"\n data, section = {}, None\n try:\n with open(p, encoding=\"utf-8\") as fh:\n lines = fh.read().splitlines()\n except OSError:\n return {}\n for raw in lines:\n s = raw.strip()\n if not s or s.startswith(\"#\"):\n continue\n if s.startswith(\"[\") and s.endswith(\"]\"):\n section = s[1:-1].strip()\n data.setdefault(section, {})\n continue\n if \"=\" not in s:\n continue\n k, v = s.split(\"=\", 1)\n k, v = k.strip(), v.strip().strip('\"').strip(\"'\")\n if v.lower() in (\"true\", \"false\"):\n v = v.lower() == \"true\"\n target = data[section] if section is not None else data\n target[k] = v\n return data\n\n\ndef load() -> dict:\n \"\"\"Parse the config and export its ``[env]`` table (without clobbering real\n env vars). Returns the top-level settings dict.\"\"\"\n p = path()\n if not os.path.isfile(p):\n return {}\n try:\n import tomllib\n with open(p, \"rb\") as f:\n data = tomllib.load(f)\n except ImportError:\n data = _load_simple(p)\n except Exception as e: # malformed TOML — warn, don't crash\n sys.stderr.write(f\"# cc-copilot: ignoring malformed config {p}: {e}\\n\")\n return {}\n env = data.get(\"env\")\n if isinstance(env, dict):\n for k, v in env.items():\n os.environ.setdefault(str(k), str(v)) # real env wins\n return data\n\n\ndef apply_defaults(args) -> None:\n \"\"\"Fill argparse defaults from the config, only where the user didn't say.\"\"\"\n data = load()\n # backend: surface as CC_COPILOT_BACKEND so resolve() picks it up when no\n # --backend flag and no real env var were given.\n if getattr(args, \"backend\", None) is None and not os.environ.get(\"CC_COPILOT_BACKEND\"):\n b = data.get(\"backend\")\n if b:\n os.environ[\"CC_COPILOT_BACKEND\"] = str(b)\n # model: a plain default when --model wasn't passed.\n if hasattr(args, \"model\") and getattr(args, \"model\", None) is None and data.get(\"model\"):\n args.model = str(data[\"model\"])\n # state dir: surface [history].dir as CC_COPILOT_STATE_DIR (mirrors backend).\n if not os.environ.get(\"CC_COPILOT_STATE_DIR\"):\n h = data.get(\"history\")\n d = h.get(\"dir\") if isinstance(h, dict) else None\n if d:\n os.environ[\"CC_COPILOT_STATE_DIR\"] = os.path.expanduser(str(d))\n # persist: fill the chat/cockpit toggle from [history].enabled when unset.\n if hasattr(args, \"persist\") and getattr(args, \"persist\", None) is None:\n args.persist = history_enabled()\n # agent filter: surface --agent as CC_COPILOT_AGENTS so the sources\n # dispatcher scopes discovery (mirrors how --backend is surfaced).\n agent = getattr(args, \"agent\", None)\n if agent:\n os.environ[\"CC_COPILOT_AGENTS\"] = \",\".join(agent)\n\n\ndef history_enabled() -> bool:\n \"\"\"Whether to persist copilot conversations. Env wins, then the file, then on.\n\n ``CC_COPILOT_HISTORY=0|false|no|off`` (or empty) forces it off for a single\n invocation; otherwise ``[history].enabled`` from the config decides; default on.\n \"\"\"\n env = os.environ.get(\"CC_COPILOT_HISTORY\")\n if env is not None:\n return env.strip().lower() not in (\"0\", \"false\", \"no\", \"off\", \"\")\n h = load().get(\"history\")\n if isinstance(h, dict) and \"enabled\" in h:\n return bool(h[\"enabled\"])\n return True\n\n\ndef agents_enabled():\n \"\"\"Agent sources to observe, or ``None`` for 'all available'.\n\n Reads ``[agents] enabled`` from the config. Accepts a TOML array\n (``[\"claude\", \"codex\"]``) or, under the no-tomllib fallback parser, a\n comma/space string. ``$CC_COPILOT_AGENTS`` is handled in the sources\n dispatcher and takes precedence over this.\n \"\"\"\n a = load().get(\"agents\")\n if isinstance(a, dict) and a.get(\"enabled\") is not None:\n v = a[\"enabled\"]\n if isinstance(v, (list, tuple)):\n names = [str(x).strip().lower() for x in v if str(x).strip()]\n else:\n # Fallback parser (no stdlib tomllib on 3.9/3.10) hands us the raw\n # text `[\"claude\", \"codex\"]`; unwrap the brackets and per-token\n # quotes so it doesn't degrade to ['[\"claude\"', '\"codex\"]'].\n raw = str(v).strip()\n if raw.startswith(\"[\") and raw.endswith(\"]\"):\n raw = raw[1:-1]\n names = [tok.strip().strip(\"'\\\"\").lower()\n for tok in raw.replace(\",\", \" \").split()]\n names = [n for n in names if n]\n return names or None\n return None\n\n\ndef init_file() -> str:\n \"\"\"Write the template to the config path if absent. Returns a status line.\"\"\"\n p = path()\n if os.path.exists(p):\n return f\"config already exists: {p} (left untouched)\"\n with open(p, \"w\", encoding=\"utf-8\") as f:\n f.write(TEMPLATE)\n try:\n os.chmod(p, 0o600)\n except OSError:\n pass\n return f\"wrote starter config: {p} (chmod 600) — edit it to set backend/model/keys\"\n"}, "cd396ff6026d3b1ef678d78fd60d613bbb3815de": {"t": 1785768236.418505, "n": 7923, "h": "6431209c1d9544d7", "c": "\"\"\"Optional config file so you set a default backend / model / keys once.\n\nLooked up at ``$CC_COPILOT_CONFIG`` or ``~/.cc-copilot.toml``. Holds:\n\n backend = \"codex\" # default LLM backend\n model = \"deepseek-v4-flash\"# default model for it\n [env] # exported as env vars (real env wins)\n DEEPSEEK_API_KEY = \"sk-…\"\n CC_COPILOT_API_BASE = \"http://localhost:11434\"\n\nPrecedence everywhere: explicit CLI flag > real environment variable > this\nfile > built-in default. The deterministic core (`brief`/`check`) ignores all\nof it — only the language features read a backend.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport sys\n\n\nTEMPLATE = '''\\\n# cc-copilot config (~/.cc-copilot.toml)\n# Defaults for the LLM-backed commands (ask / chat / brief --narrate).\n# The deterministic core (brief / check / alerts) needs none of this.\n\n# Default backend:\n# codex | claude | openai | deepseek | openrouter | moonshot | qwen | zai\n# groq | xai | gemini-api | ollama | gemini | llm\nbackend = \"codex\"\n\n# Default model for that backend (optional — omit to use the backend's own default)\n# model = \"deepseek-v4-flash\"\n\n# Secrets + provider settings, exported as environment variables.\n# Real env vars always win over these. Keep this file private (chmod 600).\n[env]\n# DEEPSEEK_API_KEY = \"sk-...\"\n# OPENAI_API_KEY = \"sk-...\"\n# OPENROUTER_API_KEY = \"sk-...\"\n# More providers: MOONSHOT_API_KEY, DASHSCOPE_API_KEY, GROQ_API_KEY,\n# XAI_API_KEY, GEMINI_API_KEY, ZAI_API_KEY.\n# Any OpenAI-compatible endpoint (vLLM, LM Studio, Ollama, a proxy, …):\n# CC_COPILOT_API_BASE = \"http://localhost:11434\"\n# CC_COPILOT_API_KEY = \"...\"\n# CC_COPILOT_MODEL = \"qwen2.5\"\n\n# Persist your copilot Q&A so switching sessions / relaunching restores prior\n# chats. Stored locally under $CC_COPILOT_STATE_DIR (default ~/.local/state/\n# cc-copilot), dir 0700 / files 0600. These files hold your questions and the\n# copilot's answers in plaintext — set enabled = false to keep everything in\n# memory only. Never written under ~/.claude or ~/.codex.\n[history]\nenabled = true\n# dir = \"~/.local/state/cc-copilot\" # or set $CC_COPILOT_STATE_DIR\n\n# Which coding agents to observe. Default: every agent whose storage exists on\n# this machine (Claude Code under ~/.claude, Codex under ~/.codex). One cockpit\n# can watch sessions from several agents at once, grouped by project. Restrict\n# the set here, or per-invocation with --agent / $CC_COPILOT_AGENTS.\n# [agents]\n# enabled = [\"claude\", \"codex\"]\n'''\n\n\ndef path() -> str:\n return os.environ.get(\"CC_COPILOT_CONFIG\") or os.path.expanduser(\"~/.cc-copilot.toml\")\n\n\ndef _load_simple(p: str) -> dict:\n \"\"\"Minimal fallback parser (key = \"value\", [tables], # comments) for Pythons\n without ``tomllib``. Every ``[section]`` nests into its own dict, so e.g.\n ``[history] enabled = false`` reads back as ``data[\"history\"][\"enabled\"]``.\"\"\"\n data, section = {}, None\n try:\n with open(p, encoding=\"utf-8\") as fh:\n lines = fh.read().splitlines()\n except OSError:\n return {}\n for raw in lines:\n s = raw.strip()\n if not s or s.startswith(\"#\"):\n continue\n if s.startswith(\"[\") and s.endswith(\"]\"):\n section = s[1:-1].strip()\n data.setdefault(section, {})\n continue\n if \"=\" not in s:\n continue\n k, v = s.split(\"=\", 1)\n k, v = k.strip(), v.strip().strip('\"').strip(\"'\")\n if v.lower() in (\"true\", \"false\"):\n v = v.lower() == \"true\"\n target = data[section] if section is not None else data\n target[k] = v\n return data\n\n\ndef load() -> dict:\n \"\"\"Parse the config and export its ``[env]`` table (without clobbering real\n env vars). Returns the top-level settings dict.\"\"\"\n p = path()\n if not os.path.isfile(p):\n return {}\n try:\n import tomllib\n with open(p, \"rb\") as f:\n data = tomllib.load(f)\n except ImportError:\n data = _load_simple(p)\n except Exception as e: # malformed TOML — warn, don't crash\n sys.stderr.write(f\"# cc-copilot: ignoring malformed config {p}: {e}\\n\")\n return {}\n env = data.get(\"env\")\n if isinstance(env, dict):\n for k, v in env.items():\n os.environ.setdefault(str(k), str(v)) # real env wins\n return data\n\n\ndef apply_defaults(args) -> None:\n \"\"\"Fill argparse defaults from the config, only where the user didn't say.\"\"\"\n data = load()\n # backend: surface as CC_COPILOT_BACKEND so resolve() picks it up when no\n # --backend flag and no real env var were given.\n if getattr(args, \"backend\", None) is None and not os.environ.get(\"CC_COPILOT_BACKEND\"):\n b = data.get(\"backend\")\n if b:\n os.environ[\"CC_COPILOT_BACKEND\"] = str(b)\n # model: a plain default when --model wasn't passed.\n if hasattr(args, \"model\") and getattr(args, \"model\", None) is None and data.get(\"model\"):\n args.model = str(data[\"model\"])\n # state dir: surface [history].dir as CC_COPILOT_STATE_DIR (mirrors backend).\n if not os.environ.get(\"CC_COPILOT_STATE_DIR\"):\n h = data.get(\"history\")\n d = h.get(\"dir\") if isinstance(h, dict) else None\n if d:\n os.environ[\"CC_COPILOT_STATE_DIR\"] = os.path.expanduser(str(d))\n # persist: fill the chat/cockpit toggle from [history].enabled when unset.\n if hasattr(args, \"persist\") and getattr(args, \"persist\", None) is None:\n args.persist = history_enabled()\n # agent filter: surface --agent as CC_COPILOT_AGENTS so the sources\n # dispatcher scopes discovery (mirrors how --backend is surfaced).\n agent = getattr(args, \"agent\", None)\n if agent:\n os.environ[\"CC_COPILOT_AGENTS\"] = \",\".join(agent)\n\n\ndef history_enabled() -> bool:\n \"\"\"Whether to persist copilot conversations. Env wins, then the file, then on.\n\n ``CC_COPILOT_HISTORY=0|false|no|off`` (or empty) forces it off for a single\n invocation; otherwise ``[history].enabled`` from the config decides; default on.\n \"\"\"\n env = os.environ.get(\"CC_COPILOT_HISTORY\")\n if env is not None:\n return env.strip().lower() not in (\"0\", \"false\", \"no\", \"off\", \"\")\n h = load().get(\"history\")\n if isinstance(h, dict) and \"enabled\" in h:\n return bool(h[\"enabled\"])\n return True\n\n\ndef agents_enabled():\n \"\"\"Agent sources to observe, or ``None`` for 'all available'.\n\n Reads ``[agents] enabled`` from the config. Accepts a TOML array\n (``[\"claude\", \"codex\"]``) or, under the no-tomllib fallback parser, a\n comma/space string. ``$CC_COPILOT_AGENTS`` is handled in the sources\n dispatcher and takes precedence over this.\n \"\"\"\n a = load().get(\"agents\")\n if isinstance(a, dict) and a.get(\"enabled\") is not None:\n v = a[\"enabled\"]\n if isinstance(v, (list, tuple)):\n names = [str(x).strip().lower() for x in v if str(x).strip()]\n else:\n # Fallback parser (no stdlib tomllib on 3.9/3.10) hands us the raw\n # text `[\"claude\", \"codex\"]`; unwrap the brackets and per-token\n # quotes so it doesn't degrade to ['[\"claude\"', '\"codex\"]'].\n raw = str(v).strip()\n if raw.startswith(\"[\") and raw.endswith(\"]\"):\n raw = raw[1:-1]\n names = [tok.strip().strip(\"'\\\"\").lower()\n for tok in raw.replace(\",\", \" \").split()]\n names = [n for n in names if n]\n return names or None\n return None\n\n\ndef init_file() -> str:\n \"\"\"Write the template to the config path if absent. Returns a status line.\"\"\"\n p = path()\n if os.path.exists(p):\n return f\"config already exists: {p} (left untouched)\"\n with open(p, \"w\", encoding=\"utf-8\") as f:\n f.write(TEMPLATE)\n try:\n os.chmod(p, 0o600)\n except OSError:\n pass\n return f\"wrote starter config: {p} (chmod 600) — edit it to set backend/model/keys\"\n"}, "0c1dbd2bb4cabdef1ab19224e59d82818d3c278a": {"t": 1785768239.8983648, "n": 7506, "h": "3c0fd2587ef571da", "c": "\"\"\"Optional config file so you set a default backend / model / keys once.\n\nLooked up at ``$CC_COPILOT_CONFIG`` or ``~/.cc-copilot.toml``. Holds:\n\n backend = \"codex\" # default LLM backend\n model = \"deepseek-v4-flash\"# default model for it\n [env] # exported as env vars (real env wins)\n DEEPSEEK_API_KEY = \"sk-…\"\n CC_COPILOT_API_BASE = \"http://localhost:11434\"\n\nPrecedence everywhere: explicit CLI flag > real environment variable > this\nfile > built-in default. The deterministic core (`brief`/`check`) ignores all\nof it — only the language features read a backend.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport sys\n\n\nTEMPLATE = '''\\\n# cc-copilot config (~/.cc-copilot.toml)\n# Defaults for the LLM-backed commands (ask / chat / brief --narrate).\n# The deterministic core (brief / check / alerts) needs none of this.\n\n# Default backend:\n# codex | claude | openai | deepseek | openrouter | moonshot | qwen | zai\n# groq | xai | gemini-api | ollama | gemini | llm\nbackend = \"codex\"\n\n# Default model for that backend (optional — omit to use the backend's own default)\n# model = \"deepseek-v4-flash\"\n\n# Secrets + provider settings, exported as environment variables.\n# Real env vars always win over these. Keep this file private (chmod 600).\n[env]\n# DEEPSEEK_API_KEY = \"sk-...\"\n# OPENAI_API_KEY = \"sk-...\"\n# OPENROUTER_API_KEY = \"sk-...\"\n# More providers: MOONSHOT_API_KEY, DASHSCOPE_API_KEY, GROQ_API_KEY,\n# XAI_API_KEY, GEMINI_API_KEY, ZAI_API_KEY.\n# Any OpenAI-compatible endpoint (vLLM, LM Studio, Ollama, a proxy, …):\n# CC_COPILOT_API_BASE = \"http://localhost:11434\"\n# CC_COPILOT_API_KEY = \"...\"\n# CC_COPILOT_MODEL = \"qwen2.5\"\n\n# Persist your copilot Q&A so switching sessions / relaunching restores prior\n# chats. Stored locally under $CC_COPILOT_STATE_DIR (default ~/.local/state/\n# cc-copilot), dir 0700 / files 0600. These files hold your questions and the\n# copilot's answers in plaintext — set enabled = false to keep everything in\n# memory only. Never written under ~/.claude or ~/.codex.\n[history]\nenabled = true\n# dir = \"~/.local/state/cc-copilot\" # or set $CC_COPILOT_STATE_DIR\n\n# Which coding agents to observe. Default: every agent whose storage exists on\n# this machine (Claude Code under ~/.claude, Codex under ~/.codex). One cockpit\n# can watch sessions from several agents at once, grouped by project. Restrict\n# the set here, or per-invocation with --agent / $CC_COPILOT_AGENTS.\n# [agents]\n# enabled = [\"claude\", \"codex\"]\n'''\n\n\ndef path() -> str:\n return os.environ.get(\"CC_COPILOT_CONFIG\") or os.path.expanduser(\"~/.cc-copilot.toml\")\n\n\ndef _load_simple(p: str) -> dict:\n \"\"\"Minimal fallback parser (key = \"value\", [tables], # comments) for Pythons\n without ``tomllib``. Every ``[section]`` nests into its own dict, so e.g.\n ``[history] enabled = false`` reads back as ``data[\"history\"][\"enabled\"]``.\"\"\"\n data, section = {}, None\n try:\n with open(p, encoding=\"utf-8\") as fh:\n lines = fh.read().splitlines()\n except OSError:\n return {}\n for raw in lines:\n s = raw.strip()\n if not s or s.startswith(\"#\"):\n continue\n if s.startswith(\"[\") and s.endswith(\"]\"):\n section = s[1:-1].strip()\n data.setdefault(section, {})\n continue\n if \"=\" not in s:\n continue\n k, v = s.split(\"=\", 1)\n k, v = k.strip(), v.strip().strip('\"').strip(\"'\")\n if v.lower() in (\"true\", \"false\"):\n v = v.lower() == \"true\"\n target = data[section] if section is not None else data\n target[k] = v\n return data\n\n\ndef load() -> dict:\n \"\"\"Parse the config and export its ``[env]`` table (without clobbering real\n env vars). Returns the top-level settings dict.\"\"\"\n p = path()\n if not os.path.isfile(p):\n return {}\n try:\n import tomllib\n with open(p, \"rb\") as f:\n data = tomllib.load(f)\n except ImportError:\n data = _load_simple(p)\n except Exception as e: # malformed TOML — warn, don't crash\n sys.stderr.write(f\"# cc-copilot: ignoring malformed config {p}: {e}\\n\")\n return {}\n env = data.get(\"env\")\n if isinstance(env, dict):\n for k, v in env.items():\n os.environ.setdefault(str(k), str(v)) # real env wins\n return data\n\n\ndef apply_defaults(args) -> None:\n \"\"\"Fill argparse defaults from the config, only where the user didn't say.\"\"\"\n data = load()\n # backend: surface as CC_COPILOT_BACKEND so resolve() picks it up when no\n # --backend flag and no real env var were given.\n if getattr(args, \"backend\", None) is None and not os.environ.get(\"CC_COPILOT_BACKEND\"):\n b = data.get(\"backend\")\n if b:\n os.environ[\"CC_COPILOT_BACKEND\"] = str(b)\n # model: a plain default when --model wasn't passed.\n if hasattr(args, \"model\") and getattr(args, \"model\", None) is None and data.get(\"model\"):\n args.model = str(data[\"model\"])\n # state dir: surface [history].dir as CC_COPILOT_STATE_DIR (mirrors backend).\n if not os.environ.get(\"CC_COPILOT_STATE_DIR\"):\n h = data.get(\"history\")\n d = h.get(\"dir\") if isinstance(h, dict) else None\n if d:\n os.environ[\"CC_COPILOT_STATE_DIR\"] = os.path.expanduser(str(d))\n # persist: fill the chat/cockpit toggle from [history].enabled when unset.\n if hasattr(args, \"persist\") and getattr(args, \"persist\", None) is None:\n args.persist = history_enabled()\n # agent filter: surface --agent as CC_COPILOT_AGENTS so the sources\n # dispatcher scopes discovery (mirrors how --backend is surfaced).\n agent = getattr(args, \"agent\", None)\n if agent:\n os.environ[\"CC_COPILOT_AGENTS\"] = \",\".join(agent)\n\n\ndef history_enabled() -> bool:\n \"\"\"Whether to persist copilot conversations. Env wins, then the file, then on.\n\n ``CC_COPILOT_HISTORY=0|false|no|off`` (or empty) forces it off for a single\n invocation; otherwise ``[history].enabled`` from the config decides; default on.\n \"\"\"\n env = os.environ.get(\"CC_COPILOT_HISTORY\")\n if env is not None:\n return env.strip().lower() not in (\"0\", \"false\", \"no\", \"off\", \"\")\n h = load().get(\"history\")\n if isinstance(h, dict) and \"enabled\" in h:\n return bool(h[\"enabled\"])\n return True\n\n\ndef agents_enabled():\n \"\"\"Agent sources to observe, or ``None`` for 'all available'.\n\n Reads ``[agents] enabled`` from the config. Accepts a TOML array\n (``[\"claude\", \"codex\"]``) or, under the no-tomllib fallback parser, a\n comma/space string. ``$CC_COPILOT_AGENTS`` is handled in the sources\n dispatcher and takes precedence over this.\n \"\"\"\n a = load().get(\"agents\")\n if isinstance(a, dict) and a.get(\"enabled\") is not None:\n v = a[\"enabled\"]\n if isinstance(v, (list, tuple)):\n names = [str(x).strip().lower() for x in v if str(x).strip()]\n else:\n names = [p.strip().lower() for p in str(v).replace(\",\", \" \").split() if p.strip()]\n return names or None\n return None\n\n\ndef init_file() -> str:\n \"\"\"Write the template to the config path if absent. Returns a status line.\"\"\"\n p = path()\n if os.path.exists(p):\n return f\"config already exists: {p} (left untouched)\"\n with open(p, \"w\", encoding=\"utf-8\") as f:\n f.write(TEMPLATE)\n try:\n os.chmod(p, 0o600)\n except OSError:\n pass\n return f\"wrote starter config: {p} (chmod 600) — edit it to set backend/model/keys\"\n"}, "6999ff2ca17f7d7ea728cba1179c6afc7c7c2bc4": {"t": 1785768242.776035, "n": 7910, "h": "ad5b0fc4e87638f8", "c": "\"\"\"Optional config file so you set a default backend / model / keys once.\n\nLooked up at ``$CC_COPILOT_CONFIG`` or ``~/.cc-copilot.toml``. Holds:\n\n backend = \"codex\" # default LLM backend\n model = \"deepseek-v4-flash\"# default model for it\n [env] # exported as env vars (real env wins)\n DEEPSEEK_API_KEY = \"sk-…\"\n CC_COPILOT_API_BASE = \"http://localhost:11434\"\n\nPrecedence everywhere: explicit CLI flag > real environment variable > this\nfile > built-in default. The deterministic core (`brief`/`check`) ignores all\nof it — only the language features read a backend.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport sys\n\n\nTEMPLATE = '''\\\n# cc-copilot config (~/.cc-copilot.toml)\n# Defaults for the LLM-backed commands (ask / chat / brief --narrate).\n# The deterministic core (brief / check / alerts) needs none of this.\n\n# Default backend:\n# codex | claude | openai | deepseek | openrouter | moonshot | qwen | zai\n# groq | xai | gemini-api | mistral | together | fireworks | cerebras\n# deepinfra | huggingface | nvidia | chutes | novita | venice | arcee | gmi\n# stepfun | xiaomi | volcengine | tencent-tokenhub | ollama | gemini | llm\nbackend = \"codex\"\n\n# Default model for that backend (optional — omit to use the backend's own default)\n# model = \"deepseek-v4-flash\"\n\n# Secrets + provider settings, exported as environment variables.\n# Real env vars always win over these. Keep this file private (chmod 600).\n[env]\n# DEEPSEEK_API_KEY = \"sk-...\"\n# OPENAI_API_KEY = \"sk-...\"\n# OPENROUTER_API_KEY = \"sk-...\"\n# More providers: MOONSHOT_API_KEY, DASHSCOPE_API_KEY, MISTRAL_API_KEY,\n# TOGETHER_API_KEY, FIREWORKS_API_KEY, CEREBRAS_API_KEY, DEEPINFRA_API_KEY,\n# HUGGINGFACE_API_KEY, NVIDIA_API_KEY, CHUTES_API_KEY, NOVITA_API_KEY,\n# VENICE_API_KEY, ARCEE_API_KEY, GMI_API_KEY, STEPFUN_API_KEY,\n# XIAOMI_API_KEY, VOLCENGINE_API_KEY, TENCENT_TOKENHUB_API_KEY.\n# Any OpenAI-compatible endpoint (vLLM, LM Studio, Ollama, a proxy, …):\n# CC_COPILOT_API_BASE = \"http://localhost:11434\"\n# CC_COPILOT_API_KEY = \"...\"\n# CC_COPILOT_MODEL = \"qwen2.5\"\n\n# Persist your copilot Q&A so switching sessions / relaunching restores prior\n# chats. Stored locally under $CC_COPILOT_STATE_DIR (default ~/.local/state/\n# cc-copilot), dir 0700 / files 0600. These files hold your questions and the\n# copilot's answers in plaintext — set enabled = false to keep everything in\n# memory only. Never written under ~/.claude or ~/.codex.\n[history]\nenabled = true\n# dir = \"~/.local/state/cc-copilot\" # or set $CC_COPILOT_STATE_DIR\n\n# Which coding agents to observe. Default: every agent whose storage exists on\n# this machine (Claude Code under ~/.claude, Codex under ~/.codex). One cockpit\n# can watch sessions from several agents at once, grouped by project. Restrict\n# the set here, or per-invocation with --agent / $CC_COPILOT_AGENTS.\n# [agents]\n# enabled = [\"claude\", \"codex\"]\n'''\n\n\ndef path() -> str:\n return os.environ.get(\"CC_COPILOT_CONFIG\") or os.path.expanduser(\"~/.cc-copilot.toml\")\n\n\ndef _load_simple(p: str) -> dict:\n \"\"\"Minimal fallback parser (key = \"value\", [tables], # comments) for Pythons\n without ``tomllib``. Every ``[section]`` nests into its own dict, so e.g.\n ``[history] enabled = false`` reads back as ``data[\"history\"][\"enabled\"]``.\"\"\"\n data, section = {}, None\n try:\n with open(p, encoding=\"utf-8\") as fh:\n lines = fh.read().splitlines()\n except OSError:\n return {}\n for raw in lines:\n s = raw.strip()\n if not s or s.startswith(\"#\"):\n continue\n if s.startswith(\"[\") and s.endswith(\"]\"):\n section = s[1:-1].strip()\n data.setdefault(section, {})\n continue\n if \"=\" not in s:\n continue\n k, v = s.split(\"=\", 1)\n k, v = k.strip(), v.strip().strip('\"').strip(\"'\")\n if v.lower() in (\"true\", \"false\"):\n v = v.lower() == \"true\"\n target = data[section] if section is not None else data\n target[k] = v\n return data\n\n\ndef load() -> dict:\n \"\"\"Parse the config and export its ``[env]`` table (without clobbering real\n env vars). Returns the top-level settings dict.\"\"\"\n p = path()\n if not os.path.isfile(p):\n return {}\n try:\n import tomllib\n with open(p, \"rb\") as f:\n data = tomllib.load(f)\n except ImportError:\n data = _load_simple(p)\n except Exception as e: # malformed TOML — warn, don't crash\n sys.stderr.write(f\"# cc-copilot: ignoring malformed config {p}: {e}\\n\")\n return {}\n env = data.get(\"env\")\n if isinstance(env, dict):\n for k, v in env.items():\n os.environ.setdefault(str(k), str(v)) # real env wins\n return data\n\n\ndef apply_defaults(args) -> None:\n \"\"\"Fill argparse defaults from the config, only where the user didn't say.\"\"\"\n data = load()\n # backend: surface as CC_COPILOT_BACKEND so resolve() picks it up when no\n # --backend flag and no real env var were given.\n if getattr(args, \"backend\", None) is None and not os.environ.get(\"CC_COPILOT_BACKEND\"):\n b = data.get(\"backend\")\n if b:\n os.environ[\"CC_COPILOT_BACKEND\"] = str(b)\n # model: a plain default when --model wasn't passed.\n if hasattr(args, \"model\") and getattr(args, \"model\", None) is None and data.get(\"model\"):\n args.model = str(data[\"model\"])\n # state dir: surface [history].dir as CC_COPILOT_STATE_DIR (mirrors backend).\n if not os.environ.get(\"CC_COPILOT_STATE_DIR\"):\n h = data.get(\"history\")\n d = h.get(\"dir\") if isinstance(h, dict) else None\n if d:\n os.environ[\"CC_COPILOT_STATE_DIR\"] = os.path.expanduser(str(d))\n # persist: fill the chat/cockpit toggle from [history].enabled when unset.\n if hasattr(args, \"persist\") and getattr(args, \"persist\", None) is None:\n args.persist = history_enabled()\n # agent filter: surface --agent as CC_COPILOT_AGENTS so the sources\n # dispatcher scopes discovery (mirrors how --backend is surfaced).\n agent = getattr(args, \"agent\", None)\n if agent:\n os.environ[\"CC_COPILOT_AGENTS\"] = \",\".join(agent)\n\n\ndef history_enabled() -> bool:\n \"\"\"Whether to persist copilot conversations. Env wins, then the file, then on.\n\n ``CC_COPILOT_HISTORY=0|false|no|off`` (or empty) forces it off for a single\n invocation; otherwise ``[history].enabled`` from the config decides; default on.\n \"\"\"\n env = os.environ.get(\"CC_COPILOT_HISTORY\")\n if env is not None:\n return env.strip().lower() not in (\"0\", \"false\", \"no\", \"off\", \"\")\n h = load().get(\"history\")\n if isinstance(h, dict) and \"enabled\" in h:\n return bool(h[\"enabled\"])\n return True\n\n\ndef agents_enabled():\n \"\"\"Agent sources to observe, or ``None`` for 'all available'.\n\n Reads ``[agents] enabled`` from the config. Accepts a TOML array\n (``[\"claude\", \"codex\"]``) or, under the no-tomllib fallback parser, a\n comma/space string. ``$CC_COPILOT_AGENTS`` is handled in the sources\n dispatcher and takes precedence over this.\n \"\"\"\n a = load().get(\"agents\")\n if isinstance(a, dict) and a.get(\"enabled\") is not None:\n v = a[\"enabled\"]\n if isinstance(v, (list, tuple)):\n names = [str(x).strip().lower() for x in v if str(x).strip()]\n else:\n names = [p.strip().lower() for p in str(v).replace(\",\", \" \").split() if p.strip()]\n return names or None\n return None\n\n\ndef init_file() -> str:\n \"\"\"Write the template to the config path if absent. Returns a status line.\"\"\"\n p = path()\n if os.path.exists(p):\n return f\"config already exists: {p} (left untouched)\"\n with open(p, \"w\", encoding=\"utf-8\") as f:\n f.write(TEMPLATE)\n try:\n os.chmod(p, 0o600)\n except OSError:\n pass\n return f\"wrote starter config: {p} (chmod 600) — edit it to set backend/model/keys\"\n"}, "5c90e2248e3a43c1728516e36944cd5b69902257": {"t": 1785768266.6363938, "n": 7327, "h": "60a87f07ee350946", "c": "\"\"\"Optional config file so you set a default backend / model / keys once.\n\nLooked up at ``$CC_COPILOT_CONFIG`` or ``~/.cc-copilot.toml``. Holds:\n\n backend = \"codex\" # default LLM backend\n model = \"deepseek-reasoner\"# default model for it\n [env] # exported as env vars (real env wins)\n DEEPSEEK_API_KEY = \"sk-…\"\n CC_COPILOT_API_BASE = \"http://localhost:11434\"\n\nPrecedence everywhere: explicit CLI flag > real environment variable > this\nfile > built-in default. The deterministic core (`brief`/`check`) ignores all\nof it — only the language features read a backend.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport sys\n\n\nTEMPLATE = '''\\\n# cc-copilot config (~/.cc-copilot.toml)\n# Defaults for the LLM-backed commands (ask / chat / brief --narrate).\n# The deterministic core (brief / check / alerts) needs none of this.\n\n# Default backend: codex | claude | deepseek | openai | openrouter | ollama | gemini | llm\nbackend = \"codex\"\n\n# Default model for that backend (optional — omit to use the backend's own default)\n# model = \"deepseek-reasoner\"\n\n# Secrets + provider settings, exported as environment variables.\n# Real env vars always win over these. Keep this file private (chmod 600).\n[env]\n# DEEPSEEK_API_KEY = \"sk-...\"\n# OPENAI_API_KEY = \"sk-...\"\n# OPENROUTER_API_KEY = \"sk-...\"\n# Any OpenAI-compatible endpoint (vLLM, LM Studio, Groq, a proxy, …):\n# CC_COPILOT_API_BASE = \"http://localhost:11434\"\n# CC_COPILOT_API_KEY = \"...\"\n# CC_COPILOT_MODEL = \"qwen2.5\"\n\n# Persist your copilot Q&A so switching sessions / relaunching restores prior\n# chats. Stored locally under $CC_COPILOT_STATE_DIR (default ~/.local/state/\n# cc-copilot), dir 0700 / files 0600. These files hold your questions and the\n# copilot's answers in plaintext — set enabled = false to keep everything in\n# memory only. Never written under ~/.claude.\n[history]\nenabled = true\n# dir = \"~/.local/state/cc-copilot\" # or set $CC_COPILOT_STATE_DIR\n\n# Which coding agents to observe. Default: every agent whose storage exists on\n# this machine (Claude Code under ~/.claude, Codex under ~/.codex). One cockpit\n# can watch sessions from several agents at once, grouped by project. Restrict\n# the set here, or per-invocation with --agent / $CC_COPILOT_AGENTS.\n# [agents]\n# enabled = [\"claude\", \"codex\"]\n'''\n\n\ndef path() -> str:\n return os.environ.get(\"CC_COPILOT_CONFIG\") or os.path.expanduser(\"~/.cc-copilot.toml\")\n\n\ndef _load_simple(p: str) -> dict:\n \"\"\"Minimal fallback parser (key = \"value\", [tables], # comments) for Pythons\n without ``tomllib``. Every ``[section]`` nests into its own dict, so e.g.\n ``[history] enabled = false`` reads back as ``data[\"history\"][\"enabled\"]``.\"\"\"\n data, section = {}, None\n try:\n with open(p, encoding=\"utf-8\") as fh:\n lines = fh.read().splitlines()\n except OSError:\n return {}\n for raw in lines:\n s = raw.strip()\n if not s or s.startswith(\"#\"):\n continue\n if s.startswith(\"[\") and s.endswith(\"]\"):\n section = s[1:-1].strip()\n data.setdefault(section, {})\n continue\n if \"=\" not in s:\n continue\n k, v = s.split(\"=\", 1)\n k, v = k.strip(), v.strip().strip('\"').strip(\"'\")\n if v.lower() in (\"true\", \"false\"):\n v = v.lower() == \"true\"\n target = data[section] if section is not None else data\n target[k] = v\n return data\n\n\ndef load() -> dict:\n \"\"\"Parse the config and export its ``[env]`` table (without clobbering real\n env vars). Returns the top-level settings dict.\"\"\"\n p = path()\n if not os.path.isfile(p):\n return {}\n try:\n import tomllib\n with open(p, \"rb\") as f:\n data = tomllib.load(f)\n except ImportError:\n data = _load_simple(p)\n except Exception as e: # malformed TOML — warn, don't crash\n sys.stderr.write(f\"# cc-copilot: ignoring malformed config {p}: {e}\\n\")\n return {}\n env = data.get(\"env\")\n if isinstance(env, dict):\n for k, v in env.items():\n os.environ.setdefault(str(k), str(v)) # real env wins\n return data\n\n\ndef apply_defaults(args) -> None:\n \"\"\"Fill argparse defaults from the config, only where the user didn't say.\"\"\"\n data = load()\n # backend: surface as CC_COPILOT_BACKEND so resolve() picks it up when no\n # --backend flag and no real env var were given.\n if getattr(args, \"backend\", None) is None and not os.environ.get(\"CC_COPILOT_BACKEND\"):\n b = data.get(\"backend\")\n if b:\n os.environ[\"CC_COPILOT_BACKEND\"] = str(b)\n # model: a plain default when --model wasn't passed.\n if hasattr(args, \"model\") and getattr(args, \"model\", None) is None and data.get(\"model\"):\n args.model = str(data[\"model\"])\n # state dir: surface [history].dir as CC_COPILOT_STATE_DIR (mirrors backend).\n if not os.environ.get(\"CC_COPILOT_STATE_DIR\"):\n h = data.get(\"history\")\n d = h.get(\"dir\") if isinstance(h, dict) else None\n if d:\n os.environ[\"CC_COPILOT_STATE_DIR\"] = os.path.expanduser(str(d))\n # persist: fill the chat/cockpit toggle from [history].enabled when unset.\n if hasattr(args, \"persist\") and getattr(args, \"persist\", None) is None:\n args.persist = history_enabled()\n # agent filter: surface --agent as CC_COPILOT_AGENTS so the sources\n # dispatcher scopes discovery (mirrors how --backend is surfaced).\n agent = getattr(args, \"agent\", None)\n if agent:\n os.environ[\"CC_COPILOT_AGENTS\"] = \",\".join(agent)\n\n\ndef history_enabled() -> bool:\n \"\"\"Whether to persist copilot conversations. Env wins, then the file, then on.\n\n ``CC_COPILOT_HISTORY=0|false|no|off`` (or empty) forces it off for a single\n invocation; otherwise ``[history].enabled`` from the config decides; default on.\n \"\"\"\n env = os.environ.get(\"CC_COPILOT_HISTORY\")\n if env is not None:\n return env.strip().lower() not in (\"0\", \"false\", \"no\", \"off\", \"\")\n h = load().get(\"history\")\n if isinstance(h, dict) and \"enabled\" in h:\n return bool(h[\"enabled\"])\n return True\n\n\ndef agents_enabled():\n \"\"\"Agent sources to observe, or ``None`` for 'all available'.\n\n Reads ``[agents] enabled`` from the config. Accepts a TOML array\n (``[\"claude\", \"codex\"]``) or, under the no-tomllib fallback parser, a\n comma/space string. ``$CC_COPILOT_AGENTS`` is handled in the sources\n dispatcher and takes precedence over this.\n \"\"\"\n a = load().get(\"agents\")\n if isinstance(a, dict) and a.get(\"enabled\") is not None:\n v = a[\"enabled\"]\n if isinstance(v, (list, tuple)):\n names = [str(x).strip().lower() for x in v if str(x).strip()]\n else:\n names = [p.strip().lower() for p in str(v).replace(\",\", \" \").split() if p.strip()]\n return names or None\n return None\n\n\ndef init_file() -> str:\n \"\"\"Write the template to the config path if absent. Returns a status line.\"\"\"\n p = path()\n if os.path.exists(p):\n return f\"config already exists: {p} (left untouched)\"\n with open(p, \"w\", encoding=\"utf-8\") as f:\n f.write(TEMPLATE)\n try:\n os.chmod(p, 0o600)\n except OSError:\n pass\n return f\"wrote starter config: {p} (chmod 600) — edit it to set backend/model/keys\"\n"}, "0a600593173541e9cce4f8cb5b8821a342f9b8d1": {"t": 1785768269.9116652, "n": 5974, "h": "04807af520222f90", "c": "\"\"\"Optional config file so you set a default backend / model / keys once.\n\nLooked up at ``$CC_COPILOT_CONFIG`` or ``~/.cc-copilot.toml``. Holds:\n\n backend = \"codex\" # default LLM backend\n model = \"deepseek-reasoner\"# default model for it\n [env] # exported as env vars (real env wins)\n DEEPSEEK_API_KEY = \"sk-…\"\n CC_COPILOT_API_BASE = \"http://localhost:11434\"\n\nPrecedence everywhere: explicit CLI flag > real environment variable > this\nfile > built-in default. The deterministic core (`brief`/`check`) ignores all\nof it — only the language features read a backend.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport sys\n\n\nTEMPLATE = '''\\\n# cc-copilot config (~/.cc-copilot.toml)\n# Defaults for the LLM-backed commands (ask / chat / brief --narrate).\n# The deterministic core (brief / check / alerts) needs none of this.\n\n# Default backend: codex | claude | deepseek | openai | openrouter | ollama | gemini | llm\nbackend = \"codex\"\n\n# Default model for that backend (optional — omit to use the backend's own default)\n# model = \"deepseek-reasoner\"\n\n# Secrets + provider settings, exported as environment variables.\n# Real env vars always win over these. Keep this file private (chmod 600).\n[env]\n# DEEPSEEK_API_KEY = \"sk-...\"\n# OPENAI_API_KEY = \"sk-...\"\n# OPENROUTER_API_KEY = \"sk-...\"\n# Any OpenAI-compatible endpoint (vLLM, LM Studio, Groq, a proxy, …):\n# CC_COPILOT_API_BASE = \"http://localhost:11434\"\n# CC_COPILOT_API_KEY = \"...\"\n# CC_COPILOT_MODEL = \"qwen2.5\"\n\n# Persist your copilot Q&A so switching sessions / relaunching restores prior\n# chats. Stored locally under $CC_COPILOT_STATE_DIR (default ~/.local/state/\n# cc-copilot), dir 0700 / files 0600. These files hold your questions and the\n# copilot's answers in plaintext — set enabled = false to keep everything in\n# memory only. Never written under ~/.claude.\n[history]\nenabled = true\n# dir = \"~/.local/state/cc-copilot\" # or set $CC_COPILOT_STATE_DIR\n'''\n\n\ndef path() -> str:\n return os.environ.get(\"CC_COPILOT_CONFIG\") or os.path.expanduser(\"~/.cc-copilot.toml\")\n\n\ndef _load_simple(p: str) -> dict:\n \"\"\"Minimal fallback parser (key = \"value\", [tables], # comments) for Pythons\n without ``tomllib``. Every ``[section]`` nests into its own dict, so e.g.\n ``[history] enabled = false`` reads back as ``data[\"history\"][\"enabled\"]``.\"\"\"\n data, section = {}, None\n try:\n with open(p, encoding=\"utf-8\") as fh:\n lines = fh.read().splitlines()\n except OSError:\n return {}\n for raw in lines:\n s = raw.strip()\n if not s or s.startswith(\"#\"):\n continue\n if s.startswith(\"[\") and s.endswith(\"]\"):\n section = s[1:-1].strip()\n data.setdefault(section, {})\n continue\n if \"=\" not in s:\n continue\n k, v = s.split(\"=\", 1)\n k, v = k.strip(), v.strip().strip('\"').strip(\"'\")\n if v.lower() in (\"true\", \"false\"):\n v = v.lower() == \"true\"\n target = data[section] if section is not None else data\n target[k] = v\n return data\n\n\ndef load() -> dict:\n \"\"\"Parse the config and export its ``[env]`` table (without clobbering real\n env vars). Returns the top-level settings dict.\"\"\"\n p = path()\n if not os.path.isfile(p):\n return {}\n try:\n import tomllib\n with open(p, \"rb\") as f:\n data = tomllib.load(f)\n except ImportError:\n data = _load_simple(p)\n except Exception as e: # malformed TOML — warn, don't crash\n sys.stderr.write(f\"# cc-copilot: ignoring malformed config {p}: {e}\\n\")\n return {}\n env = data.get(\"env\")\n if isinstance(env, dict):\n for k, v in env.items():\n os.environ.setdefault(str(k), str(v)) # real env wins\n return data\n\n\ndef apply_defaults(args) -> None:\n \"\"\"Fill argparse defaults from the config, only where the user didn't say.\"\"\"\n data = load()\n # backend: surface as CC_COPILOT_BACKEND so resolve() picks it up when no\n # --backend flag and no real env var were given.\n if getattr(args, \"backend\", None) is None and not os.environ.get(\"CC_COPILOT_BACKEND\"):\n b = data.get(\"backend\")\n if b:\n os.environ[\"CC_COPILOT_BACKEND\"] = str(b)\n # model: a plain default when --model wasn't passed.\n if hasattr(args, \"model\") and getattr(args, \"model\", None) is None and data.get(\"model\"):\n args.model = str(data[\"model\"])\n # state dir: surface [history].dir as CC_COPILOT_STATE_DIR (mirrors backend).\n if not os.environ.get(\"CC_COPILOT_STATE_DIR\"):\n h = data.get(\"history\")\n d = h.get(\"dir\") if isinstance(h, dict) else None\n if d:\n os.environ[\"CC_COPILOT_STATE_DIR\"] = os.path.expanduser(str(d))\n # persist: fill the chat/cockpit toggle from [history].enabled when unset.\n if hasattr(args, \"persist\") and getattr(args, \"persist\", None) is None:\n args.persist = history_enabled()\n\n\ndef history_enabled() -> bool:\n \"\"\"Whether to persist copilot conversations. Env wins, then the file, then on.\n\n ``CC_COPILOT_HISTORY=0|false|no|off`` (or empty) forces it off for a single\n invocation; otherwise ``[history].enabled`` from the config decides; default on.\n \"\"\"\n env = os.environ.get(\"CC_COPILOT_HISTORY\")\n if env is not None:\n return env.strip().lower() not in (\"0\", \"false\", \"no\", \"off\", \"\")\n h = load().get(\"history\")\n if isinstance(h, dict) and \"enabled\" in h:\n return bool(h[\"enabled\"])\n return True\n\n\ndef init_file() -> str:\n \"\"\"Write the template to the config path if absent. Returns a status line.\"\"\"\n p = path()\n if os.path.exists(p):\n return f\"config already exists: {p} (left untouched)\"\n with open(p, \"w\", encoding=\"utf-8\") as f:\n f.write(TEMPLATE)\n try:\n os.chmod(p, 0o600)\n except OSError:\n pass\n return f\"wrote starter config: {p} (chmod 600) — edit it to set backend/model/keys\"\n"}, "93ef50d2857d4d1ad0eb58b8c7bbafe7bbb4d74e": {"t": 1785768291.6573203, "n": 5792, "h": "34910743e86a68d3", "c": "\"\"\"Optional config file so you set a default backend / model / keys once.\n\nLooked up at ``$CC_COPILOT_CONFIG`` or ``~/.cc-copilot.toml``. Holds:\n\n backend = \"codex\" # default LLM backend\n model = \"deepseek-reasoner\"# default model for it\n [env] # exported as env vars (real env wins)\n DEEPSEEK_API_KEY = \"sk-…\"\n CC_COPILOT_API_BASE = \"http://localhost:11434\"\n\nPrecedence everywhere: explicit CLI flag > real environment variable > this\nfile > built-in default. The deterministic core (`brief`/`check`) ignores all\nof it — only the language features read a backend.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport sys\n\n\nTEMPLATE = '''\\\n# cc-copilot config (~/.cc-copilot.toml)\n# Defaults for the LLM-backed commands (ask / chat / brief --narrate).\n# The deterministic core (brief / check / alerts) needs none of this.\n\n# Default backend: claude | codex | deepseek | openai | openrouter | ollama | gemini | llm\nbackend = \"claude\"\n\n# Default model for that backend (optional — omit to use the backend's own default)\n# model = \"deepseek-reasoner\"\n\n# Secrets + provider settings, exported as environment variables.\n# Real env vars always win over these. Keep this file private (chmod 600).\n[env]\n# DEEPSEEK_API_KEY = \"sk-...\"\n# OPENAI_API_KEY = \"sk-...\"\n# OPENROUTER_API_KEY = \"sk-...\"\n# Any OpenAI-compatible endpoint (vLLM, LM Studio, Groq, a proxy, …):\n# CC_COPILOT_API_BASE = \"http://localhost:11434\"\n# CC_COPILOT_API_KEY = \"...\"\n# CC_COPILOT_MODEL = \"qwen2.5\"\n\n# Persist your copilot Q&A so switching sessions / relaunching restores prior\n# chats. Stored locally under $CC_COPILOT_STATE_DIR (default ~/.local/state/\n# cc-copilot), dir 0700 / files 0600. These files hold your questions and the\n# copilot's answers in plaintext — set enabled = false to keep everything in\n# memory only. Never written under ~/.claude.\n[history]\nenabled = true\n# dir = \"~/.local/state/cc-copilot\" # or set $CC_COPILOT_STATE_DIR\n'''\n\n\ndef path() -> str:\n return os.environ.get(\"CC_COPILOT_CONFIG\") or os.path.expanduser(\"~/.cc-copilot.toml\")\n\n\ndef _load_simple(p: str) -> dict:\n \"\"\"Minimal fallback parser (key = \"value\", [env] sections, # comments) for\n Pythons without ``tomllib``.\"\"\"\n data, env, section = {}, {}, None\n try:\n lines = open(p, encoding=\"utf-8\").read().splitlines()\n except OSError:\n return {}\n for raw in lines:\n s = raw.strip()\n if not s or s.startswith(\"#\"):\n continue\n if s.startswith(\"[\") and s.endswith(\"]\"):\n section = s[1:-1].strip()\n continue\n if \"=\" not in s:\n continue\n k, v = s.split(\"=\", 1)\n k, v = k.strip(), v.strip().strip('\"').strip(\"'\")\n if v.lower() in (\"true\", \"false\"):\n v = v.lower() == \"true\"\n (env if section == \"env\" else data)[k] = v\n if env:\n data[\"env\"] = env\n return data\n\n\ndef load() -> dict:\n \"\"\"Parse the config and export its ``[env]`` table (without clobbering real\n env vars). Returns the top-level settings dict.\"\"\"\n p = path()\n if not os.path.isfile(p):\n return {}\n try:\n import tomllib\n with open(p, \"rb\") as f:\n data = tomllib.load(f)\n except ImportError:\n data = _load_simple(p)\n except Exception as e: # malformed TOML — warn, don't crash\n sys.stderr.write(f\"# cc-copilot: ignoring malformed config {p}: {e}\\n\")\n return {}\n env = data.get(\"env\")\n if isinstance(env, dict):\n for k, v in env.items():\n os.environ.setdefault(str(k), str(v)) # real env wins\n return data\n\n\ndef apply_defaults(args) -> None:\n \"\"\"Fill argparse defaults from the config, only where the user didn't say.\"\"\"\n data = load()\n # backend: surface as CC_COPILOT_BACKEND so resolve() picks it up when no\n # --backend flag and no real env var were given.\n if getattr(args, \"backend\", None) is None and not os.environ.get(\"CC_COPILOT_BACKEND\"):\n b = data.get(\"backend\")\n if b:\n os.environ[\"CC_COPILOT_BACKEND\"] = str(b)\n # model: a plain default when --model wasn't passed.\n if hasattr(args, \"model\") and getattr(args, \"model\", None) is None and data.get(\"model\"):\n args.model = str(data[\"model\"])\n # state dir: surface [history].dir as CC_COPILOT_STATE_DIR (mirrors backend).\n if not os.environ.get(\"CC_COPILOT_STATE_DIR\"):\n h = data.get(\"history\")\n d = h.get(\"dir\") if isinstance(h, dict) else None\n if d:\n os.environ[\"CC_COPILOT_STATE_DIR\"] = os.path.expanduser(str(d))\n # persist: fill the chat/cockpit toggle from [history].enabled when unset.\n if hasattr(args, \"persist\") and getattr(args, \"persist\", None) is None:\n args.persist = history_enabled()\n\n\ndef history_enabled() -> bool:\n \"\"\"Whether to persist copilot conversations. Env wins, then the file, then on.\n\n ``CC_COPILOT_HISTORY=0|false|no|off`` (or empty) forces it off for a single\n invocation; otherwise ``[history].enabled`` from the config decides; default on.\n \"\"\"\n env = os.environ.get(\"CC_COPILOT_HISTORY\")\n if env is not None:\n return env.strip().lower() not in (\"0\", \"false\", \"no\", \"off\", \"\")\n h = load().get(\"history\")\n if isinstance(h, dict) and \"enabled\" in h:\n return bool(h[\"enabled\"])\n return True\n\n\ndef init_file() -> str:\n \"\"\"Write the template to the config path if absent. Returns a status line.\"\"\"\n p = path()\n if os.path.exists(p):\n return f\"config already exists: {p} (left untouched)\"\n with open(p, \"w\", encoding=\"utf-8\") as f:\n f.write(TEMPLATE)\n try:\n os.chmod(p, 0o600)\n except OSError:\n pass\n return f\"wrote starter config: {p} (chmod 600) — edit it to set backend/model/keys\"\n"}, "3567fd4ae9d5798741379b7a40e809d702029f8a": {"t": 1785768292.921515, "n": 4270, "h": "bf68df66f8a15f9d", "c": "\"\"\"Optional config file so you set a default backend / model / keys once.\n\nLooked up at ``$CC_COPILOT_CONFIG`` or ``~/.cc-copilot.toml``. Holds:\n\n backend = \"codex\" # default LLM backend\n model = \"deepseek-reasoner\"# default model for it\n [env] # exported as env vars (real env wins)\n DEEPSEEK_API_KEY = \"sk-…\"\n CC_COPILOT_API_BASE = \"http://localhost:11434\"\n\nPrecedence everywhere: explicit CLI flag > real environment variable > this\nfile > built-in default. The deterministic core (`brief`/`check`) ignores all\nof it — only the language features read a backend.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport sys\n\n\nTEMPLATE = '''\\\n# cc-copilot config (~/.cc-copilot.toml)\n# Defaults for the LLM-backed commands (ask / chat / brief --narrate).\n# The deterministic core (brief / check / alerts) needs none of this.\n\n# Default backend: claude | codex | deepseek | openai | openrouter | ollama | gemini | llm\nbackend = \"claude\"\n\n# Default model for that backend (optional — omit to use the backend's own default)\n# model = \"deepseek-reasoner\"\n\n# Secrets + provider settings, exported as environment variables.\n# Real env vars always win over these. Keep this file private (chmod 600).\n[env]\n# DEEPSEEK_API_KEY = \"sk-...\"\n# OPENAI_API_KEY = \"sk-...\"\n# OPENROUTER_API_KEY = \"sk-...\"\n# Any OpenAI-compatible endpoint (vLLM, LM Studio, Groq, a proxy, …):\n# CC_COPILOT_API_BASE = \"http://localhost:11434\"\n# CC_COPILOT_API_KEY = \"...\"\n# CC_COPILOT_MODEL = \"qwen2.5\"\n'''\n\n\ndef path() -> str:\n return os.environ.get(\"CC_COPILOT_CONFIG\") or os.path.expanduser(\"~/.cc-copilot.toml\")\n\n\ndef _load_simple(p: str) -> dict:\n \"\"\"Minimal fallback parser (key = \"value\", [env] sections, # comments) for\n Pythons without ``tomllib``.\"\"\"\n data, env, section = {}, {}, None\n try:\n lines = open(p, encoding=\"utf-8\").read().splitlines()\n except OSError:\n return {}\n for raw in lines:\n s = raw.strip()\n if not s or s.startswith(\"#\"):\n continue\n if s.startswith(\"[\") and s.endswith(\"]\"):\n section = s[1:-1].strip()\n continue\n if \"=\" not in s:\n continue\n k, v = s.split(\"=\", 1)\n k, v = k.strip(), v.strip().strip('\"').strip(\"'\")\n if v.lower() in (\"true\", \"false\"):\n v = v.lower() == \"true\"\n (env if section == \"env\" else data)[k] = v\n if env:\n data[\"env\"] = env\n return data\n\n\ndef load() -> dict:\n \"\"\"Parse the config and export its ``[env]`` table (without clobbering real\n env vars). Returns the top-level settings dict.\"\"\"\n p = path()\n if not os.path.isfile(p):\n return {}\n try:\n import tomllib\n with open(p, \"rb\") as f:\n data = tomllib.load(f)\n except ImportError:\n data = _load_simple(p)\n except Exception as e: # malformed TOML — warn, don't crash\n sys.stderr.write(f\"# cc-copilot: ignoring malformed config {p}: {e}\\n\")\n return {}\n env = data.get(\"env\")\n if isinstance(env, dict):\n for k, v in env.items():\n os.environ.setdefault(str(k), str(v)) # real env wins\n return data\n\n\ndef apply_defaults(args) -> None:\n \"\"\"Fill argparse defaults from the config, only where the user didn't say.\"\"\"\n data = load()\n # backend: surface as CC_COPILOT_BACKEND so resolve() picks it up when no\n # --backend flag and no real env var were given.\n if getattr(args, \"backend\", None) is None and not os.environ.get(\"CC_COPILOT_BACKEND\"):\n b = data.get(\"backend\")\n if b:\n os.environ[\"CC_COPILOT_BACKEND\"] = str(b)\n # model: a plain default when --model wasn't passed.\n if hasattr(args, \"model\") and getattr(args, \"model\", None) is None and data.get(\"model\"):\n args.model = str(data[\"model\"])\n\n\ndef init_file() -> str:\n \"\"\"Write the template to the config path if absent. Returns a status line.\"\"\"\n p = path()\n if os.path.exists(p):\n return f\"config already exists: {p} (left untouched)\"\n with open(p, \"w\", encoding=\"utf-8\") as f:\n f.write(TEMPLATE)\n try:\n os.chmod(p, 0o600)\n except OSError:\n pass\n return f\"wrote starter config: {p} (chmod 600) — edit it to set backend/model/keys\"\n"}}, "cccopilot/onboard.py": {"9d276a78fecdd6c40c4581cf54fd60ad39182151": {"t": 1785674095.4181523, "n": 14676, "h": "4aa380688bffc50a", "c": "\"\"\"First-run onboarding: pick the LLM backend (and capture an API key) once,\nthen write ``~/.cc-copilot.toml`` so it never asks again.\n\nThe config file's *existence* is the \"already onboarded\" sentinel — the moment a\nfile is written (even a \"Skip\" that picks nothing), :func:`needs_onboarding`\nreturns False and nothing prompts again. The logic here is UI-agnostic: the\nterminal wizard (``cc-copilot init``) and the cockpit's ``WelcomeScreen`` both\ncall :func:`detect` / :func:`write_choice` / :func:`apply_to_env`, so the two\nsurfaces can never drift.\n\nWhat \"model\" means here maps straight onto :mod:`cccopilot.backends`:\n - **Claude** / **Codex** are *CLI* backends — auth is the CLI's own (your\n Claude subscription, your ChatGPT login); **no API key** is stored.\n - **OpenAI / DeepSeek / OpenRouter** and the other curated providers are\n OpenAI-compatible *API* backends — they need a key, which we write into the\n ``[env]`` table (chmod 600).\n - **Skip** writes a config with no backend chosen; the default (``codex``)\n applies and the wizard stops nagging.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nfrom dataclasses import dataclass\n\nfrom . import config as CFG, backends as BK, models as MODELS\n\n\n@dataclass\nclass Choice:\n name: str # backend name in backends.registry() (\"\" for skip)\n label: str # friendly display name (\"Claude\", \"OpenAI\", …)\n kind: str # \"cli\" | \"api\" | \"skip\"\n blurb: str # one-line description of the auth model\n key_env: str = \"\" # env var the API key lives under (api only)\n default_model: str = \"\" # prefilled, editable; \"\" → backend's own default\n brand_hex: str = \"\" # identity hue for the TUI row\n featured: bool = True # shown in the compact WelcomeScreen modal\n\n\ndef _api(name, label, key_env, blurb=\"\", featured=False, brand_hex=\"\") -> Choice:\n \"\"\"An API choice whose default model comes from the curated catalog.\"\"\"\n return Choice(name, label, \"api\", blurb or f\"{label} API — needs a key\",\n key_env=key_env, default_model=MODELS.default_for(name),\n brand_hex=brand_hex, featured=featured)\n\n\n# Every key-needing provider has a Choice (that is what powers the inline\n# key-prompt when /model switches to it); the WelcomeScreen modal shows only\n# the FEATURED subset so the first-run screen stays compact. `cc-copilot init`\n# lists everything. ollama/llm/custom stay power-user, file-only.\nCHOICES = [\n Choice(\"claude\", \"Claude\", \"cli\",\n \"uses your Claude Code subscription — no API key\", brand_hex=\"#cb7d5b\"),\n Choice(\"codex\", \"Codex\", \"cli\",\n \"uses your ChatGPT (Codex) login — no API key\", brand_hex=\"#347ff2\"),\n _api(\"deepseek\", \"DeepSeek\", \"DEEPSEEK_API_KEY\",\n featured=True, brand_hex=\"#8b5cf6\"),\n _api(\"gemini-api\", \"Gemini API\", \"GEMINI_API_KEY\"),\n _api(\"groq\", \"Groq\", \"GROQ_API_KEY\"),\n _api(\"moonshot\", \"Moonshot Kimi\", \"MOONSHOT_API_KEY\"),\n _api(\"openai\", \"OpenAI\", \"OPENAI_API_KEY\", featured=True),\n _api(\"openrouter\", \"OpenRouter\", \"OPENROUTER_API_KEY\",\n \"OpenRouter API — any model, needs a key\", featured=True),\n _api(\"qwen\", \"Qwen (DashScope)\", \"DASHSCOPE_API_KEY\"),\n _api(\"xai\", \"xAI Grok\", \"XAI_API_KEY\"),\n _api(\"zai\", \"Z.ai GLM\", \"ZAI_API_KEY\"),\n _api(\"ollama-cloud\", \"Ollama Cloud\", \"OLLAMA_API_KEY\"),\n Choice(\"\", \"Skip for now\", \"skip\",\n \"deterministic recaps only — set a model later with `cc-copilot init`\"),\n]\n\n\n@dataclass\nclass Detected:\n choice: Choice\n ready: bool # usable right now (CLI on PATH, or key already set)\n status: str # short human status line\n\n\ndef choice_for(name: str) -> Choice:\n key = (name or \"\").strip().lower()\n for c in CHOICES:\n if c.name == key:\n return c\n if key in (\"skip\", \"\"):\n return CHOICES[-1]\n raise ValueError(f\"unknown onboarding choice {name!r}\")\n\n\ndef choice_for_or_none(name: str):\n \"\"\"Like :func:`choice_for` but returns None for backends outside the curated\n set (ollama / custom / gemini / llm) instead of raising — callers that switch\n to *any* backend use this to ask \"is this a key-needing API provider?\".\"\"\"\n try:\n c = choice_for(name)\n except ValueError:\n return None\n return c if c.kind != \"skip\" else None\n\n\ndef detect(featured_only: bool = False) -> list:\n \"\"\"Each curated choice annotated with whether it's usable on this machine.\n ``featured_only`` trims to the compact set the WelcomeScreen modal shows\n (claude/codex/openai/deepseek/openrouter/skip); the terminal wizard and the\n /model switch path see every provider.\"\"\"\n reg = BK.registry()\n out = []\n for c in CHOICES:\n if featured_only and not c.featured and c.kind == \"api\":\n continue\n if c.kind == \"skip\":\n out.append(Detected(c, True, \"no model — recaps show the cited evidence only\"))\n continue\n be = reg.get(c.name)\n ready = bool(be) and be.available()\n if c.kind == \"cli\":\n status = (\"ready · \" + c.blurb.split(\"—\")[0].strip()) if ready \\\n else f\"`{c.name}` CLI not found on PATH — install it to use this\"\n else: # api\n status = f\"key set · {c.key_env}\" if ready \\\n else f\"needs key · {c.key_env}\"\n out.append(Detected(c, ready, status))\n return out\n\n\ndef _disabled() -> bool:\n v = os.environ.get(\"CC_COPILOT_NO_ONBOARD\", \"\")\n return v.strip().lower() not in (\"\", \"0\", \"false\", \"no\", \"off\")\n\n\ndef needs_onboarding() -> bool:\n \"\"\"True on the first run only: no config file yet, and not opted out via\n ``CC_COPILOT_NO_ONBOARD``. Writing any config (including a Skip) flips this\n to False permanently.\"\"\"\n if _disabled():\n return False\n return not os.path.isfile(CFG.path())\n\n\n# ── config rendering ─────────────────────────────────────────────────────\n\ndef _read_existing(path: str) -> dict:\n \"\"\"Parse an existing config WITHOUT the env-export side effect of\n ``config.load`` — we only want to preserve the user's other settings when a\n re-run rewrites the file.\"\"\"\n if not os.path.isfile(path):\n return {}\n try:\n import tomllib\n with open(path, \"rb\") as f:\n return tomllib.load(f)\n except ImportError:\n return CFG._load_simple(path)\n except Exception:\n return {}\n\n\ndef _esc(v: str) -> str:\n return str(v).replace(\"\\\\\", \"\\\\\\\\\").replace('\"', '\\\\\"')\n\n\ndef _existing_agents(existing: dict):\n \"\"\"The user's ``[agents] enabled`` list from a parsed config, or None.\n Accepts a TOML array (tomllib) or a comma/space string (fallback parser).\"\"\"\n a = existing.get(\"agents\")\n if not isinstance(a, dict) or a.get(\"enabled\") is None:\n return None\n v = a[\"enabled\"]\n if isinstance(v, (list, tuple)):\n names = [str(x).strip() for x in v if str(x).strip()]\n else:\n # the no-tomllib fallback parser hands back the raw array text\n # (e.g. '[\"claude\", \"codex\"]') as a string — unwrap it ourselves.\n s = str(v).strip()\n if s.startswith(\"[\") and s.endswith(\"]\"):\n s = s[1:-1]\n names = [t.strip().strip('\"').strip(\"'\")\n for t in s.replace(\",\", \" \").split()]\n names = [n for n in names if n]\n return names or None\n\n\ndef render_config(backend, model=\"\", env=None, history_enabled=True,\n history_dir=\"\", agents_enabled=None) -> str:\n \"\"\"Produce a clean, commented ``~/.cc-copilot.toml`` reflecting a choice.\n\n ``backend`` is a backend name, or None for \"skip\" (the default applies and a\n note explains how to set one). ``env`` is a dict of secrets to write active\n in ``[env]`` (real env still wins at load time).\"\"\"\n env = dict(env or {})\n lines = [\n \"# cc-copilot config (~/.cc-copilot.toml)\",\n \"# Written by `cc-copilot init` — edit freely; re-run it to change.\",\n \"# Defaults for the LLM-backed commands (ask / chat / cockpit / since\",\n \"# recap / brief --narrate). The deterministic core needs none of this.\",\n \"\",\n \"# backend: claude | codex | openai | deepseek | openrouter | moonshot | qwen\",\n \"# zai | groq | xai | gemini-api | ollama | ollama-cloud | llm | gemini\",\n ]\n if backend:\n lines.append(f'backend = \"{_esc(backend)}\"')\n else:\n lines.append('# backend = \"codex\" # not chosen yet — the default (codex) '\n 'applies; run `cc-copilot init` to pick one')\n lines.append(\"\")\n if model:\n # NB: keep value lines free of trailing inline comments — the no-tomllib\n # fallback parser (config._load_simple, used on Python < 3.11) doesn't\n # strip them, so an inline comment would leak into the parsed value.\n lines.append(\"# the model for that backend:\")\n lines.append(f'model = \"{_esc(model)}\"')\n else:\n lines.append(\"# model = \\\"...\\\" # optional — omit to use the backend's own default\")\n lines += [\n \"\",\n \"# Secrets + provider settings, exported as environment variables.\",\n \"# Real env vars always win over these. Keep this file private (chmod 600).\",\n \"[env]\",\n ]\n for k in sorted(env):\n lines.append(f'{k} = \"{_esc(env[k])}\"')\n # discoverable examples for the providers not already set\n for ex in (\"OPENAI_API_KEY\", \"DEEPSEEK_API_KEY\", \"OPENROUTER_API_KEY\"):\n if ex not in env:\n lines.append(f'# {ex} = \"sk-...\"')\n lines.append(\"# More provider keys: MOONSHOT_API_KEY, DASHSCOPE_API_KEY, GROQ_API_KEY,\")\n lines.append(\"# XAI_API_KEY, GEMINI_API_KEY, ZAI_API_KEY, OLLAMA_API_KEY.\")\n lines += [\n \"# Any OpenAI-compatible endpoint (vLLM, LM Studio, Ollama, a proxy, …):\",\n \"# CC_COPILOT_API_BASE = \\\"http://localhost:11434\\\"\",\n \"# CC_COPILOT_API_KEY = \\\"...\\\"\",\n \"# CC_COPILOT_MODEL = \\\"qwen3\\\"\",\n \"# Mainland-China DashScope (key must match the region):\",\n \"# DASHSCOPE_API_BASE = \\\"https://dashscope.aliyuncs.com/compatible-mode/v1\\\"\",\n \"\",\n \"# Persist your copilot Q&A so relaunching restores prior chats. Stored\",\n \"# locally under $CC_COPILOT_STATE_DIR (0700/0600), never under ~/.claude.\",\n \"[history]\",\n f\"enabled = {'true' if history_enabled else 'false'}\",\n ]\n if history_dir:\n lines.append(f'dir = \"{_esc(history_dir)}\"')\n lines += [\n \"\",\n \"# Which coding agents to observe (default: every one found on this machine).\",\n ]\n if agents_enabled:\n arr = \", \".join(f'\"{_esc(a)}\"' for a in agents_enabled)\n lines += [\"[agents]\", f\"enabled = [{arr}]\"]\n else:\n lines += [\"# [agents]\", '# enabled = [\"claude\", \"codex\"]']\n lines.append(\"\")\n return \"\\n\".join(lines)\n\n\ndef write_choice(name, model=\"\", key_value=\"\", path=None) -> str:\n \"\"\"Write the config for a chosen backend, preserving any existing ``[env]``\n secrets and the history toggle. Returns the path written (chmod 600).\"\"\"\n c = choice_for(name)\n p = path or CFG.path()\n existing = _read_existing(p)\n env = dict(existing.get(\"env\") or {})\n if c.kind == \"api\" and key_value:\n env[c.key_env] = key_value\n # Preserve every non-model setting the user may have curated, so changing\n # provider via `init --force` never silently moves their saved history or\n # re-enables agents they had turned off.\n hist = existing.get(\"history\") if isinstance(existing.get(\"history\"), dict) else {}\n hist_enabled = bool(hist.get(\"enabled\", True))\n hist_dir = str(hist.get(\"dir\") or \"\")\n agents_enabled = _existing_agents(existing)\n text = render_config(backend=(c.name or None), model=model, env=env,\n history_enabled=hist_enabled, history_dir=hist_dir,\n agents_enabled=agents_enabled)\n return _write_atomic(p, text)\n\n\ndef _write_atomic(p: str, text: str) -> str:\n \"\"\"Write ``text`` to ``p`` atomically at 0600 (secrets-safe). The temp file is\n created 0600 from the start (mkstemp honors that, ignoring umask) so an API key\n never lands in a world-readable file — not even in the window before chmod, and\n not if we crash mid-write. Same dir as the target so os.replace stays atomic.\"\"\"\n import tempfile\n d = os.path.dirname(p) or \".\"\n fd, tmp = tempfile.mkstemp(dir=d, prefix=\".cc-copilot-\", suffix=\".tmp\")\n try:\n with os.fdopen(fd, \"w\", encoding=\"utf-8\") as f:\n f.write(text)\n f.flush()\n os.fsync(f.fileno())\n os.replace(tmp, p)\n except BaseException:\n try:\n os.unlink(tmp)\n except OSError:\n pass\n raise\n try:\n os.chmod(p, 0o600) # also tighten if an older file was looser\n except OSError:\n pass\n return p\n\n\ndef persist_default(backend, model=\"\", path=None) -> str:\n \"\"\"Update the saved default backend/model, preserving the rest of the config\n (``[env]`` secrets, history, agents). Unlike :func:`write_choice` this accepts\n ANY backend name — the cockpit's ``/model`` can switch to non-curated backends\n (ollama, a custom endpoint, a free-form id) that have no onboarding Choice.\"\"\"\n p = path or CFG.path()\n existing = _read_existing(p)\n env = dict(existing.get(\"env\") or {})\n hist = existing.get(\"history\") if isinstance(existing.get(\"history\"), dict) else {}\n text = render_config(backend=(backend or None), model=(model or \"\"), env=env,\n history_enabled=bool(hist.get(\"enabled\", True)),\n history_dir=str(hist.get(\"dir\") or \"\"),\n agents_enabled=_existing_agents(existing))\n return _write_atomic(p, text)\n\n\ndef saved_default(path=None):\n \"\"\"The ``(backend, model)`` currently saved in the config file, or ``(\"\", \"\")``\n when none — lets a caller skip a redundant 'make this the default?' prompt.\"\"\"\n existing = _read_existing(path or CFG.path())\n return (str(existing.get(\"backend\") or \"\"), str(existing.get(\"model\") or \"\"))\n\n\ndef apply_to_env(name, model=\"\", key_value=\"\") -> None:\n \"\"\"Make a freshly-written choice take effect in THIS process, so a running\n cockpit / chat uses it immediately without a relaunch. Mirrors the file:\n surface the backend as ``CC_COPILOT_BACKEND`` and any key as its env var.\"\"\"\n c = choice_for(name)\n if c.kind == \"skip\":\n return\n os.environ[\"CC_COPILOT_BACKEND\"] = c.name\n if c.kind == \"api\" and key_value:\n os.environ[c.key_env] = key_value\n if model:\n os.environ[\"CC_COPILOT_MODEL\"] = model\n"}}}} |