Add cc-switch-dev-workflow skill

这个提交包含在:
X
2026-03-26 00:27:17 -07:00
父节点 fc8ad7c145
当前提交 dcb2a10ad8
修改 46 个文件,包含 3393 行新增0 行删除

查看文件

@@ -0,0 +1,286 @@
#!/usr/bin/env python3
"""Summarize the local CC Switch runtime without exposing raw secrets."""
from __future__ import annotations
import argparse
import json
import re
import sqlite3
from pathlib import Path
from typing import Any
from urllib.parse import urlparse
def safe_json_load(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
try:
return json.loads(path.read_text(encoding="utf-8"))
except json.JSONDecodeError:
return {}
def safe_json_parse(raw: str | None) -> dict[str, Any]:
if not raw:
return {}
try:
parsed = json.loads(raw)
except json.JSONDecodeError:
return {}
return parsed if isinstance(parsed, dict) else {}
def extract_toml_string(config_text: str, key: str) -> str | None:
match = re.search(rf'{re.escape(key)}\s*=\s*"([^"]+)"', config_text)
return match.group(1) if match else None
def extract_toml_bool(config_text: str, key: str) -> bool | None:
match = re.search(rf"{re.escape(key)}\s*=\s*(true|false)", config_text)
if not match:
return None
return match.group(1) == "true"
def extract_host(raw_url: str | None) -> str | None:
if not raw_url:
return None
parsed = urlparse(raw_url)
return parsed.netloc or raw_url
def count_regex(config_text: str, pattern: str) -> int:
return len(re.findall(pattern, config_text))
def sqlite_rows(db_path: Path, sql: str) -> list[dict[str, Any]]:
if not db_path.exists():
return []
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
try:
rows = conn.execute(sql).fetchall()
return [dict(row) for row in rows]
finally:
conn.close()
def summarize_codex_provider(row: dict[str, Any], endpoint_rows: list[dict[str, Any]]) -> dict[str, Any]:
parsed = safe_json_parse(row.get("settings_config"))
config_text = parsed.get("config", "")
endpoint = next(
(
item["url"]
for item in endpoint_rows
if item["provider_id"] == row["id"] and item["app_type"] == row["app_type"]
),
None,
)
return {
"id": row["id"],
"name": row["name"],
"endpoint_host": extract_host(endpoint or extract_toml_string(config_text, "base_url")),
"model": extract_toml_string(config_text, "model"),
"reasoning_effort": extract_toml_string(config_text, "model_reasoning_effort"),
"model_provider": extract_toml_string(config_text, "model_provider"),
"personality": extract_toml_string(config_text, "personality"),
"multi_agent_enabled": extract_toml_bool(config_text, "multi_agent"),
"trusted_project_count": count_regex(config_text, r'\[projects\."[^"]+"\]'),
"embedded_mcp_server_count": count_regex(config_text, r"\[mcp_servers\.[^\]]+\]"),
"embedded_skill_config_count": count_regex(config_text, r"\[\[skills\.config\]\]"),
}
def summarize_claude_provider(row: dict[str, Any], endpoint_rows: list[dict[str, Any]]) -> dict[str, Any]:
parsed = safe_json_parse(row.get("settings_config"))
env = parsed.get("env", {})
permissions = parsed.get("permissions", {})
endpoint = next(
(
item["url"]
for item in endpoint_rows
if item["provider_id"] == row["id"] and item["app_type"] == row["app_type"]
),
None,
)
allow_list = permissions.get("allow", []) if isinstance(permissions, dict) else []
return {
"id": row["id"],
"name": row["name"],
"endpoint_host": extract_host(endpoint or env.get("ANTHROPIC_BASE_URL")),
"current_model": env.get("ANTHROPIC_MODEL"),
"default_opus_model": env.get("ANTHROPIC_DEFAULT_OPUS_MODEL"),
"default_sonnet_model": env.get("ANTHROPIC_DEFAULT_SONNET_MODEL"),
"default_haiku_model": env.get("ANTHROPIC_DEFAULT_HAIKU_MODEL"),
"reasoning_model": env.get("ANTHROPIC_REASONING_MODEL"),
"allow_count": len(allow_list),
}
def fallback_flags(rows: list[dict[str, Any]]) -> dict[str, bool]:
haystack = "\n".join(
f"{row.get('name', '')} {row.get('settings_config', '')}".lower() for row in rows
)
return {
"glm": "glm" in haystack,
"kimi": "kimi" in haystack,
"minimax": "minimax" in haystack,
}
def collect_summary(root: Path) -> dict[str, Any]:
settings_path = root / "settings.json"
db_path = root / "cc-switch.db"
skills_root = root / "skills"
app_settings = safe_json_load(settings_path)
provider_rows = sqlite_rows(
db_path,
"select id,app_type,name,settings_config,is_current,provider_type from providers order by app_type,name;",
)
endpoint_rows = sqlite_rows(
db_path,
"select provider_id,app_type,url from provider_endpoints order by app_type,provider_id,url;",
)
skill_rows = sqlite_rows(
db_path,
"select id,name,directory,repo_owner,repo_name,repo_branch,enabled_claude,enabled_codex,enabled_opencode from skills order by name;",
)
skill_repo_rows = sqlite_rows(
db_path,
"select owner,name,branch,enabled from skill_repos order by owner,name;",
)
mcp_rows = sqlite_rows(
db_path,
"select id,name,server_config,enabled_claude,enabled_codex from mcp_servers order by name;",
)
current_codex = next((row for row in provider_rows if row["app_type"] == "codex" and row["is_current"] == 1), None)
current_claude = next((row for row in provider_rows if row["app_type"] == "claude" and row["is_current"] == 1), None)
codex_summary = summarize_codex_provider(current_codex, endpoint_rows) if current_codex else None
claude_summary = summarize_claude_provider(current_claude, endpoint_rows) if current_claude else None
fallback = fallback_flags([row for row in provider_rows if row["app_type"] in {"claude", "opencode"}])
mcp_servers = []
for row in mcp_rows:
parsed = safe_json_parse(row.get("server_config"))
mcp_servers.append(
{
"id": row["id"],
"name": row["name"],
"type": parsed.get("type", "http" if parsed.get("url") else "unknown"),
"command": parsed.get("command"),
"endpoint_host": extract_host(parsed.get("url")),
"enabled_codex": bool(row.get("enabled_codex")),
"enabled_claude": bool(row.get("enabled_claude")),
}
)
local_skill_count = 0
if skills_root.exists():
local_skill_count = sum(1 for entry in skills_root.iterdir() if entry.is_dir())
return {
"root": str(root),
"exists": root.exists(),
"app_settings": {
"language": app_settings.get("language"),
"current_provider_claude": app_settings.get("currentProviderClaude"),
"current_provider_codex": app_settings.get("currentProviderCodex"),
"skill_sync_method": app_settings.get("skillSyncMethod"),
},
"current_providers": {
"codex": codex_summary,
"claude": claude_summary,
},
"skills": {
"database_count": len(skill_rows),
"local_directory_count": local_skill_count,
"codex_enabled_count": sum(bool(row["enabled_codex"]) for row in skill_rows),
"claude_enabled_count": sum(bool(row["enabled_claude"]) for row in skill_rows),
"opencode_enabled_count": sum(bool(row["enabled_opencode"]) for row in skill_rows),
"names": [row["name"] for row in skill_rows],
},
"skill_repos": [
{
"owner": row["owner"],
"name": row["name"],
"branch": row["branch"],
"enabled": bool(row["enabled"]),
}
for row in skill_repo_rows
],
"mcp_servers": mcp_servers,
"fallback_providers": fallback,
"alignment": {
"codex_primary": bool(
codex_summary
and codex_summary.get("model") == "gpt-5.4-pro"
and codex_summary.get("reasoning_effort") == "xhigh"
),
"claude_secondary": bool(
claude_summary and claude_summary.get("default_opus_model") == "claude-opus-4-6"
),
"multi_agent": bool(codex_summary and codex_summary.get("multi_agent_enabled") is True),
"fallback_pool": all(fallback.values()),
},
}
def to_markdown(summary: dict[str, Any]) -> str:
codex = summary["current_providers"]["codex"] or {}
claude = summary["current_providers"]["claude"] or {}
lines = [
"# CC Switch Runtime Summary",
"",
f"- Root: `{summary['root']}`",
f"- Language: `{summary['app_settings'].get('language')}`",
f"- Skill sync: `{summary['app_settings'].get('skill_sync_method')}`",
"",
"## Current Providers",
"",
f"- Codex provider: `{codex.get('name')}`",
f"- Codex model: `{codex.get('model')}`",
f"- Codex reasoning: `{codex.get('reasoning_effort')}`",
f"- Codex base host: `{codex.get('endpoint_host')}`",
f"- Codex multi-agent: `{codex.get('multi_agent_enabled')}`",
f"- Claude provider: `{claude.get('name')}`",
f"- Claude default opus: `{claude.get('default_opus_model')}`",
f"- Claude base host: `{claude.get('endpoint_host')}`",
"",
"## Alignment",
"",
f"- Codex primary aligned: `{summary['alignment']['codex_primary']}`",
f"- Claude secondary aligned: `{summary['alignment']['claude_secondary']}`",
f"- Multi-agent aligned: `{summary['alignment']['multi_agent']}`",
f"- Fallback pool aligned: `{summary['alignment']['fallback_pool']}`",
"",
"## Inventory",
"",
f"- Skills in DB: `{summary['skills']['database_count']}`",
f"- Skills on disk: `{summary['skills']['local_directory_count']}`",
f"- Skill repos: `{len(summary['skill_repos'])}`",
f"- MCP servers: `{len(summary['mcp_servers'])}`",
f"- Installed skills: {', '.join(summary['skills']['names']) or 'n/a'}",
]
return "\n".join(lines) + "\n"
def main() -> int:
parser = argparse.ArgumentParser(description="Inspect local CC Switch runtime without printing secrets.")
parser.add_argument("--root", default=str(Path.home() / ".cc-switch"))
parser.add_argument("--format", choices=("json", "markdown"), default="json")
args = parser.parse_args()
summary = collect_summary(Path(args.root).expanduser())
if args.format == "markdown":
print(to_markdown(summary), end="")
else:
print(json.dumps(summary, indent=2, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())