Extract CLI scaffolding + remote proxy into witan-core; add publish/test CI#130
Conversation
Both servers' `setup` commands carried divergent copies of the same scaffolding. Extract the shared surface into `witan_core.cli` (the `cli` extra): - AgentName / AGENT_NAMES — supported coding-agent constants. - make_app — the `--version`-wired cyclopts app factory. - resolve_author — --author → `git config user.name` → $USER → "unknown". - report_install — the agent-config install-result printer; styled with a rich `console`, plain `print` without one, so witan-code keeps its exact plain output without importing rich just for that line. witan-council and witan-code now import these instead of maintaining copies; both servers' `setup --dry-run` output is byte-for-byte unchanged (rich `→` vs plain `->`). agent-config-kit becomes a dep of the `cli` extra (supplies resolve_version + the InstallResult type) — a valid leaf→leaf edge both servers already have. Also close two gaps: witan-core had no CI test job (add witan-core-tests.yml, mirroring agent-config-kit's) and no PyPI publish workflow (add publish-witan-core.yml). Bump witan-core 0.1.0 → 0.2.0 for the new module. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request extracts shared CLI scaffolding—including agent name constants, the application factory, author resolution, and installation reporting—from both the witan and witan-code servers into the shared witan-core package. This refactoring eliminates duplicate code and ensures consistent CLI behavior, supported by a new suite of unit tests. The review feedback suggests hardening the author resolution helper by catching OSError instead of FileNotFoundError to safely handle any OS-level execution failures when invoking git.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
This PR continues the witan-core extraction work by moving the duplicated CLI “setup” scaffolding from witan (witan-council) and witan-code into a shared witan_core.cli module, updating both servers to import the shared helpers, and adding missing CI workflows for witan-core (tests + PyPI publish).
Changes:
- Added
witan_core.cliwith shared CLI primitives (AgentName/AGENT_NAMES,make_app,resolve_author,report_install) and added a dedicated test module for this shared behavior. - Updated
witanandwitan-codeCLIs to use the sharedwitan_core.clihelpers instead of local duplicated implementations. - Added witan-core GitHub Actions workflows for tests and publishing; bumped witan-core version to
0.2.0and updated changelog/locks accordingly.
Reviewed changes
Copilot reviewed 9 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/witan-core/witan_core/cli.py | Introduces the shared CLI scaffolding helpers extracted from both servers. |
| packages/witan-core/tests/test_cli.py | Adds tests to pin the shared CLI behavior (agent labels, author resolution, output, --version). |
| packages/witan-core/pyproject.toml | Bumps version to 0.2.0 and adds agent-config-kit to the cli extra + test dependency group. |
| packages/witan-core/uv.lock | Updates lockfile for new witan-core version and added dependencies. |
| packages/witan-core/CHANGELOG.md | Documents the 0.2.0 release and the new witan_core.cli module. |
| mcp/servers/witan/witan/cli/setup_cmd.py | Replaces duplicated agent-name/report/author logic with imports from witan_core.cli. |
| mcp/servers/witan/witan/cli/_common.py | Switches to make_app factory for consistent --version wiring. |
| mcp/servers/witan/uv.lock | Updates witan’s lockfile to witan-core 0.2.0 (and its CLI extra metadata). |
| mcp/servers/witan-code/witan_code/cli.py | Switches to shared make_app, resolve_author, and report_install from witan_core.cli. |
| mcp/servers/witan-code/uv.lock | Updates witan-code’s lockfile to witan-core 0.2.0 (and its CLI extra metadata). |
| .github/workflows/witan-core-tests.yml | Adds CI test + ruff checks workflow for the witan-core package. |
| .github/workflows/publish-witan-core.yml | Adds version-gated PyPI publish workflow and tags witan-core-v<version> after successful publish. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
ADR-0005 (path a) added, in witan-council only, a client-side stack for the
CLI to reach its *deployed* self over MCP. Extract the transport-agnostic core
into `witan_core.remote` (the `remote` extra: httpx + fastmcp) so a second
deployed server (e.g. witan-code) can reuse it instead of a third copy:
- `DeviceAuth` (witan_core.remote.oidc) — the OIDC device-authorization grant
(RFC 8628) + 0600 token cache + transparent refresh, parameterized by cache
path and `login_hint` (woven into "run `…`" messages).
- `RemoteMCPProxy` (witan_core.remote.proxy) — mirrors a FastMCP server's tool
surface over streamable-http: positional→name arg mapping via inputSchema,
`{"result": …}` envelope unwrap, per-call bearer token. Server policy is
supplied by subclass hooks: `_is_admin_tool`/`_admin_error` (break-glass
refusal), `_unknown_tool_error`, `_resolve_repo` (client-side repo).
witan-council keeps its policy local via thin shims: `witan/remote/oidc.py`
binds the `~/.config/witan/tokens.json` cache (+ `WITAN_TOKEN_CACHE`) and the
`witan login` hint; `witan/remote/proxy.py`'s `RemoteServerProxy` subclasses
the core with `_ADMIN_ONLY` + `repo.detect`. All CLI call sites
(cli/_common._srv, cli/auth, cli/__init__) and the `witan login/logout/whoami`
+ remote-proxy behavior are unchanged and byte-identical. witan-core imports
neither witan nor witan_code.
Both servers now declare the extras they use: witan → witan-core[cli,remote],
witan-code → witan-core[cli], floor bumped to >=0.2.
The exhaustive device-auth/cache/refresh and proxy arg-mapping tests move to
witan-core (23 tests); witan keeps a slimmer shim-binding suite.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ed fixture) - config.py: point the oidc_audience doc at witan_core.remote.oidc.DeviceAuth._auth_params (moved out of witan.remote.oidc in this PR). - witan-core-tests.yml: setup-uv input is enable-cache, not enable-caching — the typo silently disabled uv caching. - test_remote_oidc.py: drop the unused auth fixture param from test_non_json_metadata_raises_clean_error. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- resolve_author: catch OSError (not just FileNotFoundError) so any OS-level failure to spawn git degrades to the $USER/unknown fallback (gemini). - RemoteMCPProxy._map_args: raise RemoteToolUnavailable with a clear message when a call passes more positionals than the deployed tool accepts, instead of an unhandled IndexError (copilot); add regression test. - cli.py docstring: clarify the module never imports rich itself — the cli extra bundles it only for report_install's caller-supplied Console (copilot). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cut a release of both servers. The [Unreleased] sections already carried the new user-facing `--output-format` option (#127), making this a minor bump. Also folds in the witan-core adoption from #130 (shared CLI scaffolding + remote MCP-client stack; no behavior change; now depends on witan-core>=0.2). Publishing is version-gated: merging this to main triggers publish-witan.yml and publish-witan-code.yml to release 0.4.0 and tag witan-v0.4.0 / witan-code-v0.4.0. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
What & why
Part of Extract witan-core shared package (
wp-extract-witan-core-shared-package-e2660b), continuing after #129. Two extractions plus the missing CI/publish wiring.1. CLI scaffolding →
witan_core.cli(thecliextra)witanandwitan-codeeach carried divergent copies of the same setup scaffolding:AgentName/AGENT_NAMES— supported coding-agent constants.make_app— the--version-wired cyclopts app factory.resolve_author—--author→git config user.name→$USER→"unknown".report_install— the agent-config install-result printer; styled markup with a richconsole, plainprintwithout one (so witan-code keeps its plain output without a rich dependency).agent-config-kitbecomes a dep of thecliextra (suppliesresolve_version+ theInstallResulttype).2. Remote MCP-client stack →
witan_core.remote(theremoteextra)ADR-0005 (#128, path a) added — in witan-council only — a client-side stack for the CLI to reach its deployed self over MCP. Extracted the transport-agnostic core so a future deployed witan-code reuses it instead of a third copy:
DeviceAuth(witan_core.remote.oidc) — OIDC device-authorization grant (RFC 8628) + 0600 token cache + transparent refresh, parameterized by cache path andlogin_hint.RemoteMCPProxy(witan_core.remote.proxy) — mirrors a FastMCP server's tool surface overstreamable-http(positional→name arg mapping,{"result": …}envelope unwrap, per-call bearer token). Server policy comes from subclass hooks:_is_admin_tool/_admin_error,_unknown_tool_error,_resolve_repo.witan-council keeps its policy local via thin shims: the
~/.config/witan/tokens.jsoncache (+WITAN_TOKEN_CACHE) andwitan loginhint;RemoteServerProxysubclasses the core with_ADMIN_ONLY+repo.detect. All CLI call sites and thelogin/logout/whoami+ remote-proxy behavior are byte-identical.Both servers declare the extras they use:
witan → witan-core[cli,remote],witan-code → witan-core[cli].witan-coreimports neither server (leaf invariant holds).3. CI / publish (gaps from #129)
witan-core-tests.yml— the package had no CI test job (mirrorsagent-config-kit-tests.yml; hermetic suite, no binary step).publish-witan-core.yml— the package had no PyPI publish workflow (mirrorspublish-agent-config-kit.yml; version-gated, tagswitan-core-v<version>).Bumps witan-core
0.1.0 → 0.2.0(newcli+remotemodules).Testing
witan-core: 74 passed (CLI scaffolding + 23 remote device-auth/proxy tests moved here).witan(council): 484 passed (the delta vs 491 is the oidc coverage that moved to witan-core; witan keeps a shim-binding suite).witan-code: 191 passed.witan setup --dry-run/witan-code setup --dry-run(rich→vs plain->),witan --version, andwitan login/whoamiunder unconfigured + misconfigured remote env.prek run+uvx ruff@0.15.15 check/formatclean.🤖 Generated with Claude Code