multi-workspace 3.2.1__tar.gz → 3.2.2__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 (54) hide show
  1. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/.gitignore +6 -0
  2. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/PKG-INFO +61 -4
  3. multi_workspace-3.2.2/README.md +94 -0
  4. multi_workspace-3.2.2/multi/_version.py +24 -0
  5. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/add.py +35 -22
  6. multi_workspace-3.2.2/multi/api.py +17 -0
  7. multi_workspace-3.2.2/multi/app_api.py +561 -0
  8. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/bootstrap.py +1 -1
  9. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/cli.py +6 -0
  10. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/cli_helpers.py +26 -10
  11. multi_workspace-3.2.2/multi/collaborator.py +369 -0
  12. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/doctor.py +159 -67
  13. multi_workspace-3.2.2/multi/errors.py +10 -0
  14. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/git_helpers.py +19 -5
  15. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/git_set_branch.py +2 -1
  16. multi_workspace-3.2.2/multi/ignore_files.py +165 -0
  17. multi_workspace-3.2.2/multi/init.py +328 -0
  18. multi_workspace-3.2.2/multi/managed_blocks.py +102 -0
  19. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/paths.py +10 -5
  20. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/remove.py +3 -0
  21. multi_workspace-3.2.2/multi/repo_urls.py +63 -0
  22. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/repos.py +35 -1
  23. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/resources/init_readme.md +1 -1
  24. multi_workspace-3.2.2/multi/service.py +115 -0
  25. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/settings.py +5 -0
  26. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/sync.py +46 -25
  27. multi_workspace-3.2.2/multi/sync_agents.py +147 -0
  28. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/sync_github.py +6 -4
  29. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/sync_vscode.py +10 -8
  30. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/sync_vscode_devcontainer.py +4 -3
  31. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/sync_vscode_extensions.py +4 -3
  32. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/sync_vscode_launch.py +6 -5
  33. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/sync_vscode_settings.py +72 -26
  34. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/sync_vscode_tasks.py +4 -3
  35. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/utils.py +60 -37
  36. multi_workspace-3.2.2/multi/worktree.py +235 -0
  37. multi_workspace-3.2.1/README.md +0 -37
  38. multi_workspace-3.2.1/multi/_version.py +0 -34
  39. multi_workspace-3.2.1/multi/errors.py +0 -22
  40. multi_workspace-3.2.1/multi/ignore_files.py +0 -201
  41. multi_workspace-3.2.1/multi/init.py +0 -99
  42. multi_workspace-3.2.1/multi/rules.py +0 -100
  43. multi_workspace-3.2.1/multi/sync_ruff.py +0 -85
  44. multi_workspace-3.2.1/multi/sync_rules.py +0 -141
  45. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/LICENSE +0 -0
  46. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/__init__.py +0 -0
  47. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/__main__.py +0 -0
  48. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/convert_monorepo.py +0 -0
  49. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/git_run.py +0 -0
  50. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/logging.py +0 -0
  51. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/open.py +0 -0
  52. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/registry.py +0 -0
  53. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/multi/sync_vscode_helpers.py +0 -0
  54. {multi_workspace-3.2.1 → multi_workspace-3.2.2}/pyproject.toml +2 -2
@@ -184,3 +184,9 @@ staticfiles
184
184
 
185
185
  .env
186
186
  _version.py
187
+
188
+ # BEGIN multi-managed: generated-files
189
+ # These files are generated by multi.
190
+ CLAUDE.md
191
+ AGENTS.md
192
+ # END multi-managed: generated-files
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multi-workspace
3
- Version: 3.2.1
3
+ Version: 3.2.2
4
4
  Summary: Multi
5
5
  Project-URL: Repository, https://github.com/gabemontague/multi
6
6
  Project-URL: Issues, https://github.com/gabemontague/multi/issues
@@ -34,7 +34,8 @@ Description-Content-Type: text/markdown
34
34
  Features:
35
35
 
36
36
  - Generates files in your root `.vscode` folder from sub-repo `launch.json`, `tasks.json`, and `settings.json` files.
