mcp-to-cli-kit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ """Build a typed command-line interface for one or more remote MCP servers."""
@@ -0,0 +1,106 @@
1
+ """Detection of the AI coding agent this process is running under.
2
+
3
+ The CLI prints a tool's result differently for an agent than for a person: an agent gets
4
+ the server's text verbatim, a person gets it pretty-printed. There is no standard for a
5
+ harness to announce itself, so this recognises the environment variables the known
6
+ harnesses set, plus the two generic ones (``AGENT`` and ``AI_AGENT``) that carry a slug.
7
+
8
+ The table below is the single place to extend. It was researched on 2026-09-05 from
9
+ official documentation, project sources and ``vercel/detect-agent``'s ``agents.json``.
10
+ Only Claude Code is verified first hand; the rest are best effort, and a harness that
11
+ announces nothing is deliberately left out rather than guessed at from something like
12
+ ``TERM_PROGRAM``, which a human typing in the same terminal would also set.
13
+
14
+ Known to set nothing detectable: GitHub Copilot (the feature request is unshipped),
15
+ Aider, Windsurf/Cascade, and Warp's agent mode.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import os
21
+ from pathlib import Path
22
+
23
+ # Harness-specific variables, checked in this order. The presence of the variable is
24
+ # the signal; its value is not read. These win over AGENT and AI_AGENT, which a user
25
+ # may well have set by hand.
26
+ AGENT_ENV_VARS: list[tuple[str, list[str]]] = [
27
+ ("Claude Code", ["CLAUDECODE", "CLAUDE_CODE"]),
28
+ ("Gemini CLI", ["GEMINI_CLI"]),
29
+ ("Antigravity", ["ANTIGRAVITY_AGENT", "ANTIGRAVITY_CLI_ALIAS"]),
30
+ ("Codex", ["CODEX_SANDBOX", "CODEX_SANDBOX_NETWORK_DISABLED", "CODEX_CI", "CODEX_THREAD_ID"]),
31
+ # Deliberately not CURSOR_TRACE_ID: the IDE sets it for a human's terminal too
32
+ ("Cursor", ["CURSOR_AGENT", "CURSOR_CLI", "CURSOR_EXTENSION_HOST_ROLE"]),
33
+ ("Cline", ["CLINE_ACTIVE"]),
34
+ ("OpenCode", ["OPENCODE", "OPENCODE_CLIENT"]),
35
+ ("Goose", ["GOOSE_TERMINAL"]),
36
+ ("Augment", ["AUGMENT_AGENT"]),
37
+ ("Junie", ["JUNIE_DATA", "JUNIE_SHIM_PATH"]),
38
+ ("Kimi Code", ["KIMI_PLUGIN_ROOT"]),
39
+ ("Grok Build", ["GROK_PLUGIN_ROOT", "GROK_PLUGIN_DATA"]),
40
+ ("OpenClaw", ["OPENCLAW_SHELL"]),
41
+ ("Trae", ["TRAE_AI_SHELL_ID"]),
42
+ ("Pi", ["PI_CODING_AGENT"]),
43
+ ]
44
+
45
+ # Generic variables carrying a slug, checked after the table above.
46
+ GENERIC_AGENT_ENV_VARS = ["AGENT", "AI_AGENT"]
47
+
48
+ # Slugs whose title-cased form would be wrong or ugly.
49
+ AGENT_SLUG_NAMES = {
50
+ "claude-code": "Claude Code",
51
+ "cursor-cli": "Cursor",
52
+ "gemini-cli": "Gemini CLI",
53
+ "amp": "Amp",
54
+ "goose": "Goose",
55
+ "codex": "Codex",
56
+ }
57
+
58
+ # Devin announces itself with a file rather than a variable.
59
+ DEVIN_MARKER = Path("/opt/.devin")
60
+
61
+ # Name used when something says an agent is running but not which one.
62
+ UNKNOWN_AGENT = "an AI agent"
63
+
64
+
65
+ def _name_from_slug(value: str) -> str:
66
+ """Turn the value of a generic agent variable into a display name.
67
+
68
+ The value is a slug that may carry a version, as in
69
+ ``claude-code_2-1-260_agent``, so only the part before the first underscore names
70
+ the agent.
71
+
72
+ Args:
73
+ value: The raw value of AGENT or AI_AGENT.
74
+
75
+ Returns:
76
+ The display name, or the fallback when the value names nothing.
77
+ """
78
+ slug = value.strip().split("_")[0].strip().lower()
79
+ if not slug:
80
+ return UNKNOWN_AGENT
81
+
82
+ if slug in AGENT_SLUG_NAMES:
83
+ return AGENT_SLUG_NAMES[slug]
84
+
85
+ return " ".join(word.capitalize() for word in slug.split("-") if word) or UNKNOWN_AGENT
86
+
87
+
88
+ def detect_agent() -> str | None:
89
+ """Detect the AI coding agent this process is running under.
90
+
91
+ Returns:
92
+ The agent's display name, or None when no agent is detected.
93
+ """
94
+ for name, variables in AGENT_ENV_VARS:
95
+ if any(variable in os.environ for variable in variables):
96
+ return name
97
+
98
+ for variable in GENERIC_AGENT_ENV_VARS:
99
+ value = os.environ.get(variable)
100
+ if value is not None:
101
+ return _name_from_slug(value)
102
+
103
+ if DEVIN_MARKER.exists():
104
+ return "Devin"
105
+
106
+ return None