gitmux 0.1.0__tar.gz → 0.2.0__tar.gz

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.
Files changed (31) hide show
  1. gitmux-0.2.0/DESIGN.md +163 -0
  2. gitmux-0.2.0/PKG-INFO +426 -0
  3. gitmux-0.2.0/README.md +393 -0
  4. {gitmux-0.1.0 → gitmux-0.2.0}/pyproject.toml +5 -1
  5. {gitmux-0.1.0 → gitmux-0.2.0}/src/gitmux/__init__.py +1 -1
  6. gitmux-0.2.0/src/gitmux/cli.py +511 -0
  7. {gitmux-0.1.0 → gitmux-0.2.0}/src/gitmux/config.py +59 -25
  8. {gitmux-0.1.0 → gitmux-0.2.0}/src/gitmux/git_ops.py +6 -0
  9. gitmux-0.2.0/src/gitmux/mcp_server.py +313 -0
  10. gitmux-0.2.0/src/gitmux/models.py +199 -0
  11. gitmux-0.2.0/src/gitmux/registry.py +79 -0
  12. gitmux-0.2.0/src/gitmux/service.py +426 -0
  13. {gitmux-0.1.0 → gitmux-0.2.0}/tests/test_config.py +77 -21
  14. {gitmux-0.1.0 → gitmux-0.2.0}/tests/test_executor.py +1 -1
  15. gitmux-0.2.0/tests/test_mcp_server.py +117 -0
  16. gitmux-0.2.0/tests/test_registry.py +92 -0
  17. gitmux-0.2.0/tests/test_service.py +391 -0
  18. gitmux-0.1.0/DESIGN.md +0 -96
  19. gitmux-0.1.0/PKG-INFO +0 -221
  20. gitmux-0.1.0/README.md +0 -190
  21. gitmux-0.1.0/src/gitmux/cli.py +0 -535
  22. gitmux-0.1.0/src/gitmux/models.py +0 -63
  23. {gitmux-0.1.0 → gitmux-0.2.0}/.github/workflows/publish.yml +0 -0
  24. {gitmux-0.1.0 → gitmux-0.2.0}/.gitignore +0 -0
  25. {gitmux-0.1.0 → gitmux-0.2.0}/LICENSE +0 -0
  26. {gitmux-0.1.0 → gitmux-0.2.0}/src/gitmux/executor.py +0 -0
  27. {gitmux-0.1.0 → gitmux-0.2.0}/src/gitmux/hooks.py +0 -0
  28. {gitmux-0.1.0 → gitmux-0.2.0}/src/gitmux/output.py +0 -0
  29. {gitmux-0.1.0 → gitmux-0.2.0}/tests/__init__.py +0 -0
  30. {gitmux-0.1.0 → gitmux-0.2.0}/tests/test_git_ops.py +0 -0
  31. {gitmux-0.1.0 → gitmux-0.2.0}/tests/test_hooks.py +0 -0