37
- - Generates `CLAUDE.md` files from Cursor rules.
37
+ - Supports optional `installSets` so installer scripts can sync only a public/runtime subset of repos.
38
+ - Optionally generates `CLAUDE.md` and `AGENTS.md` files from tracked `AGENTS.parts/*.md` files.
38
39
  - In monorepo mode, syncs sub-repo GitHub workflows into root `.github/workflows`.
39
40
 
40
41
  ## Installation
@@ -57,8 +58,64 @@ To get started, create a new workspace directory that will house all your relate
57
58
  multi init
58
59
  ```
59
60
 
60
- When prompted, paste in the URLs of all the repositories you want to have in your workspace. You can optionally specify descriptions of what they do, which will be used to create a new repo-directories.mdc Cursor/Claude rule.
61
+ When prompted, paste in the URLs of all the repositories you want to have in your workspace. You can optionally specify descriptions of what they do, which can be included in generated agent instruction files when that feature is enabled.
61
62
 
62
- For automation, create or edit `multi.json` directly and run `multi sync`.
63
+ For scripts, you can also initialize non-interactively:
64
+
65
+ ```bash
66
+ multi init \
67
+ --repo https://github.com/org/backend \
68
+ --repo-description "Backend API" \
69
+ --repo https://github.com/org/frontend \
70
+ --repo-description "Frontend app"
71
+ ```
72
+
73
+ To create GitHub repositories first, add `--github-repo` entries. This uses `gh repo create` and defaults to private visibility:
74
+
75
+ ```bash
76
+ multi init \
77
+ --github-repo org/backend \
78
+ --github-description "Backend API" \
79
+ --github-repo org/frontend \
80
+ --github-description "Frontend app"
81
+ ```
82
+
83
+ For `--github-repo`, the GitHub CLI (`gh`) must already be installed and authenticated.
84
+
85
+ When repository slugs are product-prefixed, `multi init` automatically writes short local directory names into `multi.json` when the slug matches the workspace name prefix. For example, in a `t-ide/` workspace, `t-ide-cli` becomes local folder `cli`.
86
+
87
+ The same naming rule applies to `multi add`, so adding `https://github.com/org/t-ide-cli` inside a `t-ide/` workspace will also default to local folder `cli`.
88
+
89
+ For public installer workflows, add `installSets` to repo entries and run a filtered sync:
90
+
91
+ ```json
92
+ {
93
+ "repos": [
94
+ { "name": "cli", "url": "https://github.com/org/product-cli", "installSets": ["default", "dev"] },
95
+ { "name": "ios", "url": "git@github.com:org/product-ios.git", "installSets": ["dev"] }
96
+ ]
97
+ }
98
+ ```
99
+
100
+ ```bash
101
+ multi sync --install-set default
102
+ ```
103
+
104
+ Running `multi sync` without `--install-set` still includes every repo. Repos without `installSets` are included in every named set.
105
+
106
+ To grant or remove GitHub collaborator access across every GitHub repo in a workspace, use:
107
+
108
+ ```bash
109
+ multi collaborator add octocat --permission maintain --yes
110
+ multi collaborator add --yes
111
+ multi collaborator remove octocat --yes
112
+ multi collaborator recent-users
113
+ ```
114
+
115
+ When the workspace root repository has a GitHub `origin`, `multi collaborator` applies the change there too.
116
+
117
+ Recent collaborator usernames are saved under `~/.multi`, and `multi collaborator add` can prompt from that list when no username is supplied.
118
+
119
+ If one repo fails, `multi collaborator` continues through the rest of the workspace and reports the failures at the end.
63
120
 
64
121
  It is recommended you also install the [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=montaguegabe.multi-workspace) that automatically keeps your project synced when edits are made to synced files. To manually sync, you can run `multi sync`.
@@ -0,0 +1,94 @@
1
+ # multi
2
+
3
+ `multi` is a better way to work with VS Code/Cursor on multiple Git repos at once. It is an alternative to [multi-root workspaces](https://code.visualstudio.com/docs/editing/workspaces/multi-root-workspaces) that offers more flexibility and control. With `multi`, you can gain control over how tasks, debug runnables, and various IDE and linter settings are combined from multiple project repos ("sub-repos") located within a root workspace folder.
4
+
5
+ **[Documentation](https://multi.bighelp.ai/)**
6
+
7
+ Features:
8
+
9
+ - Generates files in your root `.vscode` folder from sub-repo `launch.json`, `tasks.json`, and `settings.json` files.
10
+ - Supports optional `installSets` so installer scripts can sync only a public/runtime subset of repos.
11
+ - Optionally generates `CLAUDE.md` and `AGENTS.md` files from tracked `AGENTS.parts/*.md` files.
12
+ - In monorepo mode, syncs sub-repo GitHub workflows into root `.github/workflows`.
13
+
14
+ ## Installation
15
+
16
+ ### Using `pipx`:
17
+
18
+ - Install [pipx](https://github.com/pypa/pipx)
19
+ - Run `pipx install multi-workspace`
20
+
21
+ ### Using `uv`
22
+
23
+ - Install [uv](https://docs.astral.sh/uv/getting-started/installation/)
24
+ - Run `uv tool install multi-workspace`
25
+
26
+ ## Getting started
27
+
28
+ To get started, create a new workspace directory that will house all your related repos and run:
29
+
30
+ ```
31
+ multi init
32
+ ```
33
+
34
+ When prompted, paste in the URLs of all the repositories you want to have in your workspace. You can optionally specify descriptions of what they do, which can be included in generated agent instruction files when that feature is enabled.
35
+
36
+ For scripts, you can also initialize non-interactively:
37
+
38
+ ```bash
39
+ multi init \
40
+ --repo https://github.com/org/backend \
41
+ --repo-description "Backend API" \
42
+ --repo https://github.com/org/frontend \
43
+ --repo-description "Frontend app"
44
+ ```
45
+
46
+ To create GitHub repositories first, add `--github-repo` entries. This uses `gh repo create` and defaults to private visibility:
47
+
48
+ ```bash
49
+ multi init \
50
+ --github-repo org/backend \
51
+ --github-description "Backend API" \
52
+ --github-repo org/frontend \
53
+ --github-description "Frontend app"
54
+ ```
55
+
56
+ For `--github-repo`, the GitHub CLI (`gh`) must already be installed and authenticated.
57
+
58
+ When repository slugs are product-prefixed, `multi init` automatically writes short local directory names into `multi.json` when the slug matches the workspace name prefix. For example, in a `t-ide/` workspace, `t-ide-cli` becomes local folder `cli`.
59
+
60
+ The same naming rule applies to `multi add`, so adding `https://github.com/org/t-ide-cli` inside a `t-ide/` workspace will also default to local folder `cli`.
61
+
62
+ For public installer workflows, add `installSets` to repo entries and run a filtered sync:
63
+
64
+ ```json
65
+ {
66
+ "repos": [
67
+ { "name": "cli", "url": "https://github.com/org/product-cli", "installSets": ["default", "dev"] },
68
+ { "name": "ios", "url": "git@github.com:org/product-ios.git", "installSets": ["dev"] }
69
+ ]
70
+ }
71
+ ```
72
+
73
+ ```bash
74
+ multi sync --install-set default
75
+ ```
76
+
77
+ Running `multi sync` without `--install-set` still includes every repo. Repos without `installSets` are included in every named set.
78
+
79
+ To grant or remove GitHub collaborator access across every GitHub repo in a workspace, use:
80
+
81
+ ```bash
82
+ multi collaborator add octocat --permission maintain --yes
83
+ multi collaborator add --yes
84
+ multi collaborator remove octocat --yes
85
+ multi collaborator recent-users
86
+ ```
87
+
88
+ When the workspace root repository has a GitHub `origin`, `multi collaborator` applies the change there too.
89
+
90
+ Recent collaborator usernames are saved under `~/.multi`, and `multi collaborator add` can prompt from that list when no username is supplied.
91
+
92
+ If one repo fails, `multi collaborator` continues through the rest of the workspace and reports the failures at the end.
93
+
94
+ It is recommended you also install the [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=montaguegabe.multi-workspace) that automatically keeps your project synced when edits are made to synced files. To manually sync, you can run `multi sync`.
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '3.2.2'
22
+ __version_tuple__ = version_tuple = (3, 2, 2)
23
+
24
+ __commit_id__ = commit_id = None
@@ -7,24 +7,16 @@ import click
7
7
  import git
8
8
 
9
9
  from multi.paths import Paths
10
+ from multi.repo_urls import (
11
+ derive_explicit_local_name,
12
+ derive_repo_slug_from_url,
13
+ normalize_repo_url,
14
+ )
10
15
  from multi.sync import sync
11
16
 
12
17
  logger = logging.getLogger(__name__)
13
18
 
14
19
 
15
- def _normalize_url(url: str) -> str:
16
- """Normalize a repo URL by stripping trailing / and .git for comparison."""
17
- url = url.rstrip("/")
18
- if url.endswith(".git"):
19
- url = url[:-4]
20
- return url
21
-
22
-
23
- def _derive_name_from_url(url: str) -> str:
24
- """Derive a directory name from a repo URL (last path component)."""
25
- return url.split("/")[-1]
26
-
27
-
28
20
  @click.command(name="add")
29
21
  @click.argument("repo_url")
30
22
  @click.option("--name", default=None, help="Override directory name derived from URL")
@@ -46,24 +38,43 @@ def add_cmd(repo_url: str, name: str | None):
46
38
  repos_list = config.get("repos", [])
47
39
 
48
40
  # Check for duplicate URL
49
- normalized_new = _normalize_url(repo_url)
41
+ normalized_new = normalize_repo_url(repo_url)
50
42
  for entry in repos_list:
51
43
  existing_url = entry.get("url", "")
52
- if _normalize_url(existing_url) == normalized_new:
44
+ if normalize_repo_url(existing_url) == normalized_new:
53
45
  raise click.ClickException(
54
46
  f"A repo with URL '{existing_url}' already exists in multi.json."
55
47
  )
56
48
 
57
49
  # Derive or validate name
58
- repo_name = name if name else _derive_name_from_url(repo_url)
50
+ auto_name = derive_explicit_local_name(
51
+ repo_url,
52
+ paths.root_dir.name,
53
+ )
54
+ slug_name = derive_repo_slug_from_url(repo_url)
55
+
56
+ existing_names = {
57
+ entry.get("name") or derive_repo_slug_from_url(entry.get("url", ""))
58
+ for entry in repos_list
59
+ }
60
+
61
+ if (
62
+ not name
63
+ and auto_name
64
+ and (
65
+ auto_name in existing_names
66
+ or (paths.root_dir / auto_name).exists()
67
+ )
68
+ ):
69
+ auto_name = None
70
+
71
+ repo_name = name if name else (auto_name or slug_name)
59
72
 
60
73
  # Check for duplicate name
61
- for entry in repos_list:
62
- existing_name = entry.get("name") or _derive_name_from_url(entry.get("url", ""))
63
- if existing_name == repo_name:
64
- raise click.ClickException(
65
- f"A repo with name '{repo_name}' already exists in multi.json."
66
- )
74
+ if repo_name in existing_names:
75
+ raise click.ClickException(
76
+ f"A repo with name '{repo_name}' already exists in multi.json."
77
+ )
67
78
 
68
79
  # Check target directory doesn't already exist
69
80
  target_dir = paths.root_dir / repo_name
@@ -86,6 +97,8 @@ def add_cmd(repo_url: str, name: str | None):
86
97
  new_entry: dict = {"url": repo_url}
87
98
  if name:
88
99
  new_entry["name"] = name
100
+ elif auto_name:
101
+ new_entry["name"] = auto_name
89
102
 
90
103
  repos_list.append(new_entry)
91
104
  config["repos"] = repos_list
@@ -0,0 +1,17 @@
1
+ from pathlib import Path
2
+
3
+ from multi.sync import sync
4
+
5
+
6
+ def sync_workspace(
7
+ root_dir: str | Path,
8
+ *,
9
+ install_set: str | None = None,
10
+ ensure_on_same_branch: bool = True,
11
+ ) -> None:
12
+ """Sync a Multi workspace from Python code."""
13
+ sync(
14
+ root_dir=Path(root_dir),
15
+ ensure_on_same_branch=ensure_on_same_branch,
16
+ install_set=install_set,
17
+ )