gitmux-0.2.0/DESIGN.md ADDED
@@ -0,0 +1,163 @@
1
+ # gitmux - Design Document
2
+
3
+ ## Overview
4
+
5
+ gitmux 是一个 Python CLI 工具,通过 YAML 配置文件管理多个 git 仓库,支持批量 git 操作、分组管理、前后置 hook、并行/串行执行模式。
6
+
7
+ ## Architecture
8
+
9
+ ```
10
+ src/gitmux/
11
+ ├── cli.py # Typer CLI 入口,子命令定义(Rich 输出 + --json)
12
+ ├── mcp_server.py # MCP server(stdio,无 token)— 给 AI agent 用的工具集
13
+ ├── service.py # Rich-free 核心逻辑(target/branch 解析、运行操作、配置增删)
14
+ ├── registry.py # 本地 config→workspace 映射(~/.config/gitmux/workspaces.yaml)
15
+ ├── config.py # YAML 配置加载/验证/保存
16
+ ├── models.py # 数据模型(Repo, Group, Hook, Template)
17
+ ├── executor.py # 命令执行引擎(串行/并行)
18
+ ├── git_ops.py # Git 操作封装
19
+ ├── hooks.py # Hook 执行逻辑
20
+ └── output.py # 输出格式化(Rich 表格、进度条)
21
+ ```
22
+
23
+ ### 分层说明
24
+
25
+ - `service.py` 是**不含终端输出**的核心层:解析 target、解析 branch 别名、运行操作
26
+ (复用 `executor._execute_one`,含 hook)、状态汇总、配置增删/初始化。返回结构化数据。
27
+ - `cli.py`(给人用)在 service 之上叠加 Rich 输出;`--json` 直接吐 service 的结构化结果。
28
+ - `mcp_server.py`(给 agent 用)在 service 之上暴露 MCP 工具,返回结构化 JSON。
29
+ - 分支别名解析逻辑只在 `service.resolve_branch` 一处,CLI 与 MCP 共用,避免重复。
30
+
31
+ ## Configuration Format
32
+
33
+ ```yaml
34
+ workspace: ~/projects
35
+
36
+ templates:
37
+ node-app:
38
+ post_pull:
39
+ - npm install
40
+ pre_push:
41
+ - npm test
42
+
43
+ groups:
44
+ backend:
45
+ repos:
46
+ - name: api-server
47
+ url: git@github.com:user/api-server.git
48
+ template: node-app
49
+ - name: auth-service
50
+ url: git@github.com:user/auth-service.git
51
+ path: ~/custom/path/auth
52
+ hooks:
53
+ post_pull:
54
+ - pip install -r requirements.txt
55
+ branches:
56
+ prod: "auth-plan-*"
57
+ dev: "auth-dev-main"
58
+ frontend:
59
+ repos:
60
+ - name: web-app
61
+ url: git@github.com:user/web-app.git
62
+ template: node-app
63
+ ```
64
+
65
+ ## CLI Commands
66
+
67
+ ```
68
+ gitmux init # 初始化配置文件
69
+ gitmux add <url> --group=<group> # 添加仓库
70
+ gitmux remove <name> # 移除仓库
71
+ gitmux list # 列出所有仓库
72
+ gitmux status [--group=<group>] # 状态总览
73
+ gitmux clone [--group=<group>] # 克隆未克隆的仓库
74
+ gitmux fetch [--group=<group>] [--branches] [--parallel]
75
+ gitmux pull [--group=<group>] [--branch=<alias|alias:latest|alias:value>] [--parallel]
76
+ gitmux push [--group=<group>] [--parallel]
77
+ gitmux exec <command> [--group=<group>] [--parallel]
78
+ gitmux group list
79
+ gitmux group create <name>
80
+ gitmux group remove <name>
81
+ ```
82
+
83
+ ## Execution Flow
84
+
85
+ 1. 用户执行命令
86
+ 2. 查找配置文件:`--config` 指定 → 当前目录 `.gitmux.yaml` → `~/.gitmux.yaml`
87
+ 3. 加载配置,解析目标仓库列表(按 --group 过滤)
88
+ 3. 判断串行/并行模式
89
+ 4. 对每个仓库执行:pre-hook → git 操作 → post-hook
90
+ 5. 输出结果
91
+
92
+ ### Hook 错误处理
93
+ - pre-hook 失败 → 中断该仓库的 git 操作
94
+ - post-hook 失败 → 标记仓库失败
95
+
96
+ ### 输出模式
97
+ - 串行:实时输出,显示当前仓库名和执行步骤
98
+ - 并行:Rich 进度条,完成后汇总(成功/失败/详情)
99
+
100
+ ## Technical Stack
101
+ - CLI: Typer
102
+ - Terminal UI: Rich
103
+ - Config: PyYAML
104
+ - Parallelism: concurrent.futures.ThreadPoolExecutor
105
+ - Git: subprocess
106
+ - MCP: `mcp` SDK (>=2, `MCPServer`) — optional extra `gitmux[mcp]`
107
+ - Testing: pytest
108
+ - Build: hatchling
109
+
110
+ ## Workspace 解析设计
111
+
112
+ `workspace`(仓库本地根目录)与"仓库清单"分离,让配置文件能当作可共享文档(不含机器路径)。
113
+
114
+ **解析优先级(高→低):**
115
+ 1. `--workspace` flag(CLI)/ `--workspace`(MCP)—— 一次性覆盖
116
+ 2. 配置文件 `workspace:` 字段 —— 支持 `${ENV_VAR}` / `${ENV_VAR:-/default}` 占位符
117
+ 3. **registry 绑定** —— `~/.config/gitmux/workspaces.yaml`,按配置文件**绝对路径**为 key 映射到本地 workspace
118
+ 4. 都无 → `WorkspaceError`
119
+
120
+ **registry(`registry.py`):**
121
+ - 机器本地状态(类比 `~/.gitconfig`),不入库;位置用 `Path.home()` 解析,不用字符串 `~`。
122
+ - key = 配置文件 `Path(...).expanduser().resolve()` 绝对路径。
123
+ - CLI `gitmux workspace set/show/unset` 与 MCP `workspace_set/show/unset` 维护它。
124
+
125
+ **设计动机:** 配置文件可省略 `workspace` 成为纯仓库清单文档(跨机器/容器共享),每个环境用
126
+ `workspace set` 绑定一次本地路径。这样 `gitmux -c xxx.yaml`(含 MCP `gitmux-mcp --config xxx`)
127
+ **无需环境变量、无需 `--workspace`** 即可自动解析 workspace——尤其适合容器内 agent(不依赖
128
+ 环境变量传导)。`config_path` 在 load 时写入 `GitmuxConfig`,供 `resolve_workspace` 查 registry。
129
+
130
+ ## Markets 层设计
131
+
132
+ 为在**单一配置文件**中管理多个市场/项目(让一个 MCP server 就能服务全部),在 group 之上
133
+ 增加可选的 `markets` 层:`markets → groups → repos`。
134
+
135
+ - **数据模型**:`MarketConfig(name, groups)`;`GroupConfig` 带 `market` 反向引用;
136
+ `GitmuxConfig.markets` 为真实存储,`groups` 属性为跨市场的扁平视图(向后兼容读取)。
137
+ - **路径布局**:有 market → `{workspace}/{market}/{group}/{repo}`;无 market(旧配置)→
138
+ `{workspace}/{group}/{repo}`。`get_repo_path` 依据 `group.market` 自动选择,**无需改任何
139
+ op 签名**(市场信息随 group 携带)。
140
+ - **target 语法**:有 markets 时三段 `market/group/repo`;旧配置仍支持 `group/repo` / `repo`。
141
+ 另有 `--market` / `-m` 选择器(CLI)与 `market` 参数(MCP 工具)。
142
+ - **向后兼容**:顶层 `groups:`(无 `markets:`)加载为一个隐式 `NO_MARKET` 市场,行为与
143
+ 改造前完全一致(两段 target、三段路径)。`save_config` 按是否含真实市场决定写 `markets:`
144
+ 还是 `groups:`。
145
+ - **MCP**:新增 `list_markets` 工具;`status/fetch/clone/pull/push/list_repos/add_repo`
146
+ 均支持 `market` 参数。**一个 config + 一个 server 管所有市场**——不需要多 server、不需要
147
+ 给工具传 config 路径。
148
+
149
+ ## MCP Server 设计
150
+
151
+ 面向 AI agent 的接入。核心原则是**最小权限 / 能力即边界**:agent 不需要 shell
152
+ 权限,只能调用显式定义的工具,因此**不暴露通用 `exec`**。
153
+
154
+ - **传输**:stdio,无 token。server 作为 agent 的子进程在同一机器运行,安全边界是
155
+ OS/进程;无网络监听,无需鉴权。(远程/跨机才需要 HTTP + 鉴权,本项目不提供。)
156
+ - **无状态**:每次工具调用读 YAML → 跑 git → 返回结构化结果,不保留 per-session 状态。
157
+ - **实现**:`mcp_server.py` 用 `mcp.server.mcpserver.MCPServer`,工具直接调用
158
+ `service.py` 的纯逻辑并返回 JSON。入口 `gitmux-mcp`(`--config` 可指定配置)。
159
+ - **工具集**:list_repos / list_groups / status / fetch / clone / pull / push /
160
+ add_repo / remove_repo / init_config。git 类工具用 `target` | `group` | `all_repos`
161
+ 三选一选择仓库。
162
+ - **扩展**:将来若需 HTTP 传输,只需在 `main()` 加分支切换 `transport` 并叠加 Bearer
163
+ 鉴权,工具逻辑不变。
gitmux-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,426 @@
1
+ Metadata-Version: 2.5
2
+ Name: gitmux
3
+ Version: 0.2.0
4
+ Summary: Manage multiple git repositories with ease
5
+ Project-URL: Homepage, https://github.com/ryan/gitmux
6
+ Project-URL: Repository, https://github.com/ryan/gitmux
7
+ Project-URL: Issues, https://github.com/ryan/gitmux/issues
8
+ Author: Ryan
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: cli,devtools,git,multi-repo
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Version Control :: Git
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: pyyaml>=6.0.2
24
+ Requires-Dist: rich>=13.9.4
25
+ Requires-Dist: typer>=0.15.4
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest-mock==3.14.0; extra == 'dev'
28
+ Requires-Dist: pytest==8.3.4; extra == 'dev'
29
+ Requires-Dist: ruff==0.11.12; extra == 'dev'
30
+ Provides-Extra: mcp
31
+ Requires-Dist: mcp>=2.0.0; extra == 'mcp'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # gitmux
35
+
36
+ Manage multiple git repositories with ease. Clone, pull, push, and run commands across repos with a single command.
37
+
38
+ ## Features
39
+
40
+ - **YAML configuration** — declarative repo management
41
+ - **Group management** — organize repos into groups
42
+ - **Batch git operations** — clone/pull/push across repos
43
+ - **Pre/post hooks** — run commands before/after git operations (e.g., `npm install` after pull)
44
+ - **Template system** — share hook configs across similar repos
45
+ - **Parallel execution** — speed up operations with `--parallel` flag
46
+ - **Status overview** — see all repos' git status at a glance
47
+ - **Arbitrary command execution** — run any shell command across repos
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ pip install gitmux
53
+ ```
54
+
55
+ ## Quick Start
56
+
57
+ ```bash
58
+ # Initialize config in current directory
59
+ gitmux init
60
+
61
+ # Add repos (default group if --group omitted)
62
+ gitmux add git@github.com:user/api-server.git --group backend
63
+ gitmux add git@github.com:user/auth-service.git --group backend
64
+
65
+ # Clone all repos
66
+ gitmux clone --all
67
+
68
+ # Pull a single repo
69
+ gitmux pull backend/api-server
70
+
71
+ # Pull entire group (parallel)
72
+ gitmux pull --group backend --parallel
73
+
74
+ # Check status of all repos
75
+ gitmux status
76
+
77
+ # Run command on a specific repo
78
+ gitmux exec "git checkout main" --target backend/api-server
79
+ ```
80
+
81
+ ## Configuration
82
+
83
+ Config file lookup order (used for both reading and writing):
84
+ 1. `--config / -c` flag (explicit path)
85
+ 2. `.gitmux.yaml` in current directory
86
+ 3. `~/.gitmux.yaml` (global fallback)
87
+
88
+ ```yaml
89
+ workspace: ~/projects
90
+
91
+ templates:
92
+ node-app:
93
+ post_pull:
94
+ - npm install
95
+ pre_push:
96
+ - npm test
97
+
98
+ groups:
99
+ backend:
100
+ repos:
101
+ - name: api-server
102
+ url: git@github.com:user/api-server.git
103
+ template: node-app
104
+ - name: auth-service
105
+ url: git@github.com:user/auth-service.git
106
+ path: ~/custom/path/auth # override default path
107
+ hooks:
108
+ post_pull:
109
+ - pip install -r requirements.txt
110
+ frontend:
111
+ repos:
112
+ - name: web-app
113
+ url: git@github.com:user/web-app.git
114
+ template: node-app
115
+ ```
116
+
117
+ ### Path Resolution
118
+
119
+ - With markets: `{workspace}/{market}/{group}/{repo_name}`
120
+ - Legacy (no markets): `{workspace}/{group}/{repo_name}`
121
+ - Override per-repo with the `path` field
122
+
123
+ ### Markets (optional top layer)
124
+
125
+ For managing multiple markets/projects in **one config file** (so a single MCP server can
126
+ serve them all), wrap groups under a `markets:` layer:
127
+
128
+ ```yaml
129
+ workspace: /home/ryan/project/CIL/ReadOnly # or bind via registry / --workspace
130
+ markets:
131
+ mastercard:
132
+ groups:
133
+ base:
134
+ repos:
135
+ - name: map
136
+ url: https://code.example.net/mastercard/base/map.git
137
+ branches: { prod: "icbcMap-plan-*" }
138
+ rebate:
139
+ repos:
140
+ - name: bonus
141
+ branches: { prod: "bonus-plan-*", cmbProd: "cmbRebate-plan-*" }
142
+ projectx:
143
+ groups:
144
+ core:
145
+ repos: [ { name: svc, url: https://code.example.net/projectx/core/svc.git } ]
146
+ ```
147
+
148
+ - **Target becomes three-segment**: `market/group/repo`
149
+ ```bash
150
+ gitmux pull mastercard/rebate/bonus -b prod:~20260902
151
+ gitmux status --market mastercard # scope to a market
152
+ gitmux status -m mastercard -g rebate # a group within a market
153
+ ```
154
+ - **Path layout**: `{workspace}/{market}/{group}/{repo}`
155
+ - **Backward compatible**: a config with a top-level `groups:` (no `markets:`) keeps the
156
+ old two-segment targets (`group/repo`) and `{workspace}/{group}/{repo}` layout.
157
+
158
+
159
+
160
+ #### Workspace resolution (env placeholders & overrides)
161
+
162
+ The `workspace` value (and per-repo `path`) supports `${ENV_VAR}` placeholders, so a
163
+ config file can be a shareable "repo manifest" that contains **no machine-specific
164
+ path** — each environment supplies the real location:
165
+
166
+ ```yaml
167
+ # mastercard.yaml — a portable document; the real path comes from the environment
168
+ workspace: ${MASTERCARD_WORKSPACE}
169
+ groups:
170
+ rebate:
171
+ repos:
172
+ - name: bonus
173
+ url: https://code.example.net/mastercard/rebate/bonus.git
174
+ branches:
175
+ prod: "bonus-plan-*"
176
+ ```
177
+
178
+ Resolution precedence (highest first):
179
+
180
+ 1. `--workspace / -w` flag (CLI) or `--workspace` (MCP server)
181
+ 2. `workspace:` field in the config, with `${VAR}` / `${VAR:-/default}` expanded from the environment
182
+ 3. **Registry binding** — a local `config → workspace` map (see below)
183
+ 4. Otherwise → clear error (unset variable, no default, no flag, no binding)
184
+
185
+ ```bash
186
+ # supply the path via environment
187
+ export MASTERCARD_WORKSPACE=/home/ryan/project/CIL/MasterCard/ReadOnly
188
+ gitmux pull rebate/bonus -b prod:~20260902 -c mastercard.yaml
189
+
190
+ # or override for one invocation
191
+ gitmux status -c mastercard.yaml --workspace /tmp/scratch
192
+ ```
193
+
194
+ A literal path (`workspace: /abs/path`) still works unchanged. This lets one manifest
195
+ map to different local workspaces per machine/container (e.g. your dev box vs. an agent
196
+ container), and lets multiple manifests each declare their own `${..._WORKSPACE}` var.
197
+
198
+ #### Workspace registry (bind once, no env vars)
199
+
200
+ For the cleanest setup, **omit `workspace` from the manifest entirely** (making it a pure,
201
+ shareable repo document) and bind the local path once per machine. The binding lives in a
202
+ machine-local registry — `~/.config/gitmux/workspaces.yaml` — keyed by the config's
203
+ absolute path. No environment variables involved.
204
+
205
+ ```bash
206
+ # bind once on this machine
207
+ gitmux workspace set -c mastercard.yaml /home/ryan/project/CIL/MasterCard/ReadOnly
208
+
209
+ # from then on, --config alone resolves the workspace
210
+ gitmux pull rebate/bonus -b prod:~20260902 -c mastercard.yaml
211
+
212
+ gitmux workspace show # list all bindings + registry location
213
+ gitmux workspace show -c mastercard.yaml # effective workspace for one config
214
+ gitmux workspace unset -c mastercard.yaml # remove a binding
215
+ ```
216
+
217
+ Registry file:
218
+
219
+ ```yaml
220
+ # ~/.config/gitmux/workspaces.yaml (machine-local; do not commit)
221
+ mappings:
222
+ /home/ryan/gitmux/mastercard.yaml: /home/ryan/project/CIL/MasterCard/ReadOnly
223
+ /home/ryan/gitmux/projectx.yaml: /home/ryan/project/CIL/ProjectX/ReadOnly
224
+ ```
225
+
226
+ Because the manifest carries no path, the same file works on your dev box and inside an
227
+ agent container — each environment binds its own local path once (in the container, run
228
+ `gitmux workspace set` / the `workspace_set` MCP tool with the container's mount path).
229
+
230
+ ### Branch Management
231
+
232
+ Configure named branch aliases per repo:
233
+
234
+ ```yaml
235
+ repos:
236
+ - name: map
237
+ url: https://code.example.com/base/map.git
238
+ branches:
239
+ prod: "bInfinite-plan-*" # pattern (contains *)
240
+ dev: "bInfinite-dev-main" # fixed branch name
241
+ ```
242
+
243
+ Usage:
244
+
245
+ ```bash
246
+ gitmux pull map --branch dev # checkout fixed branch → pull
247
+ gitmux pull map --branch prod:latest # fetch → find newest matching branch → checkout → pull
248
+ gitmux pull map --branch prod:260515 # replace * → checkout bInfinite-plan-260515 → pull
249
+ gitmux pull map --branch prod:~260520 # fetch → find latest branch with date <= 260520 → checkout → pull
250
+ gitmux pull --group base --branch dev # checkout fixed branch for all repos in group
251
+ ```
252
+
253
+ Rules:
254
+ - `--branch <alias>` — alias must be a fixed branch (no `*`), otherwise error
255
+ - `--branch <alias>:latest` — alias must be a pattern (has `*`), picks newest by commit date
256
+ - `--branch <alias>:~<date>` — alias must be a pattern, picks newest matching branch with date ≤ `<date>`
257
+ - `--branch <alias>:<value>` — alias must be a pattern, replaces `*` with `<value>`
258
+
259
+ ### Hook System
260
+
261
+ Hooks run shell commands before/after git operations:
262
+
263
+ - `pre_clone`, `post_clone`
264
+ - `pre_pull`, `post_pull`
265
+ - `pre_push`, `post_push`
266
+
267
+ **Error handling:**
268
+ - Pre-hook failure → git operation is skipped
269
+ - Post-hook failure → repo marked as failed
270
+
271
+ **Template merging:** Repo-level hooks override template hooks per hook type.
272
+
273
+ ## Commands
274
+
275
+ | Command | Description |
276
+ |---------|-------------|
277
+ | `gitmux init` | Create `.gitmux.yaml` in current dir (`--global` for `~/.gitmux.yaml`) |
278
+ | `gitmux add <url> --group <g>` | Add a repository (group auto-created) |
279
+ | `gitmux remove <name>` | Remove a repository |
280
+ | `gitmux list` | List all repositories |
281
+ | `gitmux status [target]` | Show git status overview (defaults to all) |
282
+ | `gitmux clone <target>` | Clone unclosed repositories |
283
+ | `gitmux fetch <target>` | Fetch remote data (`--branches` to list branches) |
284
+ | `gitmux pull <target>` | Pull latest changes |
285
+ | `gitmux push <target>` | Push local commits |
286
+ | `gitmux exec <cmd>` | Run command in repos (`--target` to specify) |
287
+ | `gitmux group list` | List groups |
288
+ | `gitmux group create <name>` | Create a group |
289
+ | `gitmux group remove <name>` | Remove a group |
290
+ | `gitmux workspace set <path> -c <cfg>` | Bind a config to a local workspace (registry) |
291
+ | `gitmux workspace show [-c <cfg>]` | Show workspace bindings |
292
+ | `gitmux workspace unset -c <cfg>` | Remove a config→workspace binding |
293
+
294
+ ### Target Syntax
295
+
296
+ ```bash
297
+ gitmux pull map # repo 'map' in default group
298
+ gitmux pull base/map # repo 'map' in group 'base'
299
+ gitmux pull --group base # all repos in group 'base'
300
+ gitmux pull --all # all repos (explicit)
301
+ gitmux pull # error: specify target, --group, or --all
302
+ ```
303
+
304
+ With a **markets** config, targets are three-segment and `--market` scopes operations:
305
+
306
+ ```bash
307
+ gitmux pull mastercard/base/map # repo 'map' in market/group
308
+ gitmux pull --market mastercard # all repos in a market
309
+ gitmux pull -m mastercard -g rebate # a group within a market
310
+ ```
311
+
312
+ Note: `gitmux add <url>` without `--group` places the repo in the `default` group.
313
+
314
+ ### Common Options
315
+
316
+ - `--group, -g` — operate on entire group
317
+ - `--market, -m` — scope to a market (configs using the markets layer)
318
+ - `--all, -a` — operate on all repositories (required for write operations without target)
319
+ - `--parallel, -p` — run in parallel (clone/fetch/pull/push/exec)
320
+ - `--json` — emit structured JSON instead of formatted tables (for scripts/CI; `list`, `status`, `clone`, `fetch`, `pull`, `push`, `exec`)
321
+ - `--workspace, -w` — override the workspace directory (highest precedence; also settable via `${ENV_VAR}` in the config)
322
+ - `--config, -c` — custom config file path
323
+
324
+ ## MCP Server (for AI agents)
325
+
326
+ gitmux ships an [MCP](https://modelcontextprotocol.io) server so AI agents can manage
327
+ repositories through a **fixed set of tools** instead of being granted shell access.
328
+ The tool set *is* the capability boundary — there is deliberately **no generic
329
+ `exec`/shell tool**, so an agent literally cannot run arbitrary commands.
330
+
331
+ ### Install
332
+
333
+ ```bash
334
+ pip install "gitmux[mcp]"
335
+ ```
336
+
337
+ ### Transport & security
338
+
339
+ - **stdio only, no authentication.** The server runs as a child process of the
340
+ agent on the same machine; the security boundary is the OS/process. There is no
341
+ network listener, so no token is needed or used.
342
+ - For remote/cross-machine use you would need an HTTP transport with auth — not
343
+ provided here by design (keeps the local least-privilege model simple).
344
+
345
+ ### Run
346
+
347
+ ```bash
348
+ gitmux-mcp # config lookup: ./.gitmux.yaml → ~/.gitmux.yaml
349
+ gitmux-mcp --config /path/to/.gitmux.yaml
350
+ gitmux-mcp --config /path/to/mastercard.yaml --workspace /repos/mastercard
351
+ ```
352
+
353
+ The `--workspace` flag (or a `${ENV_VAR}` placeholder in the config) lets one shared
354
+ manifest resolve to the correct local path inside the agent's environment — e.g. a
355
+ different path in a Crew container than on your dev machine.
356
+
357
+ ### Agent configuration (example)
358
+
359
+ Kiro / Claude Desktop style `mcpServers` entry:
360
+
361
+ ```json
362
+ {
363
+ "mcpServers": {
364
+ "gitmux": {
365
+ "command": "gitmux-mcp",
366
+ "args": ["--config", "/home/you/.gitmux.yaml"],
367
+ "env": { "MASTERCARD_WORKSPACE": "/home/you/repos/mastercard" }
368
+ }
369
+ }
370
+ }
371
+ ```
372
+
373
+ The config's `workspace: ${MASTERCARD_WORKSPACE}` is then resolved from `env` above
374
+ (or pass `--workspace /path` in `args` instead). One MCP server per manifest keeps each
375
+ market/project isolated — the agent only sees what that config exposes.
376
+
377
+ ### Exposed tools
378
+
379
+ | Tool | Kind | Description |
380
+ |------|------|-------------|
381
+ | `list_repos` | read | List repos (optional `group` filter) |
382
+ | `list_groups` | read | List groups and repo counts |
383
+ | `list_markets` | read | List markets and group counts (empty for legacy configs) |
384
+ | `status` | read | Git status per repo (branch/clean/ahead/behind/last commit) |
385
+ | `fetch` | read | Fetch remotes (optional `show_branches`) |
386
+ | `clone` | action | Clone repos not yet cloned (idempotent) |
387
+ | `pull` | action | Pull; supports branch aliases (`dev`, `prod:latest`, `prod:~260520`, `prod:260524`) |
388
+ | `push` | action | Push local commits |
389
+ | `add_repo` | config | Add a repo declaration to the YAML (does not clone) |
390
+ | `remove_repo` | config | Remove a repo declaration (does not delete cloned dir) |
391
+ | `init_config` | config | Create a new config file (non-interactive) |
392
+ | `workspace_set` | config | Bind this config to a local workspace (registry) |
393
+ | `workspace_show` | read | Show workspace registry bindings |
394
+ | `workspace_unset` | config | Remove a config→workspace binding |
395
+
396
+ Selection args on the git tools: pass one of `target` (`"market/group/repo"` with markets,
397
+ or `"group/repo"` / `"repo"` legacy), `market`, `group`, or `all_repos=True`. Every tool
398
+ returns structured JSON.
399
+
400
+ **Not exposed:** arbitrary shell/`exec`. If a specific command is ever needed, add a
401
+ purpose-built, parameter-constrained tool rather than a generic shell escape hatch.
402
+
403
+ ## Development
404
+
405
+ ```bash
406
+ git clone https://github.com/ryan/gitmux.git
407
+ cd gitmux
408
+ pip install -e ".[dev]"
409
+ pytest
410
+ ```
411
+
412
+ ### Code Quality
413
+
414
+ Uses [Ruff](https://docs.astral.sh/ruff/) for linting and formatting:
415
+
416
+ ```bash
417
+ ruff check . # lint
418
+ ruff check --fix . # auto-fix
419
+ ruff format . # format
420
+ ```
421
+
422
+ Rules: `E`, `F`, `W`, `I` (isort), `N`, `UP` (modern Python), `B` (bugbear), `SIM`.
423
+
424
+ ## License
425
+
426
+ MIT